8000 [Routing] fixed several bugs and applied improvements by Tobion · Pull Request #3754 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[Routing] fixed several bugs and applied improvements #3754

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from

Conversation

Tobion
Copy link
Contributor
@Tobion Tobion commented Apr 1, 2012

Ok this PR was hard because stuff was tricky to understand and there was much room for improvement. Here a list of what is fixed. Tests pass.

  1. The RouteCollection states

    it overrides existing routes with the same name defined in the instance or its children and parents.

    But this is not true for ->addCollection() but only for ->add(). addCollection does not remove routes with the same name in its parents (only in its children). I don't think this is on purpose.
    So I fixed it by making sure there can only be one route per name in all connected collections. This way we can also simplify ->get() and ->remove() and improve performance since we can stop iterating recursively when we found the first and only route with a given name.
    See testUniqueRouteWithGivenName that fails in the old code but works now.

  2. There was an bug with $collection->addPrefix('0'); that didn't apply the starting slash. Fixed and test case added.

  3. There is an issue with ->get() that I also think is not intended. Currently it allows to access a sub-RouteCollection by specifing $name as array integer index. But according to the PHPdoc you should only be allowed to receive a Route instance and not a RouteCollection.
    See testGet that has a failing test case. I fixed this behavior.

  4. Then I recognized that ->addCollection depended on the order of applying them. So

    $collection1->addCollection($collection2, '/b');
    $collection2->addCollection($collection3, '/c');
    $rootCollection->addCollection($collection1, '/a');
    

    had a different pattern result from

    $collection2->addCollection($collection3, '/c');
    $collection1->addCollection($collection2, '/b');
    $rootCollection->addCollection($collection1, '/a');
    

    Fixed and test case added. See testDefinitionOrderDoesNotMatter.

  5. PHP could have ended in an infinite loop when one tried to add an existing RouteCollection to the tree. Fixed by throwing an exception when this situation is detected. See tests testCannotSelfJoinCollection and testCannotAddExistingCollectionToTree.

  6. I made setParent() private because its not useful outside the class itself. And remove() also removes the route from its parents. Added public getRoot() method.

  7. The Route class throwed a PHP warning when trying to set an empty requirement.

  8. Fixed issue [Routing] optimization in PhpMatcherDumper produces wrong result #3777. See discussion there for more info. I fixed it by removing the over-optimization that was introduced in 91f4097 but didn't work properly. One cannot reorder the route definitions, as is was done, because then the wrong route might me matched before the correct one. If one really wanted to do that, it would require to calculate the intersection of two regular expressions to determine if they can be grouped together (a tool that would also be useful to check whether a route is unreachable, see discussion in [RFC][Routing] Improve the current matching process #3678) We can only safely optimize routes with a static prefix within a RouteCollection, not across multiple RouteCollections with different parents. This i 8000 s especially true when using variables and regular expressions requirements.
    I could however apply an optimization that was missing yet: Collections with a single route were missing the static prefix optimization with 0 === strpos().

  9. Fixed an issue where the PhpMatcherDumper would not apply the optimization if the root collection to be dumped has a prefix itself. For this I had to rewrite compileRoutes. It is also much easier to understand now. Addionally I added many comments and PHPdoc because complex recursive methods like this are still hard to grasp.
    I added a test case for this (url_matcher3.php).

  10. Fix that Route::compile needs to recompile a route once it is modified. Otherwise we have a wrong result. Test case added.

@Tobion
Copy link
Contributor Author
Tobion commented Apr 4, 2012

it's ready now

$this->remove(array_keys($collection->all()));

$collection->setParent($this);
// the sub-collection must have the prefix of the parent (current instance) prepended because it it does not
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

duplicated it there

@stof
Copy link
Member
stof commented Apr 5, 2012

This PR conflicts with master according to github. You need to rebase your branch

@Tobion
Copy link
Contributor Author
Tobion commented Apr 5, 2012

Rebased and fixed CS.

if ($routes instanceof RouteCollection) {
$routes->remove($name);
if (is_array($name)) {
foreach ($name as $n) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could be simplified foreach((array) $name) -> no need for the if

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not break BC here but add a foreach in the caller

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did it intentionally so that the root only needs to be retrieved once for all routes-to-be-deleted.

@vicb
Copy link
Contributor
vicb commented Apr 10, 2012

@Tobion I will finish the review tomorrow, please make one fix w/ associated tests by commit the next time to ease the review (It would actually be great if you can re-arrange the PR this way, otherwise I'll deal with it).

*/
public function setParent(RouteCollection $parent)
public function getRoot()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this need to be public ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's quite useful. most of the time you need to root collection to work with it (like dumping the matcher) and might not have a reference to it anymore.

@Tobion
Copy link
Contributor Author
Tobion commented Apr 10, 2012

Ok I fixed CS mentioned by @vicb so far.
And fixed another bug in ac86a74b349d. See 9) in introduction.

@stof
Copy link
Member
stof commented Apr 10, 2012

@Tobion you will need to rebase your branch. It conflicts according to github

@Tobion
Copy link
Contributor Author
Tobion commented Apr 10, 2012

Added another fix. See 10) above
Don't be afraid, I guess this is the last fix so far. ;)

@@ -364,9 +364,11 @@ To get the diff between two versions, go to https://github.com/symfony/symfony/c
* added a TraceableUrlMatcher
* added the possibility to define options, default values and requirements for placeholders in prefix, including imported routes
* added RouterInterface::getRouteCollection
* [BC BREAK] the UrlMatcher urldecodes the route parameters only once, they were
decoded twice before. Note that the `urldecode()` calls have been change for a
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed typo there: changed
@stof I rebased. Btw, how do I rebase and resolve conflict without using the --force option when pushing? Without it, it gets rejected becaues of non-fast-forward updates.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Tobion when you rebase, you need to use --force when pushing as rebasing rewrites the history.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. The problem is github puts all commits at the end after that in Tobion added some commits section.
So conversation history is not that easy to follow.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For reference there is also another syntax git push tobion +branch_name which also forces the write.

@Tobion
Copy link
Contributor Author
Tobion commented Apr 11, 2012

We optimize regular expressions with static prefixes. But from reading the documentation of PCRE, the prefix optimization doesn't seem to increase performance that much because PCRE anchors the regex and process them from left to right.
Well, what we do is still good as it will prevent starting the regex engine at all under some conditions and groups multiple regex with the same static prefix together.
But optimizing static suffixes might be even better and be worth investigating. One could do it in PHP like 'edit' === substr($path, -4) or in the regex itself to avoid backtracking: See http://www.php.net/manual/de/regexp.reference.onlyonce.php

Static suffixes are also pretty common, e.g. /blog/{slug}/delete and /blog/{slug}/edit.

*
* @return integer Number of Routes
*/
private function countDirectChildRoutes(RouteCollection $routes)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you really need an arg here ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you mean? how else should it work?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My mistake (think we were in RouteCollection here)

fabpot added a commit that referenced this pull request Apr 11, 2012
Commits
-------

77185e0 [Routing] Allow spaces in the script name for the apache dumper
6465a69 [Routing] Fixes to handle spaces in route pattern

Discussion
----------

[Routing] Handling of space characters in the dumpers

The compiler was using the 'x' modifier in order to ignore extra spaces and line feeds but the code was flawed:

- it was actually ignoring all the spaces, not only the extra ones added by the compiler,
- all the spaces were stripped in the php and apache matchers.

The proposed fix:

- do not use the 'x' modifier any more (and then do no add extra spaces / line feeds),
- do not strip the spaces in the matchers,
- escapes the spaces (both in regexs and script name) for the apache matcher.

It also include [a small optimization](https://github.com/vicb/symfony/pull/new#L9L89) when the only token of a route is an optional variable token - the idea is to make the regex easier to read.

---------------------------------------------------------------------------

by vicb at 2012-04-10T13:59:45Z

@Baachi fixed now. Thanks.

---------------------------------------------------------------------------

by Tobion at 2012-04-10T16:01:31Z

+1, I saw no reason for pretty printing the regex in the first place (just for debugging I guess).
@vicb since you want to make the regex easier to read, I propose the remove the `P` from the variable regex `?P<bar>`, which is not needed anymore in PHP 5.3 (and we only support PHP 5.3+ anyway).

---------------------------------------------------------------------------

by vicb at 2012-04-10T16:08:36Z

@Tobion could you make a PR to this branch for the named parameters ?

---------------------------------------------------------------------------

by Tobion at 2012-04-10T16:12:34Z

I can include it in #3754 because I'm about the add 2 more fixes to it anyway.
But when I proposed to apply these fixes to 2.0 Fabien rejected it. So not sure what branch you want me to apply this.

---------------------------------------------------------------------------

by vicb at 2012-04-10T16:25:38Z

May be the best is to put it on hold while I am reviewing your PRs. There are already enough changes, we'll make an other PR after all have been sorted out.

What's the difference between 3754 and 3810 ? (3810 + 3763 = 3754 ?)

---------------------------------------------------------------------------

by Tobion at 2012-04-10T16:39:32Z

Lol you forget to link the PR numbers. At first sight I thought it's some sort of mathematical riddle. Haha
#3810 is for 2.0 =  #3763 (already merged) + #3754 for master

---------------------------------------------------------------------------

by vicb at 2012-04-10T16:52:18Z

I didn't link on purpose... the question is if '=' means strictly or loosely equal (any diffs - beside master vs 2.0) ?

---------------------------------------------------------------------------

by Tobion at 2012-04-10T17:06:04Z

It just applies my changes to 2.0. Nothing more. So master still differs from 2.0 by the addional features that were already implemented (e.g. `RouteCollection->addCollection` with optional requirements and options). But since my changes are bug fixes (except the performance improvement in #3763 but that doesn't break anything and makes 2.0 easier to maintain) I thought they should go into 2.0 as well.

---------------------------------------------------------------------------

by vicb at 2012-04-10T17:14:27Z

@Tobion only bug fixes mean "only bug fixes". You should re-open a PR for 2.0 with "only bug fixes", you might want to wait for me to review 3754.

---------------------------------------------------------------------------

by Tobion at 2012-04-10T17:21:00Z

Without #3763 it's much harder to apply the bug fixes. And now that I found 2 more bugs which requiresome rewriting of the PhpMatcherDumper, I don't want to apply all the commits by hand again for 2.0...
@stof
Copy link
Member
stof commented Apr 11, 2012

@Tobion your branch needs to be rebased

@Tobion
Copy link
Contributor Author
Tobion commented Apr 11, 2012

done

@vicb
Copy link
Contributor
vicb commented Apr 11, 2012

closed in favor of #3876

@vicb vicb closed this Apr 11, 2012
fabpot added a commit that referenced this pull request Apr 11, 2012
Commits
-------

9307f5b [Routing] Implement bug fixes and enhancements

Discussion
----------

[Routing] Implement bug fixes and enhancements (from @Tobion)

This is mainly #3754 with some minor formatting changes.

Original PR message from @Tobion:

Here a list of what is fixed. Tests pass.

1. The `RouteCollection` states

    > it overrides existing routes with the same name defined in the instance or its children and parents.

    But this is not true for `->addCollection()` but only for `->add()`. addCollection does not remove routes with the same name in its parents (only in its children). I don't think this is on purpose.
    So I fixed it by making sure there can only be one route per name in all connected collections. This way we can also simplify `->get()` and `->remove()` and improve performance since we can stop iterating recursively when we found the first and only route with a given name.
    See `testUniqueRouteWithGivenName` that fails in the old code but works now.

2. There was an bug with `$collection->addPrefix('0');` that didn't apply the starting slash. Fixed and test case added.

3. There is an issue with `->get()` that I also think is not intended. Currently it allows to access a sub-RouteCollection by specifing $name as array integer index. But according to the PHPdoc you should only be allowed to receive a Route instance and not a RouteCollection.
    See `testGet` that has a failing test case. I fixed this behavior.

4. Then I recognized that `->addCollection` depended on the order of applying them. So

        $collection1->addCollection($collection2, '/b');
        $collection2->addCollection($collection3, '/c');
        $rootCollection->addCollection($collection1, '/a');

    had a different pattern result from

        $collection2->addCollection($collection3, '/c');
        $collection1->addCollection($collection2, '/b');
        $rootCollection->addCollection($collection1, '/a');

    Fixed and test case added. See `testPatternDoesNotChangeWhenDefinitionOrderChanges`.

5. PHP could have ended in an infinite loop when one tried to add an existing RouteCollection to the tree. Fixed by throwing an exception when this situation is detected. See tests `testCannotSelfJoinCollection` and `testCannotAddExistingCollectionToTree`.

6. I made `setParent()` private because its not useful outside the class itself. And `remove()` also removes the route from its parents. Added public `getRoot()` method.

7. The `Route` class throwed a PHP warning when trying to set an empty requirement.

8. Fixed issue #3777. See discussion there for more info. I fixed it by removing the over-optimization that was introduced in 91f4097 but didn't work properly. One cannot reorder the route definitions, as is was done, because then the wrong route might me matched before the correct one. If one really wanted to do that, it would require to calculate the intersection of two regular expressions to determine if they can be grouped together (a tool that would also be useful to check whether a route is unreachable, see discussion in #3678) We can only safely optimize routes with a static prefix within a RouteCollection, not across multiple RouteCollections with different parents.  This is especially true when using variables and regular expressions requirements.
I could however apply an optimization that was missing yet: Collections with a single route were missing the static prefix optimization with `0 === strpos()`.

9. Fixed an issue where the `PhpMatcherDumper` would not apply the optimization if the root collection to be dumped has a prefix itself. For this I had to rewrite `compileRoutes`. It is also much easier to understand now. Addionally I added many comments and PHPdoc because complex recursive methods like this are still hard to grasp.
I added a test case for this (`url_matcher3.php`).

10. Fix that `Route::compile` needs to recompile a route once it is modified. Otherwise we have a wrong result. Test case added.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants
0