Skip to content

WIP [Routing] add scheme and method route definition option #6049

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 4 commits into from
Jan 15, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion src/Symfony/Component/Routing/Loader/XmlFileLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,12 @@ protected function parseRoute(RouteCollection $collection, \DOMElement $node, $p
throw new \InvalidArgumentException(sprintf('The <route> element in file "%s" must have an "id" and a "pattern" attribute.', $path));
}

$schemes = array_filter(explode(' ', $node->getAttribute('schemes')));
$methods = array_filter(explode(' ', $node->getAttribute('methods')));

list($defaults, $requirements, $options) = $this->parseConfigs($node, $path);

$route = new Route($node->getAttribute('pattern'), $defaults, $requirements, $options, $node->getAttribute('hostname-pattern'));
$route = new Route($node->getAttribute('pattern'), $defaults, $requirements, $options, $node->getAttribute('hostname-pattern'), $schemes, $methods);
$collection->add($id, $route);
}

Expand All @@ -141,6 +144,8 @@ protected function parseImport(RouteCollection $collection, \DOMElement $node, $
$type = $node->getAttribute('type');
$prefix = $node->getAttribute('prefix');
$hostnamePattern = $node->hasAttribute('hostname-pattern') ? $node->getAttribute('hostname-pattern') : null;
$schemes = $node->hasAttribute('schemes') ? array_filter(explode(' ', $node->getAttribute('schemes'))) : null;
$methods = $node->hasAttribute('methods') ? array_filter(explode(' ', $node->getAttribute('methods'))) : null;

list($defaults, $requirements, $options) = $this->parseConfigs($node, $path);

Expand All @@ -152,6 +157,12 @@ protected function parseImport(RouteCollection $collection, \DOMElement $node, $
if (null !== $hostnamePattern) {
$subCollection->setHostnamePattern($hostnamePattern);
}
if (null !== $schemes) {
$subCollection->setSchemes($schemes);
}
if (null !== $methods) {
$subCollection->setMethods($methods);
}
$subCollection->addDefaults($defaults);
$subCollection->addRequirements($requirements);
$subCollection->addOptions($options);
Expand Down
16 changes: 13 additions & 3 deletions src/Symfony/Component/Routing/Loader/YamlFileLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
class YamlFileLoader extends FileLoader
{
private static $availableKeys = array(
'resource', 'type', 'prefix', 'pattern', 'hostname_pattern', 'defaults', 'requirements', 'options',
'resource', 'type', 'prefix', 'pattern', 'hostname_pattern', 'schemes', 'methods', 'defaults', 'requirements', 'options',
);

/**
Expand Down Expand Up @@ -98,9 +98,11 @@ protected function parseRoute(RouteCollection $collection, $name, array $config,
$defaults = isset($config['defaults']) ? $config['defaults'] : array();
$requirements = isset($config['requirements']) ? $config['requirements'] : array();
$options = isset($config['options']) ? $config['options'] : array();
$hostnamePattern = isset($config['hostname_pattern']) ? $config['hostname_pattern'] : null;
$hostnamePattern = isset($config['hostname_pattern']) ? $config['hostname_pattern'] : '';
$schemes = isset($config['schemes']) ? $config['schemes'] : array();
$methods = isset($config['methods']) ? $config['methods'] : array();

$route = new Route($config['pattern'], $defaults, $requirements, $options, $hostnamePattern);
$route = new Route($config['pattern'], $defaults, $requirements, $options, $hostnamePattern, $schemes, $methods);

$collection->add($name, $route);
}
Expand All @@ -121,6 +123,8 @@ protected function parseImport(RouteCollection $collection, array $config, $path
$requirements = isset($config['requirements']) ? $config['requirements'] : array();
$options = isset($config['options']) ? $config['options'] : array();
$hostnamePattern = isset($config['hostname_pattern']) ? $config['hostname_pattern'] : null;
$schemes = isset($config['schemes']) ? $config['schemes'] : null;
$methods = isset($config['methods']) ? $config['methods'] : null;

$this->setCurrentDir(dirname($path));

Expand All @@ -130,6 +134,12 @@ protected function parseImport(RouteCollection $collection, array $config, $path
if (null !== $hostnamePattern) {
$subCollection->setHostnamePattern($hostnamePattern);
}
if (null !== $schemes) {
$subCollection->setSchemes($schemes);
}
if (null !== $methods) {
$subCollection->setMethods($methods);
}
$subCollection->addDefaults($defaults);
$subCollection->addRequirements($requirements);
$subCollection->addOptions($options);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@

<xsd:element name="routes" type="routes" />

<xsd:simpleType name="word">
<xsd:restriction base="xsd:string">
<xsd:pattern value="([a-zA-Z]){3,}"/>
</xsd:restriction>
</xsd:simpleType>

<xsd:simpleType name="stringlist">
<xsd:list itemType="word"/>
</xsd:simpleType>

<xsd:complexType name="routes">
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="import" type="import" />
Expand All @@ -38,6 +48,8 @@
<xsd:attribute name="id" type="xsd:string" use="required" />
<xsd:attribute name="pattern" type="xsd:string" use="required" />
<xsd:attribute name="hostname-pattern" type="xsd:string" />
<xsd:attribute name="schemes" type="stringlist" />
<xsd:attribute name="methods" type="stringlist" />
</xsd:complexType>

<xsd:complexType name="import">
Expand All @@ -47,6 +59,8 @@
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="prefix" type="xsd:string" />
<xsd:attribute name="hostname-pattern" type="xsd:string" />
<xsd:attribute name="schemes" type="stringlist" />
<xsd:attribute name="methods" type="stringlist" />
</xsd:complexType>

<xsd:complexType name="element">
Expand Down
121 changes: 114 additions & 7 deletions src/Symfony/Component/Routing/Route.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
* A Route describes a route and its parameters.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Tobias Schultze <http://tobion.de>
*
* @api
*/
Expand All @@ -30,7 +31,17 @@ class Route implements \Serializable
*/
private $hostnamePattern = '';

/**
/**
* @var array
*/
private $schemes = array();

/**
* @var array
*/
private $methods = array();

/**
* @var array
*/
private $defaults = array();
Expand Down Expand Up @@ -59,21 +70,32 @@ class Route implements \Serializable
*
* * compiler_class: A class name able to compile this route instance (RouteCompiler by default)
*
* @param string $pattern The path pattern to match
* @param array $defaults An array of default parameter values
* @param array $requirements An array of requirements for parameters (regexes)
* @param array $options An array of options
* @param string $hostnamePattern The hostname pattern to match
* @param string $pattern The path pattern to match
* @param array $defaults An array of default parameter values
* @param array $requirements An array of requirements for parameters (regexes)
* @param array $options An array of options
* @param string $hostnamePattern The hostname pattern to match
* @param string|array $schemes A required URI scheme or an array of restricted schemes
* @param string|array $methods A required HTTP method or an array of restricted methods
*
* @api
*/
public function __construct($pattern, array $defaults = array(), array $requirements = array(), array $options = array(), $hostnamePattern = '')
public function __construct($pattern, array $defaults = array(), array $requirements = array(), array $options = array(),
$hostnamePattern = '', $schemes = array(), $methods = array())
{
$this->setPattern($pattern);
$this->setDefaults($defaults);
$this->setRequirements($requirements);
$this->setOptions($options);
$this->setHostnamePattern($hostnamePattern);
// The conditions make sure that an initial empty $schemes/$methods does not override the corresponding requirement.
// They can be removed when the BC layer is removed.
if ($schemes) {
$this->setSchemes($schemes);
}
if ($methods) {
$this->setMethods($methods);
}
}

public function serialize()
Expand All @@ -84,6 +106,8 @@ public function serialize()
'defaults' => $this->defaults,
'requirements' => $this->requirements,
'options' => $this->options,
'schemes' => $this->schemes,
'methods' => $this->methods,
));
}

Expand All @@ -95,6 +119,8 @@ public function unserialize($data)
$this->defaults = $data['defaults'];
$this->requirements = $data['requirements'];
$this->options = $data['options'];
$this->schemes = $data['schemes'];
$this->methods = $data['methods'];
}

/**
Expand Down Expand Up @@ -149,6 +175,80 @@ public function setHostnamePattern($pattern)
return $this;
}

/**
* Returns the lowercased schemes this route is restricted to.
* So an empty array means that any scheme is allowed.
*
* @return array The schemes
*/
public function getSchemes()
{
return $this->schemes;
}

/**
* Sets the schemes (e.g. 'https') this route is restricted to.
* So an empty array means that any scheme is allowed.
*
* This method implements a fluent interface.
*
* @param string|array $schemes The scheme or an array of schemes
*
* @return Route The current Route instance
*/
public function setSchemes($schemes)
{
$this->schemes = array_map('strtolower', (array) $schemes);

// this is to keep BC and will be removed in a future version
if ($this->schemes) {
$this->requirements['_scheme'] = implode('|', $this->schemes);
} else {
unset($this->requirements['_scheme']);
}

$this->compiled = null;

return $this;
}

/**
* Returns the uppercased HTTP methods this route is restricted to.
* So an empty array means that any method is allowed.
*
* @return array The schemes
*/
public function getMethods()
{
return $this->methods;
}

/**
* Sets the HTTP methods (e.g. 'POST') this route is restricted to.
* So an empty array means that any method is allowed.
*
* This method implements a fluent interface.
*
* @param string|array $methods The method or an array of methods
*
* @return Route The current Route instance
*/
public function setMethods($methods)
{
$this->methods = array_map('strtoupper', (array) $methods);

// this is to keep BC and will be removed in a future version
if ($this->methods) {
$this->requirements['_method'] = implode('|', $this->methods);
} else {
unset($this->requirements['_method']);
}

$this->compiled = null;

return $this;
}

/**
* Returns the options.
*
Expand Down Expand Up @@ -454,6 +554,13 @@ private function sanitizeRequirement($key, $regex)
throw new \InvalidArgumentException(sprintf('Routing requirement for "%s" cannot be empty.', $key));
}

// this is to keep BC and will be removed in a future version
if ('_scheme' === $key) {
$this->setSchemes(explode('|', $regex));
} elseif ('_method' === $key) {
$this->setMethods(explode('|', $regex));
}

return $regex;
}
}
24 changes: 24 additions & 0 deletions src/Symfony/Component/Routing/RouteCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,30 @@ public function addOptions(array $options)
}
}

/**
* Sets the schemes (e.g. 'https') all child routes are restricted to.
*
* @param string|array $schemes The scheme or an array of schemes
*/
public function setSchemes($schemes)
{
foreach ($this->routes as $route) {
$route->setSchemes($schemes);
}
}

/**
* Sets the HTTP methods (e.g. 'POST') all child routes are restricted to.
*
* @param string|array $methods The method or an array of methods
*/
public function setMethods($methods)
{
foreach ($this->routes as $route) {
$route->setMethods($methods);
}
}

/**
* Returns an array of resources loaded to build this collection.
*
Expand Down
52 changes: 52 additions & 0 deletions src/Symfony/Component/Routing/Tests/RouteTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ public function testConstructor()
$this->assertEquals(array('foo' => '\d+'), $route->getRequirements(), '__construct() takes requirements as its third argument');
$this->assertEquals('bar', $route->getOption('foo'), '__construct() takes options as its fourth argument');
$this->assertEquals('{locale}.example.com', $route->getHostnamePattern(), '__construct() takes a hostname pattern as its fifth argument');

$route = new Route('/', array(), array(), array(), '', array('Https'), array('POST', 'put'));
$this->assertEquals(array('https'), $route->getSchemes(), '__construct() takes schemes as its sixth argument and lowercases it');
$this->assertEquals(array('POST', 'PUT'), $route->getMethods(), '__construct() takes methods as its seventh argument and uppercases it');

$route = new Route('/', array(), array(), array(), '', 'Https', 'Post');
$this->assertEquals(array('https'), $route->getSchemes(), '__construct() takes a single scheme as its sixth argument');
$this->assertEquals(array('POST'), $route->getMethods(), '__construct() takes a single method as its seventh argument');
}

public function testPattern()
Expand Down Expand Up @@ -129,6 +137,50 @@ public function testHostnamePattern()
$this->assertEquals('{locale}.example.net', $route->getHostnamePattern(), '->setHostnamePattern() sets the hostname pattern');
}

public function testScheme()
{
$route = new Route('/');
$this->assertEquals(array(), $route->getSchemes(), 'schemes is initialized with array()');
$route->setSchemes('hTTp');
$this->assertEquals(array('http'), $route->getSchemes(), '->setSchemes() accepts a single scheme string and lowercases it');
$route->setSchemes(array('HttpS', 'hTTp'));
$this->assertEquals(array('https', 'http'), $route->getSchemes(), '->setSchemes() accepts an array of schemes and lowercases them');
}

public function testSchemeIsBC()
{
$route = new Route('/');
$route->setRequirement('_scheme', 'http|https');
$this->assertEquals('http|https', $route->getRequirement('_scheme'));
$this->assertEquals(array('http', 'https'), $route->getSchemes());
$route->setSchemes(array('hTTp'));
$this->assertEquals('http', $route->getRequirement('_scheme'));
$route->setSchemes(array());
$this->assertNull($route->getRequirement('_scheme'));
}

public function testMethod()
{
$route = new Route('/');
$this->assertEquals(array(), $route->getMethods(), 'methods is initialized with array()');
$route->setMethods('gEt');
$this->assertEquals(array('GET'), $route->getMethods(), '->setMethods() accepts a single method string and uppercases it');
$route->setMethods(array('gEt', 'PosT'));
$this->assertEquals(array('GET', 'POST'), $route->getMethods(), '->setMethods() accepts an array of methods and uppercases them');
}

public function testMethodIsBC()
{
$route = new Route('/');
$route->setRequirement('_method', 'GET|POST');
$this->assertEquals('GET|POST', $route->getRequirement('_method'));
$this->assertEquals(array('GET', 'POST'), $route->getMethods());
$route->setMethods(array('gEt'));
$this->assertEquals('GET', $route->getRequirement('_method'));
$route->setMethods(array());
$this->assertNull($route->getRequirement('_method'));
}

public function testCompile()
{
$route = new Route('/{foo}');
Expand Down