Search code examples
phpsymfonyslugsymfony-routing

How to get an associative array from URI path parameters?


I'm setting routes using Symfony/Routing component and mapping them to their respective controller and action.

The problem is that some actions (methods) receive more than one parameter and I want to solve this issue passing an array from the parameters set in the URL to the method it is calling.

(I simplified the API for defining routes, so it may seem a little different in the code examples)

 //setting new route
 Route::add('user_introduce_route',
        '/user/{name}/{$age}/{$profession}',
        ['controller' => 'App\Controllers\User.introduce'], 
        ['name' => '[a-zA-Z0-9]+', 'age' => '[0-9]+', 'profession' => '[[a-zA-Z]+]']
 );

When I navigate through /user/myuser01/programmer it calls introduceAction() set in my UserController and I want to pass these three parameters together to the method. Here is how I call the method:

public static function startRouting()
{
    $parameters = self::matchUrl(); //UrlMatcher->match()

    $class = explode('.', $parameters['controller'])[0];
    $method = explode('.', $parameters['controller'])[1];

    $class::$method(); //here I would like to pass the array with the parameters
}

What I suppose to be done in this situation is to retrieve an associative array from the parameters in the URL and pass to the method that is being called only name, age and profession slugs.

If you can help me or know any other workaround for this issue I appreciate.


Solution

  • as far as I can tell there are two canonical solutions, where the second one is slightly more elaborate. and a bonus solution, that requires changing your current routing definition approach, but I like that solution way more.

    use the $parameters

    since $parameters already contains all data from the slug with the correct name, we can just remove / filter out the controller (and other general well-known entries, if you have any):

    unset($parameters['controller']);
    unset($parameters['_route']); // <-- should be there
    $class::$method($parameters);
    

    use the route requirements

    I hope you still have the routecollection you provided to the url matcher:

    $requirements = $routecollection->get($parameters['_route'])->getRequirements();
    $reduced = [];
    foreach(array_keys($requirements) as $key) {
        // ?? null, in case there are optional route params?
        $reduced[$key] = $parameters[$key] ?? null;
    }
    $class::$method($reduced);
    

    As far as I can tell, every route parameter must be a requirement, so ... yeah. Unless you have some very very very sophisticated routes with weird regular expressions, this should work and is probably future proof regarding adding more non-path parameters to your routes.

    change your routing (bonus, would be my preference)

    the symfony framework uses the routing as well. however, every non-path route parameter is always prefixed with a _, which makes it very easy to filter out. If you would change your route definition accordingly (turning 'controller' into '_controller'), you could filter out the params quite easily:

    //$class = explode('.', $parameters['_controller'])[0];
    //$method = explode('.', $parameters['_controller'])[1];
    // different here -------------------^^ 
    
    $reduced = [];
    foreach($parameters as $key => $value) {
        if($key[0] != '_') { 
            $reduced[$key] = $value;
        }
    }
    $class::$method($reduced);
    

    and routing definition obviously has to be adapted, as mentioned.