We have moved our forum to GitHub Discussions. For questions about Phalcon v3/v4/v5 you can visit here and for Phalcon v6 here.

Phalcon micro app routes regex pattern (all except words)

Asked same question at stackoverflow. Thought it makes sense to ask here.

Im lost with the phalcon regex routes pattern.

I want to get the id (f.e.)

url/foo/bar/baz/some123id

So id can be anything but count and find.

Usually you get this via a path /foo/{id}

But i need a pattern to exclude the words find and count.

Example code

class EntityController extends \Phalcon\Mvc\Controller
{
    public function get($id)
    {
        return $this->response->setStatusCode(200, "OK")
            ->sendHeaders()
            ->setContent("TEST: get entity by id {$id}")
            ->send();
    }

    public function count()
    {
        return $this->response->setStatusCode(200, "OK")
            ->sendHeaders()
            ->setContent("TEST: count entities")
            ->send();
    }

    public function find()
    {
        return $this->response->setStatusCode(200, "OK")
            ->sendHeaders()
            ->setContent("TEST: find entities")
            ->send();
    }
}

$app = new \Phalcon\Mvc\Micro();
$app->notFound(
    function () use ($app) {
        $app->response->setStatusCode(404, 'Not Found');
        $app->response->sendHeaders();
        $message = 'Route Not Found';
        $app->response->setContent($message);
        $app->response->send();
    }
);

$entities = new \Phalcon\Mvc\Micro\Collection();
$entities->setHandler(new EntityController());
$entities->get('/bom/count', 'count');
$entities->get('/bom/find', 'find');

// usually search by id (does not work)
// $entities->get('/bom/{id}', 'get');

// does not work with /123c (c is in "count")
// $entities->get('/bom/{id:[^count|find]+}', 'get');

// should actually work (as of PHP regex) but does not
$entities->get('/bom/{id:^(?!^count$|^find$).+}', 'get');
$app->mount($entities);
$app->handle();

// Tests
// curl -v url/bom/count
// TEST: count entities

// curl -v url/bom/find
// TEST: find entities

// curl -v url/bom/abc123
// Route Not Found

The pattern ^(?!^count$|^find$).+} works with PHP.

PHP Pattern Test

$format = "%-20s%-20s%-20s\r\n";
echo sprintf(
    $format,
    "Subject",
    "Does match",
    "Expect match"
);
$pattern = "^(?!^count$|^find$).+";
foreach (
    [
        'sku'       => true,
        'count'     => false,
        'find'      => false,
        'countfind' => true,
        'findcount' => true,
        'skucount'  => true,
        'skufind'   => true,
        'countsku'  => true,
        'findsku'   => true,
        'coun'      => true,
        'fin'       => true,
        'ount'      => true,
        'ind'       => true,
        'skucoun'   => true,
        'skufin'    => true,
        'finsku'    => true,
        'counsku'   => true,
    ] as $subject => $expectMatch
) {

    $matches = [];
    $doesMatch = preg_match("/{$pattern}/", $subject, $matches);
    echo sprintf(
        $format,
        $subject,
        $doesMatch,
        var_export($expectMatch, true)
    );
}

// Subject             Does match          Expect match
// sku                 1                   true
// count               0                   false
// find                0                   false
// countfind           1                   true
// findcount           1                   true
// skucount            1                   true
// skufind             1                   true
// countsku            1                   true
// findsku             1                   true
// coun                1                   true
// fin                 1                   true
// ount                1                   true
// ind                 1                   true
// skucoun             1                   true
// skufin              1                   true
// finsku              1                   true
// counsku             1                   true

What am i missing here?



1.2k
Accepted
answer
edited Feb '18

Solution:

\/*((?!count$|find$)[^\/\r\n\t]+)\/*

Why:

I checked what phalcon used as compiled pattern:

foreach ($app->getRouter()->getRoutes() as $route) {
    file_put_contents('tmp/_test.log', var_export($route->getCompiledPattern(), true) . PHP_EOL, FILE_APPEND);
}

and

/{id:^(?!^count$|^find$).+}

became to

#^/^(?!^count$|^find$).+$#u

And i think as Leyff da (at stack) mentioned the ^ is the problem.


Example of a /foo/bar/ -path:

/foo/bar/{id:\/*((?!count$|find$)[^\/\r\n\t]+)\/*}

becomes to

#^\/foo\/bar\/\/*((?!count$|find$)[^\/\r\n\t]+)\/*$#u

Pattern break down:

#                                       delimiter
    ^                                   start of string
        \/foo\/bar\/                    path
        \/*                             possible multiple slashes before id like "foo/bar///id123"
            (                           capture id
                (
                    ?!                  negative lookahead
                    count               string
                        $               end of string (to not match f.e. counts, count1, ...)
                    |                   or
                    find                string
                        $               end of string (to not match f.e. finds, find1, ...)
                )
                [^\/\r\n\t]+            do not capture slashes, tab or new line after id
            )
        \/*                             possible multiple slashes after id like "foo/bar/id123///"
    $                                   end of string
#                                       delimiter
u                                       unicode