Optional parameters without default value and Child Routes #5224

Closed
acelaya opened this Issue Oct 4, 2013 · 18 comments

Projects

None yet
acelaya commented Oct 4, 2013

I want to create routes with an optional [lang] parameter that would be used to set the application language as follows

Base example URLs

domain.com/
domain.com/page-one
domain.com/page-two
domain.com/page-two/search

In this case the language used is the default one

Example URLs with this param

domain.com/en
domain.com/en/page-one
domain.com/en/page-two
domain.com/en/page-two/search

domain.com/fr
domain.com/fr/page-one
domain.com/fr/page-two
domain.com/fr/page-two/search

In these two cases the language is defined by the user

The router configuration could be something like this:

    'router' => array(
        'routes' => array(
            'home' => array(
                'type'      => 'Segment',
                'options'   => array(
                    'route'    => '/[:lang]',
                    'constraints' => array(
                        'lang' => 'en|fr'
                    ),
                    'defaults' => array(
                        'controller' => 'Application\Controller\Index',
                        'action'     => 'index',
                    ),
                ),
            ),
            'page-one' => array(
                'type'      => 'Segment',
                'options'   => array(
                    'route'    => '[/:lang]/page-one',
                    'constraints' => array(
                        'lang' => 'en|fr'
                    ),
                    'defaults' => array(
                        'controller' => 'Application\Controller\Index',
                        'action'     => 'page-one',
                    ),
                ),
            ),
            'page-two' => array(
                'type'      => 'Segment',
                'options'   => array(
                    'route'    => '[/:lang]/page-two',
                    'constraints' => array(
                        'lang' => 'en|fr'
                    ),
                    'defaults' => array(
                        'controller' => 'Application\Controller\Index',
                        'action'     => 'page-two',
                    ),
                ),
                'may_terminate' => true,
                'child_routes' => array(
                    'search' => array(
                        'type' => 'Literal',
                        'options' => array(
                            'route' => '/search',
                        ),
                    ),
                ),
            ),
        ),
    ),

Notice that default lang value has not been set. There is a business logic onBootstrap that sets default language if lang param hasn't been set.

With this configuration, if url view helper is used, something like this:

    <?php echo $this->url("page-one", array(), array(), true) ?>

would result in a route like /page-one if no language has been set or /fr/page-one if language has previously been set to fr.

The problem comes when I try to use the url view helper and force links to inherit params by using a child route.
This:

    <?php echo $this->url("page-two/search", array(), array(), true) ?>

will throw an exception that says that lang parameter is missing. It is solved if I set a default value for it, but then the result, assuming en is the default value, is /en/page-two/search instead of /page-two/search when no language is set.

By the moment I have solved it by removing the child route an creating a new route with 'route' => '/[:lang]/page-two/search', but it is not the best solution.

Am I doing something wrong?

DMerva commented Dec 13, 2013

I have the same problem!

Owner
ezimuel commented Jun 18, 2014

@acelaya can you try using this example? https://gist.github.com/ezimuel/92d9f6b8ed62f9fd71c7#file-gistfile1-php-L7
Basically I manage the first route as '/[:lang/]' and the child as simple 'page-one', 'page-two', etc.

acelaya commented Jun 18, 2014

@ezimuel That is the kind of solution I am trying to achieve. A parent lang route and everything else as child routes.
However I just tested your solution and it has the same problem. If I don't set a default lang in the route I get a

 Fatal error: Zend\Mvc\Router\Exception\InvalidArgumentException: Missing parameter "lang"

And if I set the default value, all the routes are constructed with that default value.
What I want is to be able to create routes without the lang by default and with the lang if it has been previously defined.

This works when no child routes are involved. If my route has an optional param and no child routes, I can set it or not (that is what happens with page-one in my example) and the route is properly created.
The error happens only when the route has child routes (like my page-two or your example). In those cases I have to set either a default value for the param or specifically set it when assembling the route (with the Url view helper, for example).

This means I will never be able to assemble a /page-two/search route, it forces me to be /en/page-two/search or /fr/page-two/search.

In my webpage you can see a very simple example of what I need to make, but now it is made by setting the lang param on each route, to avoid the child routes problem, what is not the ideal solution.
If you enter http://www.alejandrocelaya.com all the routes in the top menu are assembled without a lang.
If you enter http://www.alejandrocelaya.com/en, then they have the /en prepended in all of them, just by inheriting the route params when assembling the routes.
The same happens with http://www.alejandrocelaya.com/es.

If I use child routes and set the default language, when you enter the base domain (http://www.alejandrocelaya.com) all the routes have the /en (or whatelse is the default) prepended to them.

Anyway thank you for your time. I really appreciate it.

towr commented Aug 25, 2014

You can create your own routing class to skip parameters that are missing or equal to their defaults, e.g. https://gist.github.com/towr/3320fb4ee13dfd079890

acelaya commented Aug 25, 2014

@towr thanks! I'll give that a try.

acelaya commented Aug 26, 2014

@towr I'm affraid it's not working.

I have changed the parent route which has the lang parameter in order to be a SkipableSegment instead of a Segment, and it still adds the default value of the language if child routes are defined when you try to assemble the route by using the URL view helper (for example).
That is to say, in my original example, your class works with the routes home and page-one, but not with page-two/search.
That was the same I already got with the standard Segment class.

Maybe I'm doing something wrong. Can you explain how to use your class with child routes?

towr commented Aug 26, 2014

I should really document my code better, because it even took me ages to figure out again how it worked. (Apparently a default value is required to be able to skip it.)

Anyway, I rewrote a version of your routes that works for me: https://gist.github.com/towr/74ed658e7bb64b67c950

acelaya commented Aug 26, 2014

It's weird, that's pretty much what I tried. I tested it with and without a default value. It didn't work with it and threw an error without it.
Any way, if you say it works for you, I will make more tests tomorrow.
Thank you very much for your time, I appreciate it.

kafibd commented Aug 28, 2014

Hi @acelaya ,
i'm very new this zf2. according to ZF2, child_routes should within parent route options array.But i found your child route not align with options of parent '/[:lang']. Every child of a parent should follow this convention, then it should work.
Lets give a try, who knows might work.

acelaya commented Aug 28, 2014

Thanks @kafibd, but child routes are not defined in the options, that's not the problem. Indeed child routes are working fine as long as you define the optional param.

acelaya commented Aug 28, 2014

@towr Your class do work!! In my first test I deffined a skipable option in the configuration instead of a skippable option, with double p... My bad.
But again, thanks, now it works as I intended.

Hey guys. I have the same issue with multilingual site and get the same troubles.
I tried to use SkippableSegment in routing, but get this exception:

Fatal error: Uncaught exception 'Zend\Di\Exception\MissingPropertyException' with message 'Missing instance/object for parameter route for Application\Router\Http\SkippableSegment::__construct' in /var/www/portfolio/vendor/zendframework/zendframework/library/Zend/Di/Di.php:871

Should i register this class somewhere?

towr commented Nov 28, 2014

@antonzhukov There should be no need to register the class.
The error sounds more like something that would come up if you're using a different version of Zend (I've only used this route in 2.1.5). Which version are you using?
When I google on that exception I do see a related issue in 2.1.0 (but that would equally apply to Zend\Mvc\Router\Http\Segment or any use of the long name for a route)

This looks like a duplicate of #6697

acelaya commented Nov 29, 2014

Yes @CrispCrumbs, it looks like a similar problem.

Contributor
shipleyr commented Mar 4, 2015

Hi @towr I just wanted to let you know that I've had exactly the same problem as you and I've created a module out of the gist snippet you posted above to overcome the problem you were facing. It works for me as you have described in the code above. It's a very outline module but does what you need. https://github.com/tccltd/TccSkippableSegment.

@GeeH GeeH added the To Be Closed label Mar 5, 2016
dannio commented Apr 14, 2016

Hmm after I used this plugin I can't seem to find the event in the EventManager.

Basically I need to access the params in the URL in onBootstrap like so:

$eventManager -> attach('route', function(MvcEvent $mvcEvent) {

  $params = $mvcEvent -> getRouteMatch() -> getParams();
  $route = $mvcEvent -> getRouteMatch() -> getMatchedRouteName();
  foreach ($params as $name => $value) {
    if (!isset($_GET[$name])) {
      $_GET[$name] = $value;
    }
  }

}

But it's not being recognized as an $mvcEvent. Any way to do this?

GeeH commented Jun 27, 2016

This issue has been closed as part of the bug migration program as outlined here - http://framework.zend.com/blog/2016-04-11-issue-closures.html

@GeeH GeeH closed this Jun 27, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment