-
Notifications
You must be signed in to change notification settings - Fork 18
Add query parameters and fragment support #27
Add query parameters and fragment support #27
Conversation
@michaelmoussa I have added the PR. I have some mixed feeling about this. I know it's already discussed too much and don't want to cause more headaches. I had some doubts while implementing this. Right now it has a fallback for the v1 and v2 behavior. So basically you can upgrade to v3 without many issues. However since we using the $params also for query and fragment, the I can only think of some solutions:
Ok, back to the PR... The tests are in place but I'm not sure if there are more tests needed.
|
@xtreamwayz @michaelmoussa On one side it's seem natural to have sorry guys for bring it up again, but i believe that it's better to expose any doubt (they usually come for a reason). about the other question: kind regards |
This is what I was thinking as well. This PR adds the option to add the query and fragment in a proper way. So there is no need to support any hackisch way to accomplish the same. |
I tried adding a query string to a FastRoute path the other day, and there were no errors. This means that someone might have a path like The same is probably true for the fragment, and conceivably even other parts of the URL, which is why I used Again, I don't currently have this requirement, but others may. FWIW, I think moving |
Hello @glen-84, You can define route names like this:
Route names are merely string identifiers (array keys) and using this representation (dots instead of forward slashes) makes it more clear that $routeName is not a path (i also use forward slashes as a habit) UrlHelper so far is a proxy to ServerUrlHelper instead is a completely different helper: it generates a full url from a given path. Pls let me know if you are referring to something else. kind regards |
I'm not referring to the route name, I'm referring to the route path. For example: [
'name' => 'api.ping',
'path' => '/api/ping?fixed=value', // *** NOTE THE QUERY STRING ***
'middleware' => App\Action\PingAction::class,
'allowed_methods' => ['GET'],
] The above is valid IIRC, but will not play nicely with the code in this PR. |
@glen-84
you can see that the query string is stripped away before the matching happens. kind regards added later: when you define a route in the config file the |
@glen-84 the router deals in parsing and building the absolute-path of a relative-reference of type path-pattern: /user[/{action}[/{id:\d+}]] handled absolute-paths /user (with a default implicit action index) the absolute-path is considered related to the application. the real absolute-path could have a base path prepended, but this is handled outside the router so that the application can be moved easilty inside directories without the need of redefining all the routes. kind regards |
From my understanding, a router is just something that looks at the request to determine which code to run. The ZF documentation confirms this:
The ZF router also allows you to specify a query and fragment part in the options passed to the I'm not concerned with what is correct or what is intended (I don't have query strings in my own route paths), I'm only pointing out that the current code may return invalid URIs as it does not currently account for this possibility. |
Hello @glen-84 The old zf Query route also was used as a child route under a Literal or Segment route and when assembling the url you needed to add an ending "/query" part to the route name. So why should we use query string inside (All other features such as hostname matching if available are implemented differently, so we need to have a common ze way of passing them properly to the used implementation via an the options key |
You're missing the point. Add this to your routes: [
'name' => 'test',
'path' => '/xxx?a=b',
'middleware' => /* insert something here */,
'allowed_methods' => ['GET']
] Then generate a URI (example with Twig): {{ path('test') }} It will output (at least with FastRoute):
Is it right to do this? I don't care (I don't do it). |
Hello @glen-84 , I believe that the term As of now I think this is possible only in the router implementation (the injectRoute() method at least this is the case for zendrouter and fastroute implementation). kind regards |
A lot of good points have been raised, and the more I look at the continued discussion, the more I think that the approach I encouraged might not be the best one. Specifically:
and
The motivation to put these items into the public function __invoke(
$routeName = null,
array $routeParams = [],
array $queryParams = [],
$fragmentIdentifier = '',
array $options = []
) I mean yeah it's ugly and long etc, but it was one of the first options that were discussed and all of the arguments are optional, so it's only as ugly as you need it to be for the URL you're trying to generate. If we bite the bullet on appearance and go with the above, the benefits are as follows:
As is the case now, the first two parameters for What do you think? |
I'm generally of the opinion that one should take reasonable measures to protect developers from making common mistakes, but as @pine3ree said, the term The worst that would happen if we left things alone and someone generated a URL for a route containing a query string in the path, and appended query params of their own, would be that they'd get a goofy looking URL with two If we wanted to prevent this anyway, it'd probably be something to discuss in one of the router repos, as it's not really in scope for the |
Given that there can only be route parameters, query parameters and a fragment and everything else goes in options, this signature shouldn't need to change in the future. This signature is the same I have used in zendframework/zend-expressive-twigrenderer#18: It felt the best way when coding it. @michaelmoussa I'll update this PR today. |
The only issue that I saw with this signature was if you just wanted a "current page" route without reusing params, it would be something like: $url(null, [], [], null, ['reuse_result_params' => false]) That's why I was trying to make use of the 2nd parameter, but perhaps it's not the end of the world. I'm not really against it. |
@glen-84 There are always edge cases :) @michaelmoussa PR is updated. |
Hello @xtreamwayz @michaelmoussa About reusing the current route and current route parameters I feel that they can be convenient and useful features, but in some way of less importance in comparison to the other params. kind regards |
…xtreamwayz/zend-expressive-helpers into feature/27 Close #27
@xtreamwayz I made a couple of minor tweaks - look OK to you? If so I'll merge this today. |
@xtreamwayz ah - the test failures raise a question, actually. The tests were passing |
why not just: $reuseResultParams = !isset($options['reuse_result_params']) || (bool) $options['reuse_result_params']; kind regards |
@michaelmoussa The tests fail on this: 8753d79...feature/27#diff-c051d24dcf87b2f1bfb6a96ed36220a3R97 if ($fragmentIdentifier !== '') {
$path .= '#' . $fragmentIdentifier;
} I've used |
@michaelmoussa The other change you made: It tested for $result first, if there is no result, it doesn't need to go through the other tests. With your change it has to do all tests always. It's a minor speed difference. Also, I had a question about the identifier but didn't ask before. Should it be |
@pine3ree updated - thanks! Silly oversight on my part. |
@xtreamwayz I ran a quick test locally to check (php7, xdebug turned off): <?php
$foo = [];
$start = microtime(true);
for ($i = 0; $i < 40000; $i++) {
!isset($foo['bar']) || (bool) $foo['bar'];
}
echo (microtime(true) - $start) * 1000; I ended up needing around 40k executions in the loop in order to add ~1ms to the execution time (~20k on php56), so in this case I don't think we should worry about it because generating that many URLs in a single request is a far edge case. I usually lean towards readability over tiny performance optimizations, but if you have strong feelings about it I don't mind changing it since the original version isn't totally unreadable or anything like that. Regarding If we change the signature to say
Changing the condition to |
Hello @michaelmoussa, if (!empty($fragmentIdentifier) || is_string($fragmentIdentifier)) { be less cryptic? :-) In my opinion @xtreamwayz @michaelmoussa In the implementation i would add type checking (pseudo-code) if (null !== $fragmentIdentifier) { // quick test to check if fragment is set, most of the cases
if (!is_string($fragmentIdentifier)) {
// throw exception
}
if ('' !== $fragmentIdentifier) {
$path .= '#' . $fragmentIdentifier;
}
} kind regards |
@xtreamwayz @michaelmoussa I am not sure this has been discussed previously $fragmentIdentifier = ltrim($fragmentIdentifier, '#'); // updated left-trim obviously.... before checking for empty string (last if in my pseudo code) |
Looks good to me.
I agree. It's just unneeded micro optimization.
According to the specs it must have this format: |
@michaelmoussa Once this is merged I'll finish the Twig helper and the tests and after that I'll add the same for zend-view and plates. |
@xtreamwayz kind regards |
|
@xtreamwayz @glen-84 here is a more thorough explanation of what's allowed. So... this? -> I can make a class constant out of that and throw an |
@michaelmoussa As you are saying the router is taking care of the path, http_build_query does the query parameters. It might be a nice addition to build a complete fail safe package. |
@xtreamwayz done. Anything else you can think of, or are we pretty much 👍 ? |
LGTM |
Ok @xtreamwayz this has been merged into the |
@michaelmoussa (@xtreamwayz) No sorry, i mis-read the code, it's actually allowing it. |
@pine3ree correct, it is allowing it, but when I looked I realized there's a simpler way to do it, so I've pushed an update. /cc @xtreamwayz |
@michaelmoussa I am not sure if we should also pre-test for the empy string with an early return or just not allow it at all. kind regards |
@pine3ree If the fragment === null it skips the fragment and if the fragment doesn't validate the regex it throws an error. The regex says that it needs to contain one or more characters from that list. So the fragment must be null or valid and there is no need to check if it's an empty string as it would classify as an invalid fragment. |
Hello @xtreamwayz update: |
@pine3ree I think leaving it as-is is fine. The phpDoc says the value must be a string. If it's saying to pass a string and somebody passes an integer or an object, they shouldn't be surprised when it doesn't behave properly. The exception that's there now is useful I think because it communicates why the parameter is not valid even though it satisfies the type requirement. The warning will tell them to fix their code, and |
@michaelmoussa |
Your regex is missing the hyphen character. |
Thanks @glen-84 - fixed in |
This PR adds support for query parameters and fragments as discussed in zendframework/zend-expressive#325.
The invoke function has now this signature:
Behaves exactly as it does now
Can have the following keys:
route
,query
,fragment
.route
is an array containing the route parametersquery
appends query paramsfragment
appends a fragment identifier, respectivelyCan have the following keys: router, reuse_result_params.
router
must be an array containing the router optionsreuse_result_params
is a boolean to indicate if the current RouteResult parameters will be used, defaults to true.