-
Notifications
You must be signed in to change notification settings - Fork 25
Use the standard route parser from FastRoute #32
Use the standard route parser from FastRoute #32
Conversation
Instead of using a custom regex, nikic suggests to use parsed results: nikic/FastRoute#66 (comment) This also throws errors for missing mandatory parameters (#30).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This all makes sense to me! 👍
src/FastRouteRouter.php
Outdated
|
||
return $path; | ||
throw new LogicException('Too many parameters given'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should add a test for this as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is also not covered by a test, but it wasn't the result of any code you added. Might as well include a test for it though if it won't be much extra effort.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On second thought... if the format is /user/{id}
and I call $this->url('user', ['id' => 123, 'extra' => 'extra'])
, then the helper has enough information to produce /user/123
, but I'd get an exception because of extra
- does having too many parameters necessarily need to be cause for an error?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed with @michaelmoussa here. Passing parameters that will not be used should not be exceptional behavior, so long as the expected parameters were discovered.
(It may be interesting to raise an E_USER_NOTICE
indicating what additional parameters were passed, but ignored, though I think such behavior should be toggleable, and disabled by default.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have to allow optional parameters since they are appended to the query (by the UrlHelper ???).
src/FastRouteRouter.php
Outdated
|
||
// Placeholder in the route | ||
if ($paramIdx === count($substitutions)) { | ||
throw new LogicException('Not enough parameters given'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we could make this exception more helpful by indicating how many parameters are required and which were missing.
Each $part
that is an array appear to be tuples containing the key and a valid regex. If prior to beginning the substitutions we extract all the keys, we'll know the total number of parameters required and what their keys are. $substitutions
already tells us what we've managed to match, so something along these lines would do the trick (not tested):
throw new LogicException(sprintf('Expected parameter values for [%s], but only found [%s]', implode(',', $needed), implode(',', array_keys($substitutions))));
... where $needed
would be that list of all the required parameter names. So instead of Not enough parameters given
, we'd see Expected parameter values for [id,foo,bar], but only found [id]
or something like that.
Thoughts @xtreamwayz @weierophinney ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clearly I rushed through the review a bit hastily!
Yes, agreed with @michaelmoussa — it's far more helpful to the developer to know what was expected, and what was actually provided, as they can then figure out what they missed in their code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/FastRouteRouter.php
Outdated
foreach ($segs as $n => $seg) { | ||
if (strpos($seg, '{') !== false) { | ||
if (isset($segs[$n - 1])) { | ||
if (! isset($substitutions[$part[0]])) { | ||
throw new Exception\InvalidArgumentException( | ||
'Optional segments with unsubstituted parameters cannot ' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The messaging here is a bit confusing as well. Assuming route format of /user/{id}/{foo}/{bar}
named user
, calling $this->url('user', ['id' => 123, 'foo' => 456, 'not_bar' => 789])
would produce this exception (correct number of parameters, but wrong keys). But none of those segments are optional, so "Optional segments with unsubstituted parameters cannot contain segments with substituted parameters when using FastRoute" doesn't really tell us what's wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should use a message similar to what @michaelmoussa proposed earlier: Expected parameter values for [id,foo,bar], but received [id,foo,not_bar]
. Again, this would demonstrate to the developer what occurred so they have a reasonable chance of correcting the situation in the calling code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR definitely addresses issue #30 but did prompt me to notice a few things that were concerning. Would like to hear you guys' opinions on it to see if the concerns are valid.
I'm working on this but I'm running into some issues to get optional parameters working. The problem is that the StandardParser is returning an array with possible routes. Route [
['/test'],
['/test/', ['name', '[^/]+']],
['/test/', ['name', '[^/]+'], '/', ['id', '[0-9]+']],
] That code that I added loops through all returned routes until it has found a solution that has the exact parameters. Thinking about this... What I could try is reverse that array (going from the most parameters up to the least. And compare the required parameters with the substitution parameters. Once they all exist, we have a match. Also currently there isn't a regex check. So if only digits are allowed, even characters are accepted. |
As explained before, this is a revamp of the logic to generate the uri. It goes in reverse order through the parsed routes, starting with the longest possible route to the shortest. It checks if all required parameters are available. If not, it skips to the next (shorter) route. Major changes:
Because of the removal of the incomplete substitutions exception and the new exception thrown for values not matching their regex, I would mark this as a BC break. |
src/FastRouteRouter.php
Outdated
); | ||
$path = preg_replace($pattern, $value, $path); | ||
} | ||
$routeParser = new RouteParser(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be stored for re-use in a private property?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@xtreamwayz It appears to be stateless, so I think that'd be OK. Maybe do $routeParser = $this->getRouteParser()
and add a private getRouteParser()
for lazy instantiation and setting the private property?
src/FastRouteRouter.php
Outdated
// No valid route was found; explain which minimal required parameters are needed | ||
throw new Exception\InvalidArgumentException(sprintf( | ||
'Expected parameter values for at least [%s], but received [%s]', | ||
implode(',', $requiredParameters), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Enforce an array here? However $requiredParameters
should always be an array, or a route is returned... I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps instead of using $requiredParameters
in this way, keep a running list of the different valid parameter combinations? So in the case of your earlier example, given '/test[/{name}[/{id:[0-9]+}]]'
, the exception message would be:
"Expected any combination of: [{name,id}, {name}, {}], but received {foo,bar,baz}
"
So as you'd go through the required parameters for the potential matching routes, you'd record them in a $validParameterSets
(or something) array in order to report the exception if one arises.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Returning all possible combinations means that it always has to go through all possible combinations for that route. Plus is might also get confusing when return all combinations. Besides that, it's up to the developer to figure it out for complex routes. But what I can do is adding the route name to the exception so he can easily track what route is throwing the error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good balance I think... at least there'll be an indication of where to look.
src/FastRouteRouter.php
Outdated
* | ||
* @return array|bool | ||
*/ | ||
private function hasRequiredParameters(array $parts, array $substitutions) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@xtreamwayz What would you think of missingRequiredParameters(...)
here instead?
In the calling method where the return value is saved in $requiredParameters
, at first reading I expected that $requiredParameters
would contain a list of the parameters that were required to match that route, when in fact it was either boolean true (which I thought to mean "yes, there are required parameters") or an array (which I thought to mean "here are all the required parameters").
I think $missingParameters = $this->missingParameters($parts, $substitutions)
might be a little more obvious, as $missingParameters = false
would be taken to mean "no, there are no parameters missing" and $missingParameters = ['foo', 'bar']
would be taken to mean "the parameters that are missing are 'foo' and 'bar'".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. The function is now called missingParameters()
and always returns an array. If there are any parameters missing it will return those, otherwise it returns an empty array.
I'm a little on the fence about that... changes in a method signature or in an interface are obvious BC breaks, but a change in behavior that wouldn't necessarily break existing implementations are a bit of a gray area. If we were doing it as an enhancement then I'd lean towards BC break, but it seems more like a bug fix to me.
|
@michaelmoussa makes sense. Bugfix works for me as well. |
src/FastRouteRouter.php
Outdated
@@ -304,7 +304,8 @@ public function generateUri($name, array $substitutions = [], array $options = [ | |||
|
|||
// No valid route was found: list minimal required parameters | |||
throw new Exception\InvalidArgumentException(sprintf( | |||
'Expected parameter values for at least [%s], but received [%s]', | |||
'Route `%s` expects al least parameter values for [%s], but received [%s]', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
al
-> at
(typo)
Great work on the changes @xtreamwayz - they appear to cover all the concerns I had. I'll mark my review approved. Do you want to give it another once-over before it gets merged, @weierophinney ? |
@michaelmoussa I'd like to test this in production applications as well before merge. I'll do that later today or Monday morning. |
Do you think this is ready to merge and release, @xtreamwayz? Anything I can do to help move it along? |
@michaelmoussa I've tested this on two production sites and the tests are passing so I guess we can merge it. |
Instead of using a custom regex, nikic suggests to use parsed results:
nikic/FastRoute#66 (comment)
This also throws errors for missing mandatory parameters (#30).
This is a work in progress and should be optimized and tested extensively.