ZF-7848: Empty static route (empty string) will NEVER match (sample from docs, Route_Hostname, Route_Static)
Description
The following is suggested in docs:
$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)
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:
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:
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:
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.
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.