feature #35782 [Routing] Add stateless route attribute (mtarld)

This PR was merged into the 5.1-dev branch.

Discussion
----------

[Routing] Add stateless route attribute

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Ticket		| https://github.com/orgs/symfony/projects/1#card-30506005
| License       | MIT
| Doc PR        | TODO

On top of https://github.com/symfony/symfony/pull/35732

Add a stateless attribute for:
Routes in annotations
```
@Route(stateless=true)
```
Yaml
```yml
route:
  stateless: true
```
Xml
```xml
<route stateless="true" />
```
PHP configurator
```php
$route->stateless(true);
```

That stateless attribute is a shortcut for setting `_stateless` default attribute in route.

Commits
-------

2da68bae8f [Routing] Add stateless route attribute
This commit is contained in:
Fabien Potencier 2020-02-29 10:56:15 +01:00
commit 146945ad8e
20 changed files with 67 additions and 10 deletions

View File

@ -72,6 +72,11 @@ class Route
unset($data['utf8']);
}
if (isset($data['stateless'])) {
$data['defaults']['_stateless'] = filter_var($data['stateless'], FILTER_VALIDATE_BOOLEAN) ?: false;
unset($data['stateless']);
}
foreach ($data as $key => $value) {
$method = 'set'.str_replace('_', '', $key);
if (!method_exists($this, $method)) {

View File

@ -10,6 +10,7 @@ CHANGELOG
* added argument `$priority` to `RouteCollection::add()`
* deprecated the `RouteCompiler::REGEX_DELIMITER` constant
* added `ExpressionLanguageProvider` to expose extra functions to route conditions
* added support for a `stateless` keyword for configuring route stateless in PHP, YAML and XML configurations.
5.0.0
-----

View File

@ -160,4 +160,16 @@ trait RouteTrait
return $this;
}
/**
* Adds the "_stateless" entry to defaults.
*
* @return $this
*/
final public function stateless(bool $stateless = true): self
{
$this->route->addDefaults(['_stateless' => $stateless]);
return $this;
}
}

View File

@ -17,7 +17,6 @@ use Symfony\Component\Config\Util\XmlUtils;
use Symfony\Component\Routing\Loader\Configurator\Traits\LocalizedRouteTrait;
use Symfony\Component\Routing\Loader\Configurator\Traits\PrefixTrait;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\RouteCompiler;
/**
* XmlFileLoader loads XML routing files.
@ -300,6 +299,15 @@ class XmlFileLoader extends FileLoader
if ($node->hasAttribute('utf8')) {
$options['utf8'] = XmlUtils::phpize($node->getAttribute('utf8'));
}
if ($stateless = $node->getAttribute('stateless')) {
if (isset($defaults['_stateless'])) {
$name = $node->hasAttribute('id') ? sprintf('"%s"', $node->getAttribute('id')) : sprintf('the "%s" tag', $node->tagName);
throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify both the "stateless" attribute and the defaults key "_stateless" for %s.', $path, $name));
}
$defaults['_stateless'] = XmlUtils::phpize($stateless);
}
return [$defaults, $requirements, $options, $condition, $paths, $prefixes];
}

View File

@ -16,7 +16,6 @@ use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\Routing\Loader\Configurator\Traits\LocalizedRouteTrait;
use Symfony\Component\Routing\Loader\Configurator\Traits\PrefixTrait;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\RouteCompiler;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Parser as YamlParser;
use Symfony\Component\Yaml\Yaml;
@ -33,7 +32,7 @@ class YamlFileLoader extends FileLoader
use PrefixTrait;
private static $availableKeys = [
'resource', 'type', 'prefix', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options', 'condition', 'controller', 'name_prefix', 'trailing_slash_on_root', 'locale', 'format', 'utf8', 'exclude',
'resource', 'type', 'prefix', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options', 'condition', 'controller', 'name_prefix', 'trailing_slash_on_root', 'locale', 'format', 'utf8', 'exclude', 'stateless',
];
private $yamlParser;
@ -134,6 +133,9 @@ class YamlFileLoader extends FileLoader
if (isset($config['utf8'])) {
$options['utf8'] = $config['utf8'];
}
if (isset($config['stateless'])) {
$defaults['_stateless'] = $config['stateless'];
}
$route = $this->createLocalizedRoute($collection, $name, $config['path']);
$route->addDefaults($defaults);
@ -179,6 +181,9 @@ class YamlFileLoader extends FileLoader
if (isset($config['utf8'])) {
$options['utf8'] = $config['utf8'];
}
if (isset($config['stateless'])) {
$defaults['_stateless'] = $config['stateless'];
}
$this->setCurrentDir(\dirname($path));
@ -245,5 +250,8 @@ class YamlFileLoader extends FileLoader
if (isset($config['controller']) && isset($config['defaults']['_controller'])) {
throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify both the "controller" key and the defaults key "_controller" for "%s".', $path, $name));
}
if (isset($config['stateless']) && isset($config['defaults']['_stateless'])) {
throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify both the "stateless" key and the defaults key "_stateless" for "%s".', $path, $name));
}
}
}

View File

@ -55,6 +55,7 @@
<xsd:attribute name="locale" type="xsd:string" />
<xsd:attribute name="format" type="xsd:string" />
<xsd:attribute name="utf8" type="xsd:boolean" />
<xsd:attribute name="stateless" type="xsd:boolean" />
</xsd:complexType>
<xsd:complexType name="import">
@ -76,6 +77,7 @@
<xsd:attribute name="format" type="xsd:string" />
<xsd:attribute name="trailing-slash-on-root" type="xsd:boolean" />
<xsd:attribute name="utf8" type="xsd:boolean" />
<xsd:attribute name="stateless" type="xsd:boolean" />
</xsd:complexType>
<xsd:complexType name="default" mixed="true">

View File

@ -6,5 +6,6 @@ return function (RoutingConfigurator $routes) {
$routes->add('defaults', '/defaults')
->locale('en')
->format('html')
->stateless(true)
;
};

View File

@ -4,5 +4,5 @@
xsi:schemaLocation="http://symfony.com/schema/routing
https://symfony.com/schema/routing/routing-1.0.xsd">
<route id="defaults" path="/defaults" locale="en" format="html" />
<route id="defaults" path="/defaults" locale="en" format="html" stateless="true" />
</routes>

View File

@ -2,3 +2,4 @@ defaults:
path: /defaults
locale: en
format: html
stateless: true

View File

@ -7,5 +7,6 @@ return function (RoutingConfigurator $routes) {
->prefix('/defaults')
->locale('g_locale')
->format('g_format')
->stateless(true)
;
};

View File

@ -6,5 +6,6 @@
<import resource="imported-with-defaults.xml" prefix="/defaults"
locale="g_locale"
format="g_format" />
format="g_format"
stateless="true" />
</routes>

View File

@ -3,3 +3,4 @@ defaults:
prefix: /defaults
locale: g_locale
format: g_format
stateless: true

View File

@ -9,7 +9,8 @@ return function (RoutingConfigurator $routes) {
->condition('abc')
->options(['utf8' => true])
->add('buz', 'zub')
->controller('foo:act');
->controller('foo:act')
->stateless(true);
$routes->import('php_dsl_sub.php')
->prefix('/sub')

View File

@ -11,7 +11,8 @@ return new class() {
->condition('abc')
->options(['utf8' => true])
->add('buz', 'zub')
->controller('foo:act');
->controller('foo:act')
->stateless(true);
$routes->import('php_dsl_sub.php')
->prefix('/sub')

View File

@ -6,7 +6,7 @@ use Symfony\Component\Routing\RouteCollection;
$collection = new RouteCollection();
$collection->add('blog_show', new Route(
'/blog/{slug}',
['_controller' => 'MyBlogBundle:Blog:show'],
['_controller' => 'MyBlogBundle:Blog:show', '_stateless' => true],
['locale' => '\w+'],
['compiler_class' => 'RouteCompiler'],
'{locale}.example.com',

View File

@ -6,6 +6,9 @@
<route id="blog_show" path="/blog/{slug}" host="{locale}.example.com" methods="GET|POST put,OpTiOnS" schemes="hTTps">
<default key="_controller">MyBundle:Blog:show</default>
<default key="_stateless">
<bool>true</bool>
</default>
<requirement key="locale">\w+</requirement>
<option key="compiler_class">RouteCompiler</option>
<condition>context.getMethod() == "GET"</condition>

View File

@ -1,6 +1,6 @@
blog_show:
path: /blog/{slug}
defaults: { _controller: "MyBundle:Blog:show" }
defaults: { _controller: "MyBundle:Blog:show", _stateless: true }
host: "{locale}.example.com"
requirements: { 'locale': '\w+' }
methods: ['GET','POST','put','OpTiOnS']

View File

@ -43,6 +43,7 @@ class PhpFileLoaderTest extends TestCase
foreach ($routes as $route) {
$this->assertSame('/blog/{slug}', $route->getPath());
$this->assertSame('MyBlogBundle:Blog:show', $route->getDefault('_controller'));
$this->assertTrue($route->getDefault('_stateless'));
$this->assertSame('{locale}.example.com', $route->getHost());
$this->assertSame('RouteCompiler', $route->getOption('compiler_class'));
$this->assertEquals(['GET', 'POST', 'PUT', 'OPTIONS'], $route->getMethods());
@ -109,9 +110,11 @@ class PhpFileLoaderTest extends TestCase
$expectedRoutes->add('one', $localeRoute = new Route('/defaults/one'));
$localeRoute->setDefault('_locale', 'g_locale');
$localeRoute->setDefault('_format', 'g_format');
$localeRoute->setDefault('_stateless', true);
$expectedRoutes->add('two', $formatRoute = new Route('/defaults/two'));
$formatRoute->setDefault('_locale', 'g_locale');
$formatRoute->setDefault('_format', 'g_format');
$formatRoute->setDefault('_stateless', true);
$formatRoute->setDefault('specific', 'imported');
$expectedRoutes->addResource(new FileResource(__DIR__.'/../Fixtures/imported-with-defaults.php'));
@ -172,7 +175,7 @@ class PhpFileLoaderTest extends TestCase
->setCondition('abc')
);
$expectedCollection->add('buz', (new Route('/zub'))
->setDefaults(['_controller' => 'foo:act'])
->setDefaults(['_controller' => 'foo:act', '_stateless' => true])
);
$expectedCollection->add('c_root', (new Route('/sub/pub/'))
->setRequirements(['id' => '\d+'])

View File

@ -47,6 +47,7 @@ class XmlFileLoaderTest extends TestCase
$this->assertEquals(['GET', 'POST', 'PUT', 'OPTIONS'], $route->getMethods());
$this->assertEquals(['https'], $route->getSchemes());
$this->assertEquals('context.getMethod() == "GET"', $route->getCondition());
$this->assertTrue($route->getDefault('_stateless'));
}
public function testLoadWithNamespacePrefix()
@ -98,6 +99,7 @@ class XmlFileLoaderTest extends TestCase
$this->assertSame('/defaults', $defaultsRoute->getPath());
$this->assertSame('en', $defaultsRoute->getDefault('_locale'));
$this->assertSame('html', $defaultsRoute->getDefault('_format'));
$this->assertTrue($defaultsRoute->getDefault('_stateless'));
}
public function testLoadingImportedRoutesWithDefaults()
@ -111,9 +113,11 @@ class XmlFileLoaderTest extends TestCase
$expectedRoutes->add('one', $localeRoute = new Route('/defaults/one'));
$localeRoute->setDefault('_locale', 'g_locale');
$localeRoute->setDefault('_format', 'g_format');
$localeRoute->setDefault('_stateless', true);
$expectedRoutes->add('two', $formatRoute = new Route('/defaults/two'));
$formatRoute->setDefault('_locale', 'g_locale');
$formatRoute->setDefault('_format', 'g_format');
$formatRoute->setDefault('_stateless', true);
$formatRoute->setDefault('specific', 'imported');
$expectedRoutes->addResource(new FileResource(__DIR__.'/../Fixtures/imported-with-defaults.xml'));

View File

@ -90,6 +90,7 @@ class YamlFileLoaderTest extends TestCase
$this->assertEquals(['GET', 'POST', 'PUT', 'OPTIONS'], $route->getMethods());
$this->assertEquals(['https'], $route->getSchemes());
$this->assertEquals('context.getMethod() == "GET"', $route->getCondition());
$this->assertTrue($route->getDefault('_stateless'));
}
public function testLoadWithResource()
@ -232,6 +233,7 @@ class YamlFileLoaderTest extends TestCase
$this->assertSame('/defaults', $defaultsRoute->getPath());
$this->assertSame('en', $defaultsRoute->getDefault('_locale'));
$this->assertSame('html', $defaultsRoute->getDefault('_format'));
$this->assertTrue($defaultsRoute->getDefault('_stateless'));
}
public function testLoadingImportedRoutesWithDefaults()
@ -245,9 +247,11 @@ class YamlFileLoaderTest extends TestCase
$expectedRoutes->add('one', $localeRoute = new Route('/defaults/one'));
$localeRoute->setDefault('_locale', 'g_locale');
$localeRoute->setDefault('_format', 'g_format');
$localeRoute->setDefault('_stateless', true);
$expectedRoutes->add('two', $formatRoute = new Route('/defaults/two'));
$formatRoute->setDefault('_locale', 'g_locale');
$formatRoute->setDefault('_format', 'g_format');
$formatRoute->setDefault('_stateless', true);
$formatRoute->setDefault('specific', 'imported');
$expectedRoutes->addResource(new FileResource(__DIR__.'/../Fixtures/imported-with-defaults.yml'));