Skip to content

[Routing] Allow inline definition of requirements and defaults #26518

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

Merged
merged 1 commit into from
Mar 19, 2018

Conversation

nicolas-grekas
Copy link
Member

@nicolas-grekas nicolas-grekas commented Mar 14, 2018

Q A
Branch? master
Bug fix? no
New feature? yes
BC breaks? no
Deprecations? no
Tests pass? yes
Fixed tickets #26481
License MIT
Doc PR -
{bar} -- no requirement, no default value
{bar<.*>} -- with requirement, no default value
{bar?default_value} -- no requirement, with default value
{bar<.*>?default_value} -- with requirement and default value
{bar?} -- no requirement, with default value of null
{bar<.*>?} -- with requirement and default value of null

Details:

  • Requirements and default values are not escaped in any way. This is valid -> @Route("/foo/{bar<>>?<>}") (requirements = > and default value = <>)
  • Because of the lack of escaping, you can't use a closing brace (}) inside the default value (wrong -> @Route("/foo/{bar<\d+>?aa}bb}")) but you can use it inside requirements (correct -> @Route("/foo/{bar<\d{3}>}")).
  • PHP constants are not supported (use the traditional defaults syntax for that)
  • ...

$this->assertEquals((new Route('/foo/{bar}'))->setRequirement('bar', '\d+'), new Route('/foo/{bar<.*>}', array(), array('bar' => '\d+')));

$this->assertEquals((new Route('/foo/{bar}'))->setDefault('bar', null)->setRequirement('bar', '.*'), new Route('/foo/{bar<.*>?}'));
$this->assertEquals((new Route('/foo/{bar}'))->setDefault('bar', '<>')->setRequirement('bar', '>'), new Route('/foo/{bar<>>?<>}'));
Copy link
Member

Choose a reason for hiding this comment

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

This one is surprising. Instead of /foo/{bar<>>?<>} I thought this should be like /foo/{bar<\>>? <>} to escape the > value as a requirement.

Copy link
Member Author

Choose a reason for hiding this comment

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

there is no escaping to keep the parser trivial
I don't think this limits anything in anyway

@derrabus
Copy link
Member

Regarding the escaping: Could my default value contain curly braces?

@nicolas-grekas
Copy link
Member Author

nicolas-grekas commented Mar 14, 2018

Could my default value contain curly braces?

Not a closing one that's correct.

@javiereguiluz javiereguiluz added the ❄️ Feature Freeze Important Pull Requests to finish before the next Symfony "feature freeze" label Mar 14, 2018
@derrabus
Copy link
Member

All right. We can probably live with that limitation, but we should document it.

@Tobion
Copy link
Contributor

Tobion commented Mar 14, 2018

I would also leave escaping out. If you have such a special regex or default, you can simply use the old long route definition.

@ostrolucky
Copy link
Contributor

will !php/const work inside?

@nicolas-grekas
Copy link
Member Author

will !php/const work inside?

no, but you could submit a PR on top to make constants work, eg

{bar!SOME_CONST} -- no requirement, with default value
{bar<.*>!SOME_CONST} -- with requirement and default value

@nicolas-grekas
Copy link
Member Author

nicolas-grekas commented Mar 14, 2018

you could submit a PR on top to make constants work, eg

or just use the existing syntax btw... (explicit defaults: ...)

@javiereguiluz
Copy link
Member

Is it really so important to support PHP constants here? They could make routes really difficult to understand or very easy to override some typo or wrong character.

@Destroy666x
Copy link

Destroy666x commented Mar 14, 2018

Nice feature. What about a case like {bar<test>?>?test}?

@nicolas-grekas
Copy link
Member Author

nicolas-grekas commented Mar 14, 2018

@Destroy666x see tests, that's covered: the shortest regexp is used
(use e.g. {bar<test[>]?>?test} if you want the other way)

@ostrolucky
Copy link
Contributor

It's not so important, was just curious. Seems this syntax can be freely combined with _defaults anyway 👍

@Destroy666x
Copy link

Destroy666x commented Mar 14, 2018

@nicolas-grekas I see a similar test but without ? inside <>, which is allowed in requirements, right? It's kind of ambigous which ? is the separator

EDIT: I see, could you please add a test for bar<test[>]?>?test too then? Or maybe it's good to mention that in documentation

Copy link
Contributor

@ostrolucky ostrolucky left a comment

Choose a reason for hiding this comment

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

I don't think this is gonna work. Route::__construct calls setPath first, then setDefaults which erases those defaults as a first thing

@nicolas-grekas
Copy link
Member Author

nicolas-grekas commented Mar 14, 2018

I don't think this is gonna work. Route::__construct calls setPath first, then setDefaults which erases those defaults first

please review again, addDefaults is used ;)

@ostrolucky
Copy link
Contributor

Indeed. Can you add a test case for passing defaults both via path and via defaults tho?

@nicolas-grekas
Copy link
Member Author

Indeed. Can you add a test case for passing defaults both via path and via defaults tho?

review again :)

{
$this->assertEquals((new Route('/foo/{bar}'))->setDefault('bar', null), new Route('/foo/{bar?}'));
$this->assertEquals((new Route('/foo/{bar}'))->setDefault('bar', 'baz'), new Route('/foo/{bar?baz}'));
$this->assertEquals((new Route('/foo/{bar}'))->setDefault('bar', 'baz<buz>'), new Route('/foo/{bar?baz<buz>}'));
Copy link
Contributor

@Tobion Tobion Mar 14, 2018

Choose a reason for hiding this comment

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

How about we make it so that it does not matter if you specify a default or regex first?
So '/foo/{bar?baz<.*>}' == '/foo/{bar<.*>?baz}'

This seems more user-friendly as you don't need to remember the order. Also this syntax is not feature-complete anyway due to missing escaping. So if you really need to specify a default like baz<buz> then just use the explicit config. But having such a default is much less likely than a user mixing up the order.

Copy link
Member Author

Choose a reason for hiding this comment

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

Copy link
Member Author

@nicolas-grekas nicolas-grekas Mar 14, 2018

Choose a reason for hiding this comment

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

I think having two ways to express things just creates more confusion.
Having only one order means everyone will write these in exactly the same way, thus less WTF when switching projects.

Copy link
Contributor

Choose a reason for hiding this comment

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

Normally I agree consistency is better. But I also want to prevent bugs in the first place. So when we do not accept both ways, it would be cleaner to throw an exception if you do it wrong. I cannot believe somewhat actually uses a default like baz<buz> in a URL.

Copy link
Member Author

@nicolas-grekas nicolas-grekas Mar 16, 2018

Choose a reason for hiding this comment

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

I cannot believe somewhat actually uses a default like baz in a URL

This is not a reason to technically forbid it. Symfony devs have been proved surprising many time :)

Copy link
Contributor

@Tobion Tobion Mar 16, 2018

Choose a reason for hiding this comment

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

It's not forbidden. You just need to use the explicit syntax.

Symfony devs have been proved surprising many time

Yes they will be surprised with unexpected behavior.

@@ -123,6 +123,19 @@ public function getPath()
*/
public function setPath($pattern)
{
if (false !== strpbrk($pattern, '?<')) {
$pattern = preg_replace_callback('#\{(\w++)(<.*?>)?(\?[^\}]*+)?\}#', function ($m) {
Copy link
Contributor

@ro0NL ro0NL Mar 15, 2018

Choose a reason for hiding this comment

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

what does {bar<>} exactly do?

ah that's sanitized. never mind.. perhaps a test?

Copy link
Member Author

Choose a reason for hiding this comment

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

this is already tested elsewhere, nothing specific to this feature

@nicolas-grekas
Copy link
Member Author

@symfony/deciders votes pending


$this->assertEquals((new Route('/foo/{bar}'))->setDefault('bar', null)->setRequirement('bar', '.*'), new Route('/foo/{bar<.*>?}'));
$this->assertEquals((new Route('/foo/{bar}'))->setDefault('bar', '<>')->setRequirement('bar', '>'), new Route('/foo/{bar<>>?<>}'));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe there should also be a test case with using curly braces in the requirements?

Like '/foo/{_locale<[a-z]{2}>}' for example?

Copy link
Member Author

Choose a reason for hiding this comment

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

case added

@nicolas-grekas nicolas-grekas merged commit 67559e1 into symfony:master Mar 19, 2018
nicolas-grekas added a commit that referenced this pull request Mar 19, 2018
…defaults (nicolas-grekas)

This PR was merged into the 4.1-dev branch.

Discussion
----------

[Routing] Allow inline definition of requirements and defaults

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #26481
| License       | MIT
| Doc PR        | -

```
{bar} -- no requirement, no default value
{bar<.*>} -- with requirement, no default value
{bar?default_value} -- no requirement, with default value
{bar<.*>?default_value} -- with requirement and default value
{bar?} -- no requirement, with default value of null
{bar<.*>?} -- with requirement and default value of null
```

Details:

* Requirements and default values are not escaped in any way. This is valid -> `@Route("/foo/{bar<>>?<>}")` (requirements = `>`  and default value = `<>`)
* Because of the lack of escaping, you can't use a closing brace (`}`) inside the default value (wrong -> `@Route("/foo/{bar<\d+>?aa}bb}")`) but you can use it inside requirements (correct -> `@Route("/foo/{bar<\d{3}>}")`).
* PHP constants are not supported (use the traditional `defaults` syntax for that)
* ...

Commits
-------

67559e1 [Routing] Allow inline definition of requirements and defaults
@nicolas-grekas nicolas-grekas deleted the route-inline-reqs branch March 19, 2018 12:13
@fabpot fabpot mentioned this pull request May 7, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature Routing ❄️ Feature Freeze Important Pull Requests to finish before the next Symfony "feature freeze" Status: Reviewed
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants