Leo Galleguillos
Leo Galleguillos

Reputation: 2720

Zend Framework 3 - route based on query string

In Zend Framework 3, is it possible to route to a controller depending on whether a URL contains a query string?

For example, I have these two URLs:

/users
/users?name=Bob

I would like the first route to call a UsersController and second route to call a NameController.

Is this possible?

Upvotes: 3

Views: 1149

Answers (3)

rkeet
rkeet

Reputation: 3468

Have a read of the ZF3 docs on Router and possibly RFC 3986 Chapter 3 - Syntax Components which shows what is path and what is query.

From RFC 3986 Chapter 3 - Syntax Components

The following are two example URIs and their component parts:

     foo://example.com:8042/over/there?name=ferret#nose
     \_/   \______________/\_________/ \_________/ \__/
      |           |            |            |        |
   scheme     authority       path        query   fragment
      |   _____________________|__
     / \ /                        \
     urn:example:animal:ferret:nose

ZF3 route configuration is typically configuration on a path. (This is also true of pretty much every common framework.) Yes, variables can be part of a path. As such, they're configured in route configuration. Advanced configuration of framework routing often also allows for changes/requirements in schemes and authorities.

Not part of routing configurations are the 'query' and 'fragment' parts.

If you want to do something, e.g. catch a "name" key/value pair and do routing accordingly, you're going to have to create a "catcher" (or whatever the name for that would be) on the path and determine the redirection yourself.

For example, you could do something like this answer. If your controller instance extends the default Zend Framework AbstractActionController class, then you should have the forward plugin available. From the docs:

Forward returns the results of dispatching the requested controller; it is up to the developer to determine what, if anything, to do with those results. One recommendation is to aggregate them in any return value from the invoking controller.

As an example:

 $foo = $this->forward()->dispatch('foo', ['action' => 'process']);
 return [
     'somekey' => $somevalue,
     'foo'     => $foo,
 ];

Of course, you could immediately return it.

Another option is the redirect plugin (same link).

return $this->redirect()->toRoute('login-success');

With all this you can do something like:

$name = $this->params()->fromQuery('name', null);

if ($name) {
    // dispatch

    if ($dispatchResult) {
        // return special
    }
} 

// redirect

Where you redirect to a route name (ie. configured path)

Upvotes: 4

Ermenegildo
Ermenegildo

Reputation: 1308

Even if rkeet's answer is technically correct, I'd avoid that solution because it transform a single HTTP request into two (the initial GET and the forward/redirect).

I'd configure the route paths in order to have two different routes:

'router' => [
    'routes' => [
        'users' => [
            'type' => Literal::class,
            'options' => [
                'route' => '/users',
                'defaults' => [
                    'controller' => UsersController::class,
                    'action' => 'index'
                ],
                'may_terminate' => true,
                'child_routes' => [
                    'name' => [ // This is the subroute name
                        'type' => Segment::class,
                        'options' => [
                            'route' => '/:name', // Option 1
                            // 'route' => '/name/:name', // Option 2
                            'defaults' => [
                                'controller' => NameController::class,
                                'action' => 'index'
                            ],
                            'constraints' => [
                                'name' => '[a-zA-Z]+',
                            ]
                        ]
                    ]
                ]
            ]
        ]
    ]
]

With this configuration, you can access the following URL's

/users
/users/Bob (option 1)
/users/name/Bob (option 2)

Upvotes: -1

Leo Galleguillos
Leo Galleguillos

Reputation: 2720

As rkeet mentioned, query string is not part of the route. Therefore, we simply added a ternary to the route config array:

'users' => [ 
    'type'    => Literal::class,
    'options' => [ 
        'route'    => '/users',
        'defaults' => [
            'controller' => isset($_GET['name'])
                          ? NameController::class
                          : UsersController::class,
            'action'     => 'index',
        ],   
    ],   
    'may_terminate' => true,
],

It's self explanatory, but basically the controller that is dispatched is dependent on whether a value is set in the query string.

We decided not to use the forward() plugin because we do not want to instantiate an extra controller superfluously.

Upvotes: -1

Related Questions