5
votes

I've read all posts about routing and Zend Documentation but I still can't solve this issue.

I have a multi-language application with two modules: default and admin. The language selection is working fine (in a Controller routeShutdown Plugin), but I have some problems configuring the router:

I want to have these URL working:

/
/controller
/controller/action
/action                  (default controller)
/controller/param        (default action)
/admin
/admin/admin-controller
/admin/admin-controller/action

and using the language selector it would be:

/en
/en/controller
/en/controller/action
/en/action                  (default controller)
/en/controller/param        (default action)
/en/admin/admin-controller
/en/admin/admin-controller/action

I added this to my bootstap file (index.php):

$frontController = Zend_Controller_Front::getInstance();
$router = $frontController->getRouter();
$router->removeDefaultRoutes();
$router->addRoute('langmodcontrolleraction',

new Zend_Controller_Router_Route('/:lang/:module/:controller/:action',
    array('lang' => ':lang'))
);

$router->addRoute('langmodcontroller',
new Zend_Controller_Router_Route('/:lang/:module/:controller',
    array('lang' => ':lang',
        'action' => 'index'))
);

$router->addRoute('langmod',
new Zend_Controller_Router_Route('/:lang/:module',
    array('lang' => ':lang',
        'action' => 'index',
        'controller' => 'index'))
);

$router->addRoute('lang',
new Zend_Controller_Router_Route('/:lang',
    array('lang' => ':lang',
        'action' => 'index',
        'controller' => 'index',
        'module' => 'default'))
);

$frontController->setControllerDirectory(array(
'default'=>BASE_PATH.'app/modules/default/controllers',
'admin'=>BASE_PATH.'app/modules/admin/controllers'));    

In order to check how the router is parsing the URL, I added a var_dump to the routeShutdown plugin:

Entering to /en, I get:

array
'lang' => string 'en' (length=2)
'action' => string 'index' (length=5)
'controller' => string 'index' (length=5)
'module' => string 'default' (length=7)

which is OK. But when I enter to /en/controller1 I get:

array
'lang' => string 'en' (length=2)
'module' => string 'controller1' (length=8)
'action' => string 'index' (length=5)
'controller' => string 'index' (length=5) 

It is setting module to "controller1". How can I tell the router to set the default value to the module? And for an URL like /en/controller/param? (setting module and action to default)

1

1 Answers

6
votes

I'm afraid you're going to need to rethink your URL scheme a little, or change the way your routes are setup, as you've hit two limitations of the way ZF's routing works.

The first is that the router has no knowledge of what is or isn't a valid module, controller or action; all it does is match the strings in the URL to variables in the route. It does this by checking each route in succession, in reverse order, until it finds a match. When you hit /en/controller, it first checks your /:lang route, which won't match. It then checks /:lang/:module, which will match, because /:lang/:module will match /anything/anything unless you tell it otherwise.

With that in mind you won't be able to have both:

/en/controller
/en/action

unless you set some restrictions, as a URL like /en/foo will always be matched by whichever of the two you define last.

If you have a fairly small number of actions/controllers that don't often change, the simplest way around this is to hardcode in some possible values for the 2nd of the two routes, e.g.:

$router->addRoute('langmod', new Zend_Controller_Router_Route(
    '/:lang/:module', 
    array(
        'lang' => ':lang',
        'action' => 'index',
        'controller' => 'index'
    ),
    array(
        'module' => '(foo|bar|something)'
    )
));

replace foo, bar etc. with valid module names. Now when you hit /en/controller1 it won't match this route because controller1 doesn't match the regexp pattern defined for the :module variable. You would then need a separate /:lang/:controller route (or possibly /:lang/:controller/:action) for it to match instead.

You asked how you set a default value for some of the variables. You are actually already doing this with the action in a few of your routes, but for controller/module won't quite work in the way you are hoping. If we take your langmodcontroller route and change it to this:

$router->addRoute('langmodcontroller',new Zend_Controller_Router_Route(
    '/:lang/:module/:controller',
    array(
        'lang' => ':lang',
        'controller' => 'index'
        'action' => 'index' 
    )
));

there's now a default value for the controller variable. If we pretend for a second that this was the only route, a request for /en/blog would now get matched by this and set the request params to lang = en, module = blog, controller = index, action = index. /en/blog/index/foo would also match this route, and would give you module = blog, controller = index, action = foo. But note that even though controller = index you still need that in the URL. So limitation number two is that you always need the variable in the URL (even if it is set to your default) as long as you have something after it that isn't the default.

With these limitations in mind I'd suggest you go with something like this (defined in this order):

/:lang/:controller/:action/ (with 'index' defaults for controller and action)
/:lang/:action (with 'action' restricted to some predefined values)
/:lang/admin/:controller/:action (with 'admin' as a string in the URL, and :module set to 'admin' as the default)

This would give you URLs like this:

/en
/en/controller
/en/controller/action
/en/action
/en/controller/param
/en/admin/controller
/en/admin/controller/action

which is pretty much what you are after.

The routing in ZF is very powerful, you just need to know its quirks.