4

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?

Leo Galleguillos
  • 1,810
  • 3
  • 16
  • 38

3 Answers3

7

My comment was turning into an answer. So, here you go.

Next time, please make sure to follow the How to Ask.


Please, 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)

rkeet
  • 3,221
  • 2
  • 21
  • 43
-1

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.

Leo Galleguillos
  • 1,810
  • 3
  • 16
  • 38
  • 1
    Though this would work, it breaks the pattern of a single action on a single route. Something which is explicitly done in ZF. I would seriously advise against this, as it creates some magic, which you know about today, but will be a head-scratcher in a year. Then again, would work ... – rkeet Apr 11 '19 at 06:31
-1

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)
Ermenegildo
  • 1,091
  • 1
  • 10
  • 16
  • We actually used this route structure initially. However, the `:name` parameter often contains characters that are not suitable for URLs, such as slashes. So, the `:name` parameter must be a query parameter rather than a segment of a route. – Leo Galleguillos Apr 19 '19 at 01:19
  • The question was not about route configuration on the "path" part (as shown in my answer), but in the query part (after a `?` in the complete URL). Your answer shows basic route configuration setup as also explained in [the docs](https://zendframework.github.io/tutorials/in-depth-guide/understanding-routing/) I'd already linked. – rkeet Apr 24 '19 at 14:24