slimphp / Slim

Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs.
http://slimframework.com
MIT License
11.94k stars 1.95k forks source link

Optional parameter in url #1289

Closed silentworks closed 9 years ago

silentworks commented 9 years ago

Slim doesn't support optional parameters as the underlying routing engine doesn't support them.

We need:

This was originally reported as:

It would seem it currently isn't possible to add an optional paramater in the url if you are using urlFor to get the url via a named route.

$app->get('/test/{param1}[/{optional:[A-Za-z]}]')->setName('test');

$app->getContainer()['router']->urlFor('test', ['param1' => 'World']);

This will throw an error because urlFor method does not know how to handle optional regex. I saw another project using FastRoute and they handle it using this regex https://github.com/mrjgreen/phroute/blob/master/src/Phroute/RouteParser.php#L23-L29

akrabat commented 9 years ago

I don't think that FastRoute supports optional parameters.

This doesn't work for me:

$app->get('/hello/{name}[/{optional}]', function ($req, $res, $args) {
    $name = $req->getAttribute('name', '== not set ==');
    $optional = $req->getAttribute('optional', '== not set ==');

    return $res->write("<p>Hello $name. Optional: $optional</p>");
});

Am I missing something?

akrabat commented 9 years ago

The only way I've found to do optional parameters is like this:

$app->get('/hello2/{name}{a:/?}{optional:.*}', function ($req, $res, $args) {
    $name = $req->getAttribute('name', '== not set ==');
    $optional = $req->getAttribute('optional', '== not set ==');

    return $res->write("<p>Hello $name. Optional: $optional</p>");
});
silentworks commented 9 years ago

Correct FastRoute does not support optional paramaters, although the syntax I provided is what @nikic provided in the comment section on his blog http://nikic.github.io/2014/02/18/Fast-request-routing-using-regular-expressions.html#comment-1256023948.

You example will not work with urlFor method on the router, which is where the problem is at for me.

akrabat commented 9 years ago

So you get $app->get('/test/{param1}[/{optional:[A-Za-z]}]')->setName('test'); to route to a callable?

geggleto commented 9 years ago

the optional URL problem is only for calling via urlFor correct? //cc @silentworks

silentworks commented 9 years ago

@geggleto correct, and its because the regex in urlFor is broken, it doesn't take optionals into consideration.

akrabat commented 9 years ago

I am genuinely confused. Why have urlFor work with path definitions that do not work?

geggleto commented 9 years ago

So to clarify @silentworks

//Given this route
$app->get('/test/{param1}[/{optional:[A-Za-z]}]');

//the request 
GET /test/1/abcdefg
//would work in Slim currently?
silentworks commented 9 years ago

@akrabat https://github.com/slimphp/Slim/blob/develop/Slim/Router.php#L184 has no clue how to deal with optionals, it will think that {optional:.*} is required although its not in the example you provided.

$app->get('/hello2/{name}{a:/?}{optional:.*}', function ($req, $res, $args) {
    $name = $req->getAttribute('name', '== not set ==');
    $optional = $req->getAttribute('optional', '== not set ==');

    return $res->write("<p>Hello $name. Optional: $optional</p>");
})->setName('hello');

The below example breaks

$app->getContainer()['router']->urlFor('hello', ['name' => 'World']);
silentworks commented 9 years ago

@geggleto correct but

//Given this route
$app->get('/test/{param1}[/{optional:[A-Za-z]}]');

// the request
GET /test/1
// would break in Slim currently.
akrabat commented 9 years ago

Yeah - fundamentally, Slim doesn't support optional parameters as it can't dispatch them.

geggleto commented 9 years ago

In v2 the router did support them...

silentworks commented 9 years ago

@akrabat Slim 2 does and FastRoute can support it if you add it in.

geggleto commented 9 years ago

I very much rely on the optional parameters functionality. It needs to be in v3.

akrabat commented 9 years ago

I clearly don't understand, so am happy to review a PR :)

codeguy commented 9 years ago

:+1: for PR that adds optional params functionality to FastRoute

akrabat commented 9 years ago

I agree that a clean way to do optional parameters is required. I think it's deeper than a urlFor() issue.

geggleto commented 9 years ago

I guess we are at a point where we need to decide if we want to extend FastRoute to include the capabilities we need or use another Router that already has those capabilities.

Given the whole, lets not reinvent the wheel. I prefer the latter over the former since Aura.Router has all the features we need as well as strategies for deployment to Production.

codeguy commented 9 years ago

FastRoute as is lets us create custom named tokens that are expanded into an appropriate regex. See League Route for example implementation:

https://github.com/thephpleague/route/blob/master/src/RouteCollection.php#L202

silentworks commented 9 years ago

@codeguy I don't think this solves the optional parameter issue, this just allows custom named tokens.

codeguy commented 9 years ago

hmm. okay. If necessary this can wait until 3.1.

lalop commented 9 years ago

I don't know how fastroute works, but maybe we can add multiple path to a route. Something like

$app->get(['/hello', '/hello/{name}'])->setName('hello-world');

Than for the urlFor we return the first one matching the maximum of the given parameters.

geggleto commented 9 years ago

This was a few hours ago, but I do believe FastRoute does not support optional parameters at all. urlFor is a pure Slim item, which spawned this whole fiasco with FastRoute.

akrabat commented 9 years ago

I've started looking at replacing FastRoute with Aura.Router.

Initial work here: https://github.com/slimphp/Slim/compare/develop...akrabat:aura-router?expand=1

Testbed that I'm using: https://gist.github.com/akrabat/66d04bb7a8be8082a53f

Things that I know work:

BC breaks with Slim 3 so far:

Notes:

Melbournite commented 9 years ago

Generally speaking, are there any downsides to using aura over FastRoute? Assuming there's minimal work required to switch them out... I know FastRoute was originally chosen due to its speed. At the same time, optional paramters is an absolute must IMO.

I can't really find any reliable real world bench marking between the two besides here: https://github.com/tyler-sommer/php-router-benchmark

geggleto commented 9 years ago

Currently in v3 the api for attaching middleware is fragmented. Route groups are handled internally in Slim and not actually in the router and that seems a bit weird when we are using 3rd party components. Using a more feature rich router would likely pull some of the legacy code out of Slim and unify the API :+1:

akrabat commented 9 years ago

I can't see any way around the middleware attachment issues for route groups. Even though Aura.Router has the concept of route groups, they are implemented the same way Slim does it by prefixing the path and then adding the individual routes. As a result, you don't have the concept of a group object after adding the route group on which you can then set some middleware.

This is why I've made Slim's add route group function signature as:

public function group($pattern, $callback, $name = null, $middleware = [])

I couldn't work out a way to do ->add() for this case. If we wanted consistency between adding route middleware and route group middleware, then we'd have to change the function signatures for adding routes. In Slim 2 we have variable arguments and the ones we don't know are assumed to be middleware. If we were to go back to defining middleware as a method parameter, then I would prefer an array as I've done for group, so you'd end up with:

$app->get($pattern, $callable, $name=null, $middleware=[]);
$app->map(array $methods, $pattern, $callable, $name=null, $middleware=[])

Obviously, we can't change the way we add middleware to the app itself, so $app->add() would still exist, regardless.

One final option. Don't allow middleware on route groups :)

Melbournite commented 9 years ago

Middleware on groups is more just a convenience thing I guess. I am still interested to see any performance degradation with Aura being used. I'll try do some tests tomorrow.

Is the general consensus that Aura should be switched in agreed?

geggleto commented 9 years ago

:-1: @akrabat There shalt be no mention of this removal of a feature cries

Yeah it is a huge convenience thing. Given that it makes it very easy to apply say an authentication layer to an entire slim application. In v3 I do not believe there is pass-thru anymore so that option is also gone as a mitigation. ;(

jaapverloop commented 9 years ago

+1 for changing all the function signatures for adding routes. As a result, it's not necessary to keep the route objects container aware, right?

Maybe we could introduce something like https://gist.github.com/jaapverloop/142b2857ee005e91068f for convenience.

geggleto commented 9 years ago

@akrabat looking at

public function group($pattern, $callback, $name = null, $middleware = []);

I can :+1: that signature, its not as clean as doing ->add() but at least I don't have to apply middleware to all the subsequent routes.

nikic commented 9 years ago

fyi FastRoute now supports trailing optional segments using the syntax /user/{id:\d+}[/{name}].

akrabat commented 9 years ago

That's great timing! Thanks @nikic :)

geggleto commented 9 years ago

Yeah!!! :100:

codeguy commented 9 years ago

Awesome news. Because I really like FastRoute.

silentworks commented 9 years ago

Thanks @nikic.

Now we just need to fix urlFor to work with the new syntax for optionals.

silentworks commented 9 years ago

Fixed.