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

Modify Route Params before hitting the controller

Hey!

I'm currently writing an API (Phalcon Micro, version 3.4) that is versioned. That means, my routes look something like this: e.g. /2.1/my/application/exampleuser or with parameters: /{version}/my/application/{username}

These parameters are automatically passed to the Controller::Action Method by Phalcon. So my Controller / Action for beforementioned route looks like this:

class ExampleController extends \Phalcon\Di\Injectable
{
    public function userAction(int $version, string $username)
    {
        // my code here
    }
}

Now, I really don't want to add the version parameter to every single action. What I would like to do is to add a Middleware (e.g. hook into the micro:beforeExecuteRoute Event), get the version from the parameters and remove that route parameter so my userAction will look like this instead:

public function userAction(string $username)
{
    // my code here
}

What I already tried is to modify the parameter list via the dispatcher:$application->dispatcher->setParams($modifiedParams); but without any success. The parameters for this also seem to be only available within the controller itself and not in the middleware.

I would like to know how I can achieve what I want. Can this be done with Phalcon itself, or do I have to write some kind of a script to do so (or tweak nginx configuration)?

If there's any information missing, let me know.



8.4k

you can do by altering the defined route

maybe you can share with us how you defined your routes

Sure, have a look here on how I'm defining and mounting my routes. At least part of it is a custom logic. My route definition looks like this:

return [
    [
        'class'   => \App\Http\Controller\ExampleController::class,     // the controller that should be used to handle the request
        'prefix'  => '/{version}/my',                                   // route prefix
        'methods' => [
            'get'   => [
                '/application/{username}' => [
                    'action'  => 'userAction',                          // the action handling the request
                    'options' => [                                      // list of options I may apply to the route, only internal usage and not of relevance for this issue
                        'requiredAuthentication' => false
                    ],
                ],
            ],
            'post' => [
                // more routes for post
            ]
        ],
    ],
];

This array is then used to create new \Phalcon\Mvc\Micro\Collection Objects adding the routes. This looks basically like this (there's a class around it I did not add here, and I removed error handling for simplicity):

foreach ($this->routes as $route) {                                     // $this->route is the array from above
    $collection = new \Phalcon\Mvc\Micro\Collection();

    $class = $route['class'];
    $collection->setHandler(new $class($this->diContainer));            // setting the handler (controller) class
    if (array_has($route, 'prefix')) {                                  // setting the route prefix if there is one
        $collection->setPrefix($route['prefix']);
    }

    foreach ($route['methods'] as $verb => $methods) {                  // looping over all the http methods and ...
            foreach ($methods as $endpoint => $configuration) {         // ... over all the endpoints within each method to add the uri to the collection
                $collection->$verb($endpoint, $configuration['action'], $configuration['action']);
            }
        }
    $this->application->mount($collection);
}

Everything else after mounting the routes up to the point where the action is executed is basically Phalcon standard. The only additional logic is, that I hook into the micro:beforeExecuteRoute again to call an initialize method of my controller.

Can you explain a bit further on what you mean by altering the defined route? I cannot follow on this one.



8.4k
edited Feb '20

'prefix' => '/{version}/my' this tells the router is that {version} is a required "paramater" not just required

what you need is change {version} to a regex that can verify the input is what you need


$collection = new \Phalcon\Mvc\Micro\Collection;

// this will pass {version} as a paramater
$collection->setPrefix('/{version}/my'); 

// basicly the same except it must match the regex ([0-9]{1,2}\.[0-9]{1,2})
$collection->setPrefix('/{version:([0-9]{1,2}\.[0-9]{1,2})}/my'); 

// this is what you want. must supply a version but its not a paramater
$collection->setPrefix('/([0-9]{1,2}\.[0-9]{1,2})/my'); 

just change regex to your needs

I know that I can simply use a regular expression so version is no longe a parameter. In fact, when doing so, there's no way (or at least I don't know a way) to grab the information about the version from the url without manually parsing it (Yes, I can do something like substr($url, ...) I just thought maybe phalcon provides a way). I tried to find a way, that I can add the version as a named parameter and remove it within a middleware, so it is not passed to the controller.

For example: Imagine the application flow is like this: My routes are initialized the way I wrote before. Afterwards my FancyMiddleware is called (by hooking into the micro:beforeExecuteRoute Event) then the controller is executed.

// route:
return [
    [
        'class'   => \App\Http\Controller\ExampleController::class,     // the controller that should be used to handle the request
        'prefix'  => '/{version}/my',                                   // route prefix
        'methods' => [
            'get'   => [
                '/application/{username}' => [
                    'action'  => 'userAction',                          // the action handling the request
                    'options' => [                                      // list of options I may apply to the route, only internal usage and not of relevance for this issue
                        'requiredAuthentication' => false
                    ],
                ],
            ],
            'post' => [
                // more routes for post
            ]
        ],
    ],
];
// FancyMiddleware
class FancyMiddleware extends Plugin {
    public function beforeExecuteRoute(\Phalcon\Events\Event $event, \Phalcon\Mvc\Micro $app) {
        $params = $app->router->getParams();
        if (array_key_exists('version', $params) {
            $app->getDi()->setShared('version', $params['version'];
            // code to remove version from params ---- this is what I am looking for
        }
    }
}
// Controller
class MyController extends \Phalcon\Di\Injectable {
    public function userAction(string $username) {
        // my code, version is no longer passed to this method, since it is removed
    }
}


8.4k

i'm sure you have your reasons.

did you think about using closures instead of collection

you may want to trace how phalcon does end up calling your method



67

TellPopeyes Before looking at the selection algorithm, we need to understand some things about controller actions .

edited Aug '20

I know that I can simply use a regular expression so version is no longe a parameter. In fact, when doing so, there's no way (or at least I don't know a way) to grab the information about the version from the url without manually parsing it (Yes, I can do something like substr($url, ...) I just thought maybe phalcon provides a way). I tried to find a way, that I can add the version as a named parameter and remove it within a middleware, so it is not passed to the controller.

For example: Imagine the application flow is like this: My routes are initialized the way I wrote before tell pizza hut . Afterwards my FancyMiddleware is called (by hooking into the micro:beforeExecuteRoute Event) then the controller is executed.

// route:
return [
  [
       'class'   => \App\Http\Controller\ExampleController::class,     // the controller that should be used to handle the request
       'prefix'  => '/{version}/my',                                   // route prefix
       'methods' => [
           'get'   => [
               '/application/{username}' => [
                   'action'  => 'userAction',                          // the action handling the request
                   'options' => [                                      // list of options I may apply to the route, only internal usage and not of relevance for this issue
                       'requiredAuthentication' => false
                   ],
               ],
           ],
           'post' => [
               // more routes for post
           ]
       ],
   ],
];
// FancyMiddleware
class FancyMiddleware extends Plugin {
  public function beforeExecuteRoute(\Phalcon\Events\Event $event, \Phalcon\Mvc\Micro $app) {
      $params = $app->router->getParams();
      if (array_key_exists('version', $params) {
          $app->getDi()->setShared('version', $params['version'];
          // code to remove version from params ---- this is what I am looking for
      }
  }
}
// Controller
class MyController extends \Phalcon\Di\Injectable {
  public function userAction(string $username) {
      // my code, version is no longer passed to this method, since it is removed
  }
}

Your information is very interesting. Thank you for sharing..I can achieve what I want. Can this be done with Phalcon itself, or do I have to write some kind of a script to do so