Skip to content

Loading…

Fixed incorrect replacement of route elements beginning with same string #2991

Merged
merged 9 commits into from

4 participants

@mikegibson

Where a route contains a named element which starts with the name of a previous named element, the router is matching these incorrectly.

For example:

Router::connect('/:product/:productsPart', array('controller' => 'products', 'action' => 'view'));
echo Router::url(array('controller' => 'products', 'action' => 'view', 'product' => 'widget', 'productsPart' => 2));

gives /widget/widgetsPart when it should give /widget/2

I have fixed this by simply ordering the params in reverse length order before checking for matches.

@jippi
CakePHP member

Can you please provide a test case for this issue?

@mikegibson

Done

@lorenzo lorenzo commented on an outdated diff
lib/Cake/Routing/Route/CakeRoute.php
@@ -518,7 +518,12 @@ protected function _writeUrl($params) {
$out = $this->template;
$search = $replace = array();
- foreach ($this->keys as $key) {
+ $keys = $this->keys;
+
+ // Sort the keys in reverse order by length to prevent mismatches
+ uasort($keys, array($this, '_sortKeys'));
@lorenzo CakePHP member
lorenzo added a note

maybe just use uasort($keys, 'strlen'); $keys = reverse($keys) to avoid the extra sorting method

@markstory CakePHP member

Fewer function calls is important here.

The example you provided doesn't work, not sure if this is possible without a callback?

@lorenzo CakePHP member
lorenzo added a note

it should work, what is the result you are getting? Btw with reverse I meant array_reverse

AFAIK strlen returns the length of the string where uasort expects either 0, 1 or -1 in return? All examples I've found with strlen have to use a separate comparison function. I've pushed a new commit using an alternate sorting method.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@markstory markstory added this to the 2.4.7 milestone
@mikegibson

I have sorted the keys in a different way eliminating the need for an additional method in the CakeRoute class

@mikegibson

Sorry that's not going to work either as the lengths may be the same... I'll think about how to do this without a comparison method...

@mikegibson

Just added another commit which should improve things

@markstory
CakePHP member

There are numerous test failures on the 5.2 and 5.3 builds.

@mikegibson

Failures were due to different behaviour of array_combine with empty arrays in PHP <5.4. I'm now testing if $keys are empty before sorting and all tests are passing.

Mike Gibson added some commits
@markstory markstory self-assigned this
@mikegibson

I believe it's Travis CI that failed here not my code, this is all working now.

@markstory markstory merged commit 0c207db into cakephp:master

1 check passed

Details default The Travis CI build passed
@markstory markstory added a commit that referenced this pull request
@markstory markstory Only sort the keys once per request instead of on each match.
Sorting the keys property by value sorts keys with the same prefix for
free. This does change the order of the keys, but I don't think that is
actually a large issue as it is just a list.

Refs #2991
c0ac611
@HavokInspiration HavokInspiration added a commit that referenced this pull request
@markstory markstory Only sort the keys once per request instead of on each match.
Sorting the keys property by value sorts keys with the same prefix for
free. This does change the order of the keys, but I don't think that is
actually a large issue as it is just a list.

Refs #2991
87af04a
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Mar 10, 2014
  1. Sort route keys in reverse length order before replacing to prevent i…

    Mike Gibson committed
    …ncorrect matching
  2. Added a test case

    Mike Gibson committed
  3. Removed comparison method

    Mike Gibson committed
  4. Fixed key sorting

    Mike Gibson committed
  5. Use array_keys instead of array_flip

    Mike Gibson committed
  6. Test if keys are empty before sorting

    Mike Gibson committed
  7. Made it slightly neater

    Mike Gibson committed
  8. Fixed PHPCS error

    Mike Gibson committed
  9. Changed if statement to force Travis rebuild

    Mike Gibson committed
Showing with 39 additions and 10 deletions.
  1. +20 −10 lib/Cake/Routing/Route/CakeRoute.php
  2. +19 −0 lib/Cake/Test/Case/Routing/Route/CakeRouteTest.php
View
30 lib/Cake/Routing/Route/CakeRoute.php
@@ -517,18 +517,28 @@ protected function _writeUrl($params) {
}
$out = $this->template;
- $search = $replace = array();
- foreach ($this->keys as $key) {
- $string = null;
- if (isset($params[$key])) {
- $string = $params[$key];
- } elseif (strpos($out, $key) != strlen($out) - strlen($key)) {
- $key .= '/';
+ if ($this->keys !== array()) {
+
+ $search = $replace = array();
+
+ $lengths = array_map('strlen', $this->keys);
+ $flipped = array_combine($this->keys, $lengths);
+ arsort($flipped);
+ $keys = array_keys($flipped);
+
+ foreach ($keys as $key) {
+ $string = null;
+ if (isset($params[$key])) {
+ $string = $params[$key];
+ } elseif (strpos($out, $key) != strlen($out) - strlen($key)) {
+ $key .= '/';
+ }
+ $search[] = ':' . $key;
+ $replace[] = $string;
}
- $search[] = ':' . $key;
- $replace[] = $string;
+ $out = str_replace($search, $replace, $out);
+
}
- $out = str_replace($search, $replace, $out);
if (strpos($this->template, '*')) {
$out = str_replace('*', $params['pass'], $out);
View
19 lib/Cake/Test/Case/Routing/Route/CakeRouteTest.php
@@ -856,6 +856,24 @@ public function testMatchNamedParametersArray() {
}
/**
+ * Test matching of parameters where one parameter name starts with another parameter name
+ *
+ * @return void
+ */
+ public function testMatchSimilarParameters() {
+ $route = new CakeRoute('/:thisParam/:thisParamIsLonger');
+
+ $url = array(
+ 'thisParam' => 'foo',
+ 'thisParamIsLonger' => 'bar'
+ );
+
+ $result = $route->match($url);
+ $expected = '/foo/bar';
+ $this->assertEquals($expected, $result);
+ }
+
+/**
* test restructuring args with pass key
*
* @return void
@@ -941,4 +959,5 @@ public function testUTF8PatternOnSection() {
$expected = array('section' => 'weblog', 'plugin' => 'blogs', 'controller' => 'posts', 'action' => 'index', 'pass' => array(), 'named' => array());
$this->assertEquals($expected, $result);
}
+
}
Something went wrong with that request. Please try again.