Issues

ZF-7848: Empty static route (empty string) will NEVER match (sample from docs, Route_Hostname, Route_Static)

Issue Type: Bug Created: 2009-09-15T04:30:35.000+0000 Last Updated: 2010-10-20T11:43:49.000+0000 Status: Resolved Fix version(s): - 1.11.0 (02/Nov/10)

Reporter: Artur Bodera (joust) Assignee: Matthew Weier O'Phinney (matthew) Tags: - Zend_Controller

Related issues: - ZF-6299

Attachments:

Description

The following is suggested in docs:

<pre class="highlight">
$hostnameRoute = new Zend_Controller_Router_Route_Hostname(
    ':username.users.example.com',
    array(
        'controller' => 'profile',
        'action'     => 'userinfo'
    )
);

$plainPathRoute = new Zend_Controller_Router_Route_Static('');

$router->addRoute('user', $hostnameRoute->chain($plainPathRoute);

The purpose of $plainPathRoute: to create a default route for users visiting the hostname .users.example.com. Unfortunatelly this will never work, because static route of [empty string] *will never match(). Unfortunatelly, leaving only the Route_Hostname is not an option - because as stated in the manual, lone Route_Hostname will catch each and every request (?).

The bug is here: (line 78 in Zend/Controller/Router/Route/Static.php)

<pre class="highlight">
    public function match($path, $partial = false)
    {
        if ($partial) {
            if (substr($path, 0, strlen($this->_route)) === $this->_route) {
                $this->setMatchedPath($this->_route);
                return $this->_defaults;
            }
        } else {
            if (trim($path, '/') == $this->_route) {
                return $this->_defaults;
            }
        }

Why? substr() of empty string always returns false - thus this route will never match and the whole chain is ommited.

Fix:

<pre class="highlight">
    public function match($path, $partial = false)
    {
        if ($partial) {
            if (($this->_route === '' && $path === '') || substr($path, 0, strlen($this->_route)) === $this->_route) {
                $this->setMatchedPath($this->_route);
                return $this->_defaults;
            }
        } else {
            if (($this->_route === '' && $path === '') || trim($path, '/') == $this->_route) {
                return $this->_defaults;
            }
        }

Comments

Posted by Artur Bodera (joust) on 2009-09-15T05:00:49.000+0000

Modified fix to work with non-partial matches.

Posted by Artur Bodera (joust) on 2009-09-30T00:37:36.000+0000

This will not work: http://stackoverflow.com/questions/1203441/…

Posted by Artur Bodera (joust) on 2009-09-30T00:57:24.000+0000

UPDATE

It is also broken for simple chains, like the one in link above:

<pre class="highlight">
<?xml version="1.0" encoding="UTF-8"?>
adminadminindexindexloginadminloginindex

Expected: To work for urls /admin and /admin/login.

Actual: It will only match for /admin/login.

Workaround: It's caused by the following snippet in Zend_Controller_Router_Route_Chain:

<pre class="highlight">
    /**
     * Matches a user submitted path with a previously defined route.
     * Assigns and returns an array of defaults on a successful match.
     *
     * @param  Zend_Controller_Request_Http $request Request to get the path info from
     * @return array|false An array of assigned values or a false on a mismatch
     */
    public function match($request, $partial = null)
    {
        $path    = trim($request->getPathInfo(), '/');
        $subPath = $path;
        $values  = array();
        
       foreach ($this->_routes as $key => $route) {
            if ($key > 0 && $matchedPath !== null) {
                $separator = substr($subPath, 0, strlen($this->_separators[$key]));
                if ($separator !== $this->_separators[$key]) {
                    return false;
                }
                
                $subPath = substr($subPath, strlen($separator));
            }

Below is a quick fix which takes into account the behaviour of substr() on empty strings, as in this case when chain has already consumed admin and an empty '' $subpath is left for matching.

<pre class="highlight">
    /**
     * Matches a user submitted path with a previously defined route.
     * Assigns and returns an array of defaults on a successful match.
     *
     * @param  Zend_Controller_Request_Http $request Request to get the path info from
     * @return array|false An array of assigned values or a false on a mismatch
     */
    public function match($request, $partial = null)
    {
        $path    = trim($request->getPathInfo(), '/');
        $subPath = $path;
        $values  = array();
        
       foreach ($this->_routes as $key => $route) {
            if ($key > 0 && $matchedPath !== null) {
                $separator = substr($subPath, 0, strlen($this->_separators[$key]));
                if (($subPath !== '') && $separator !== $this->_separators[$key]) {
                    return false;
                }
                
                $subPath = (string)substr($subPath, strlen($separator));
            }

What happens is that we check for empty string '' and then force (string) as a result of substr(), because other routes would fail to match agains false.

This allows "default" routes to work with simple non-host-based chains!

Cheers!

Posted by Maurice Fonk (naneau) on 2009-12-25T06:47:34.000+0000

In 1.10 alpha this patch does not give the required result.

Posted by Artur Bodera (joust) on 2009-12-25T09:15:20.000+0000

Thank you for info.

I am willing to analyze it and contribute a new patch as long as there is any chance of (finally) commiting and fixing it permanently!

Posted by Edvin Seferovic (seferovic) on 2010-02-06T18:55:24.000+0000

Artur it would be great if you could look into this problem, because it is a real blocker (for me).

I need hostname based routing for my modules and the only "half"-workaround Ive managed to produce is following...

$hostnameRoute = new Zend_Controller_Router_Route_Hostname('admin.test.local',array('module' => 'admin'));

// Instead of empty static route...
// $oRoute = new Zend_Controller_Router_Route_Static('');

$oRoute = new Zend_Controller_Router_Route('/:controller/:action/*', array('module' => 'admin', 'controller' => 'index', 'action' => 'index'));

$router->addRoute('admin', $hostnameRoute->chain($oRoute));

With this Ive managed the routing part... but in the routing process the path information is build without "/" at the beginning. $this->_request->getPathInfo() returns "controller/action" instead of "/controller/action" This breaks the Zend_Navigation component !!

Maybe there is another workaround I am not aware of?

Posted by Artur Bodera (joust) on 2010-02-10T04:47:19.000+0000

Hey Edvin!

Which version are you using? Have you patched your ZF with the snippets I provided?

I use these routes (suggested by docs, sic!) every day and they work fine.

Posted by John Kleijn (448191) on 2010-02-18T05:10:32.000+0000

Same issue here. I really don't want to patch ZF. I can probably override match() and the method in the standard router to use the child class instead of Zend_Controller_Router_Route_Chain on "chain" in the config, but obviously that's hardly ideal.

Posted by John Kleijn (448191) on 2010-02-18T05:58:49.000+0000

I fixed this by overriding some methods in the involved route classes (thanks Artur), but one issue remains: it never produces a 404. It always goes to the index action of the default module. This is probably a separate related issue, but just wanted to check if people experiencing the issue in this ticket are also having this issue..

Posted by Steven Young (monkeyhybrid) on 2010-05-25T12:05:44.000+0000

I've just come up against the same issue.

Is there a reason Artur's patches can not be commited in the trunk? I'm surprised more people aren't complaining about this problem.

Posted by Matthew Weier O'Phinney (matthew) on 2010-10-20T09:31:59.000+0000

I actually get the exact opposite of what the reporter and several commenters have discovered (when testing against current trunk, which is 1.11.0beta1): I can match the /admin route, but not the /admin/login route. I'm attempting to fix this issue now.

Posted by Matthew Weier O'Phinney (matthew) on 2010-10-20T09:44:21.000+0000

Additionally, the behavior does not change with the "patch" applied.

Posted by Kim Blomqvist (kblomqvist) on 2010-10-20T10:07:08.000+0000

Matthew: check if this is related to ZF-8812.

Posted by Matthew Weier O'Phinney (matthew) on 2010-10-20T11:07:55.000+0000

Kim -- nope. (I've applied your patches locally; doesn't change anything in regards to the environment and expectations presented here.)

Posted by Matthew Weier O'Phinney (matthew) on 2010-10-20T11:43:47.000+0000

Fixed in trunk and release branch. Patch had to change due to changes that have already been introduced; basic gist was that a check for (empty($path) && empty($this->_route)) had to ORd to the existing partial conditional in the Static route.

Have you found an issue?

See the Overview section for more details.

Copyright

© 2006-2016 by Zend, a Rogue Wave Company. Made with by awesome contributors.

This website is built using zend-expressive and it runs on PHP 7.

Contacts