[Routing] added support for expression conditions in routes
This commit is contained in:
parent
86ac8d7547
commit
d477f157ce
@ -28,6 +28,7 @@ class Route
|
|||||||
private $host;
|
private $host;
|
||||||
private $methods;
|
private $methods;
|
||||||
private $schemes;
|
private $schemes;
|
||||||
|
private $condition;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
@ -153,4 +154,14 @@ class Route
|
|||||||
{
|
{
|
||||||
return $this->methods;
|
return $this->methods;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setCondition($condition)
|
||||||
|
{
|
||||||
|
$this->condition = $condition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCondition()
|
||||||
|
{
|
||||||
|
return $this->condition;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -116,6 +116,7 @@ abstract class AnnotationClassLoader implements LoaderInterface
|
|||||||
'schemes' => array(),
|
'schemes' => array(),
|
||||||
'methods' => array(),
|
'methods' => array(),
|
||||||
'host' => '',
|
'host' => '',
|
||||||
|
'condition' => '',
|
||||||
);
|
);
|
||||||
|
|
||||||
$class = new \ReflectionClass($class);
|
$class = new \ReflectionClass($class);
|
||||||
@ -154,6 +155,10 @@ abstract class AnnotationClassLoader implements LoaderInterface
|
|||||||
if (null !== $annot->getHost()) {
|
if (null !== $annot->getHost()) {
|
||||||
$globals['host'] = $annot->getHost();
|
$globals['host'] = $annot->getHost();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (null !== $annot->getCondition()) {
|
||||||
|
$globals['condition'] = $annot->getCondition();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$collection = new RouteCollection();
|
$collection = new RouteCollection();
|
||||||
@ -194,7 +199,12 @@ abstract class AnnotationClassLoader implements LoaderInterface
|
|||||||
$host = $globals['host'];
|
$host = $globals['host'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$route = new Route($globals['path'].$annot->getPath(), $defaults, $requirements, $options, $host, $schemes, $methods);
|
$condition = $annot->getCondition();
|
||||||
|
if (null === $condition) {
|
||||||
|
$condition = $globals['condition'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$route = new Route($globals['path'].$annot->getPath(), $defaults, $requirements, $options, $host, $schemes, $methods, $condition);
|
||||||
|
|
||||||
$this->configureRoute($route, $class, $method, $annot);
|
$this->configureRoute($route, $class, $method, $annot);
|
||||||
|
|
||||||
|
@ -129,9 +129,9 @@ class XmlFileLoader extends FileLoader
|
|||||||
$schemes = preg_split('/[\s,\|]++/', $node->getAttribute('schemes'), -1, PREG_SPLIT_NO_EMPTY);
|
$schemes = preg_split('/[\s,\|]++/', $node->getAttribute('schemes'), -1, PREG_SPLIT_NO_EMPTY);
|
||||||
$methods = preg_split('/[\s,\|]++/', $node->getAttribute('methods'), -1, PREG_SPLIT_NO_EMPTY);
|
$methods = preg_split('/[\s,\|]++/', $node->getAttribute('methods'), -1, PREG_SPLIT_NO_EMPTY);
|
||||||
|
|
||||||
list($defaults, $requirements, $options) = $this->parseConfigs($node, $path);
|
list($defaults, $requirements, $options, $condition) = $this->parseConfigs($node, $path);
|
||||||
|
|
||||||
$route = new Route($node->getAttribute('path'), $defaults, $requirements, $options, $node->getAttribute('host'), $schemes, $methods);
|
$route = new Route($node->getAttribute('path'), $defaults, $requirements, $options, $node->getAttribute('host'), $schemes, $methods, $condition);
|
||||||
$collection->add($id, $route);
|
$collection->add($id, $route);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,7 +157,7 @@ class XmlFileLoader extends FileLoader
|
|||||||
$schemes = $node->hasAttribute('schemes') ? preg_split('/[\s,\|]++/', $node->getAttribute('schemes'), -1, PREG_SPLIT_NO_EMPTY) : null;
|
$schemes = $node->hasAttribute('schemes') ? preg_split('/[\s,\|]++/', $node->getAttribute('schemes'), -1, PREG_SPLIT_NO_EMPTY) : null;
|
||||||
$methods = $node->hasAttribute('methods') ? preg_split('/[\s,\|]++/', $node->getAttribute('methods'), -1, PREG_SPLIT_NO_EMPTY) : null;
|
$methods = $node->hasAttribute('methods') ? preg_split('/[\s,\|]++/', $node->getAttribute('methods'), -1, PREG_SPLIT_NO_EMPTY) : null;
|
||||||
|
|
||||||
list($defaults, $requirements, $options) = $this->parseConfigs($node, $path);
|
list($defaults, $requirements, $options, $condition) = $this->parseConfigs($node, $path);
|
||||||
|
|
||||||
$this->setCurrentDir(dirname($path));
|
$this->setCurrentDir(dirname($path));
|
||||||
|
|
||||||
@ -211,6 +211,7 @@ class XmlFileLoader extends FileLoader
|
|||||||
$defaults = array();
|
$defaults = array();
|
||||||
$requirements = array();
|
$requirements = array();
|
||||||
$options = array();
|
$options = array();
|
||||||
|
$condition = null;
|
||||||
|
|
||||||
foreach ($node->getElementsByTagNameNS(self::NAMESPACE_URI, '*') as $n) {
|
foreach ($node->getElementsByTagNameNS(self::NAMESPACE_URI, '*') as $n) {
|
||||||
switch ($n->localName) {
|
switch ($n->localName) {
|
||||||
@ -228,11 +229,14 @@ class XmlFileLoader extends FileLoader
|
|||||||
case 'option':
|
case 'option':
|
||||||
$options[$n->getAttribute('key')] = trim($n->textContent);
|
$options[$n->getAttribute('key')] = trim($n->textContent);
|
||||||
break;
|
break;
|
||||||
|
case 'condition':
|
||||||
|
$condition = trim($n->textContent);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new \InvalidArgumentException(sprintf('Unknown tag "%s" used in file "%s". Expected "default", "requirement" or "option".', $n->localName, $path));
|
throw new \InvalidArgumentException(sprintf('Unknown tag "%s" used in file "%s". Expected "default", "requirement" or "option".', $n->localName, $path));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return array($defaults, $requirements, $options);
|
return array($defaults, $requirements, $options, $condition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ use Symfony\Component\Config\Loader\FileLoader;
|
|||||||
class YamlFileLoader extends FileLoader
|
class YamlFileLoader extends FileLoader
|
||||||
{
|
{
|
||||||
private static $availableKeys = array(
|
private static $availableKeys = array(
|
||||||
'resource', 'type', 'prefix', 'pattern', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options',
|
'resource', 'type', 'prefix', 'pattern', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options', 'condition'
|
||||||
);
|
);
|
||||||
private $yamlParser;
|
private $yamlParser;
|
||||||
|
|
||||||
@ -123,8 +123,9 @@ class YamlFileLoader extends FileLoader
|
|||||||
$host = isset($config['host']) ? $config['host'] : '';
|
$host = isset($config['host']) ? $config['host'] : '';
|
||||||
$schemes = isset($config['schemes']) ? $config['schemes'] : array();
|
$schemes = isset($config['schemes']) ? $config['schemes'] : array();
|
||||||
$methods = isset($config['methods']) ? $config['methods'] : array();
|
$methods = isset($config['methods']) ? $config['methods'] : array();
|
||||||
|
$condition = isset($config['condition']) ? $config['condition'] : null;
|
||||||
|
|
||||||
$route = new Route($config['path'], $defaults, $requirements, $options, $host, $schemes, $methods);
|
$route = new Route($config['path'], $defaults, $requirements, $options, $host, $schemes, $methods, $condition);
|
||||||
|
|
||||||
$collection->add($name, $route);
|
$collection->add($name, $route);
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
<xsd:element name="default" nillable="true" type="element" />
|
<xsd:element name="default" nillable="true" type="element" />
|
||||||
<xsd:element name="requirement" type="element" />
|
<xsd:element name="requirement" type="element" />
|
||||||
<xsd:element name="option" type="element" />
|
<xsd:element name="option" type="element" />
|
||||||
|
<xsd:element name="condition" type="condition" />
|
||||||
</xsd:choice>
|
</xsd:choice>
|
||||||
</xsd:group>
|
</xsd:group>
|
||||||
|
|
||||||
@ -61,4 +62,9 @@
|
|||||||
</xsd:extension>
|
</xsd:extension>
|
||||||
</xsd:simpleContent>
|
</xsd:simpleContent>
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
|
|
||||||
|
<xsd:simpleType name="condition">
|
||||||
|
<xsd:restriction base="xsd:string">
|
||||||
|
</xsd:restriction>
|
||||||
|
</xsd:simpleType>
|
||||||
</xsd:schema>
|
</xsd:schema>
|
||||||
|
@ -50,6 +50,9 @@ class ApacheMatcherDumper extends MatcherDumper
|
|||||||
$prevHostRegex = '';
|
$prevHostRegex = '';
|
||||||
|
|
||||||
foreach ($this->getRoutes()->all() as $name => $route) {
|
foreach ($this->getRoutes()->all() as $name => $route) {
|
||||||
|
if ($route->getCondition()) {
|
||||||
|
throw new \LogicException(sprintf('Unable to dump the routes for Apache as route "%s" has a condition.', $name));
|
||||||
|
}
|
||||||
|
|
||||||
$compiledRoute = $route->compile();
|
$compiledRoute = $route->compile();
|
||||||
$hostRegex = $compiledRoute->getHostRegex();
|
$hostRegex = $compiledRoute->getHostRegex();
|
||||||
|
@ -13,6 +13,7 @@ namespace Symfony\Component\Routing\Matcher\Dumper;
|
|||||||
|
|
||||||
use Symfony\Component\Routing\Route;
|
use Symfony\Component\Routing\Route;
|
||||||
use Symfony\Component\Routing\RouteCollection;
|
use Symfony\Component\Routing\RouteCollection;
|
||||||
|
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PhpMatcherDumper creates a PHP class able to match URLs for a given set of routes.
|
* PhpMatcherDumper creates a PHP class able to match URLs for a given set of routes.
|
||||||
@ -23,6 +24,8 @@ use Symfony\Component\Routing\RouteCollection;
|
|||||||
*/
|
*/
|
||||||
class PhpMatcherDumper extends MatcherDumper
|
class PhpMatcherDumper extends MatcherDumper
|
||||||
{
|
{
|
||||||
|
private $expressionLanguage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dumps a set of routes to a PHP class.
|
* Dumps a set of routes to a PHP class.
|
||||||
*
|
*
|
||||||
@ -91,6 +94,8 @@ EOF;
|
|||||||
{
|
{
|
||||||
\$allow = array();
|
\$allow = array();
|
||||||
\$pathinfo = rawurldecode(\$pathinfo);
|
\$pathinfo = rawurldecode(\$pathinfo);
|
||||||
|
\$context = \$this->context;
|
||||||
|
\$request = \$this->request;
|
||||||
|
|
||||||
$code
|
$code
|
||||||
|
|
||||||
@ -237,6 +242,10 @@ EOF;
|
|||||||
$hostMatches = true;
|
$hostMatches = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($route->getCondition()) {
|
||||||
|
$conditions[] = $this->getExpressionLanguage()->compile($route->getCondition(), array('context', 'request'));
|
||||||
|
}
|
||||||
|
|
||||||
$conditions = implode(' && ', $conditions);
|
$conditions = implode(' && ', $conditions);
|
||||||
|
|
||||||
$code .= <<<EOF
|
$code .= <<<EOF
|
||||||
@ -245,9 +254,8 @@ EOF;
|
|||||||
|
|
||||||
EOF;
|
EOF;
|
||||||
|
|
||||||
|
$gotoname = 'not_'.preg_replace('/[^A-Za-z0-9_]/', '', $name);
|
||||||
if ($methods) {
|
if ($methods) {
|
||||||
$gotoname = 'not_'.preg_replace('/[^A-Za-z0-9_]/', '', $name);
|
|
||||||
|
|
||||||
if (1 === count($methods)) {
|
if (1 === count($methods)) {
|
||||||
$code .= <<<EOF
|
$code .= <<<EOF
|
||||||
if (\$this->context->getMethod() != '$methods[0]') {
|
if (\$this->context->getMethod() != '$methods[0]') {
|
||||||
@ -375,4 +383,16 @@ EOF;
|
|||||||
|
|
||||||
return $tree;
|
return $tree;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function getExpressionLanguage()
|
||||||
|
{
|
||||||
|
if (null === $this->expressionLanguage) {
|
||||||
|
if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
|
||||||
|
throw new RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
|
||||||
|
}
|
||||||
|
$this->expressionLanguage = new ExpressionLanguage();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->expressionLanguage;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,11 @@ abstract class RedirectableUrlMatcher extends UrlMatcher implements Redirectable
|
|||||||
*/
|
*/
|
||||||
protected function handleRouteRequirements($pathinfo, $name, Route $route)
|
protected function handleRouteRequirements($pathinfo, $name, Route $route)
|
||||||
{
|
{
|
||||||
|
// expression condition
|
||||||
|
if ($route->getCondition() && !$this->getExpressionLanguage()->evaluate($route->getCondition(), array('context' => $this->context, 'request' => $this->request))) {
|
||||||
|
return array(self::REQUIREMENT_MISMATCH, null);
|
||||||
|
}
|
||||||
|
|
||||||
// check HTTP scheme requirement
|
// check HTTP scheme requirement
|
||||||
$scheme = $route->getRequirement('_scheme');
|
$scheme = $route->getRequirement('_scheme');
|
||||||
if ($scheme && $this->context->getScheme() !== $scheme) {
|
if ($scheme && $this->context->getScheme() !== $scheme) {
|
||||||
|
@ -94,6 +94,15 @@ class TraceableUrlMatcher extends UrlMatcher
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check condition
|
||||||
|
if ($condition = $route->getCondition()) {
|
||||||
|
if (!$this->getExpressionLanguage()->evaluate($condition, array('context' => $this->context, 'request' => $this->request))) {
|
||||||
|
$this->addTrace(sprintf('Condition "%s" does not evaluate to "true"', $condition), self::ROUTE_ALMOST_MATCHES, $name, $route);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// check HTTP scheme requirement
|
// check HTTP scheme requirement
|
||||||
if ($scheme = $route->getRequirement('_scheme')) {
|
if ($scheme = $route->getRequirement('_scheme')) {
|
||||||
if ($this->context->getScheme() !== $scheme) {
|
if ($this->context->getScheme() !== $scheme) {
|
||||||
|
@ -16,6 +16,8 @@ use Symfony\Component\Routing\Exception\ResourceNotFoundException;
|
|||||||
use Symfony\Component\Routing\RouteCollection;
|
use Symfony\Component\Routing\RouteCollection;
|
||||||
use Symfony\Component\Routing\RequestContext;
|
use Symfony\Component\Routing\RequestContext;
|
||||||
use Symfony\Component\Routing\Route;
|
use Symfony\Component\Routing\Route;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UrlMatcher matches URL based on a set of routes.
|
* UrlMatcher matches URL based on a set of routes.
|
||||||
@ -24,7 +26,7 @@ use Symfony\Component\Routing\Route;
|
|||||||
*
|
*
|
||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
class UrlMatcher implements UrlMatcherInterface
|
class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
|
||||||
{
|
{
|
||||||
const REQUIREMENT_MATCH = 0;
|
const REQUIREMENT_MATCH = 0;
|
||||||
const REQUIREMENT_MISMATCH = 1;
|
const REQUIREMENT_MISMATCH = 1;
|
||||||
@ -45,6 +47,9 @@ class UrlMatcher implements UrlMatcherInterface
|
|||||||
*/
|
*/
|
||||||
protected $routes;
|
protected $routes;
|
||||||
|
|
||||||
|
protected $request;
|
||||||
|
protected $expressionLanguage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
@ -91,6 +96,20 @@ class UrlMatcher implements UrlMatcherInterface
|
|||||||
: new ResourceNotFoundException();
|
: new ResourceNotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function matchRequest(Request $request)
|
||||||
|
{
|
||||||
|
$this->request = $request;
|
||||||
|
|
||||||
|
$ret = $this->match($request->getPathInfo());
|
||||||
|
|
||||||
|
$this->request = null;
|
||||||
|
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tries to match a URL with a set of routes.
|
* Tries to match a URL with a set of routes.
|
||||||
*
|
*
|
||||||
@ -180,11 +199,18 @@ class UrlMatcher implements UrlMatcherInterface
|
|||||||
*/
|
*/
|
||||||
protected function handleRouteRequirements($pathinfo, $name, Route $route)
|
protected function handleRouteRequirements($pathinfo, $name, Route $route)
|
||||||
{
|
{
|
||||||
|
// expression condition
|
||||||
|
if ($route->getCondition() && !$this->getExpressionLanguage()->evaluate($route->getCondition(), array('context' => $this->context, 'request' => $this->request))) {
|
||||||
|
return array(self::REQUIREMENT_MISMATCH, null);
|
||||||
|
}
|
||||||
|
|
||||||
// check HTTP scheme requirement
|
// check HTTP scheme requirement
|
||||||
$scheme = $route->getRequirement('_scheme');
|
$scheme = $route->getRequirement('_scheme');
|
||||||
$status = $scheme && $scheme !== $this->context->getScheme() ? self::REQUIREMENT_MISMATCH : self::REQUIREMENT_MATCH;
|
if ($scheme && $scheme !== $this->context->getScheme()) {
|
||||||
|
return array(self::REQUIREMENT_MISMATCH, null);
|
||||||
|
}
|
||||||
|
|
||||||
return array($status, null);
|
return array(self::REQUIREMENT_MATCH, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -205,4 +231,16 @@ class UrlMatcher implements UrlMatcherInterface
|
|||||||
|
|
||||||
return $defaults;
|
return $defaults;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function getExpressionLanguage()
|
||||||
|
{
|
||||||
|
if (null === $this->expressionLanguage) {
|
||||||
|
if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
|
||||||
|
throw new RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
|
||||||
|
}
|
||||||
|
$this->expressionLanguage = new ExpressionLanguage();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->expressionLanguage;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,6 +61,8 @@ class Route implements \Serializable
|
|||||||
*/
|
*/
|
||||||
private $compiled;
|
private $compiled;
|
||||||
|
|
||||||
|
private $condition;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
@ -75,10 +77,11 @@ class Route implements \Serializable
|
|||||||
* @param string $host The host pattern to match
|
* @param string $host The host pattern to match
|
||||||
* @param string|array $schemes A required URI scheme or an array of restricted schemes
|
* @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
|
* @param string|array $methods A required HTTP method or an array of restricted methods
|
||||||
|
* @param string $condition A condition that should evaluate to true for the route to match
|
||||||
*
|
*
|
||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
public function __construct($path, array $defaults = array(), array $requirements = array(), array $options = array(), $host = '', $schemes = array(), $methods = array())
|
public function __construct($path, array $defaults = array(), array $requirements = array(), array $options = array(), $host = '', $schemes = array(), $methods = array(), $condition = null)
|
||||||
{
|
{
|
||||||
$this->setPath($path);
|
$this->setPath($path);
|
||||||
$this->setDefaults($defaults);
|
$this->setDefaults($defaults);
|
||||||
@ -93,6 +96,7 @@ class Route implements \Serializable
|
|||||||
if ($methods) {
|
if ($methods) {
|
||||||
$this->setMethods($methods);
|
$this->setMethods($methods);
|
||||||
}
|
}
|
||||||
|
$this->setCondition($condition);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function serialize()
|
public function serialize()
|
||||||
@ -105,6 +109,7 @@ class Route implements \Serializable
|
|||||||
'options' => $this->options,
|
'options' => $this->options,
|
||||||
'schemes' => $this->schemes,
|
'schemes' => $this->schemes,
|
||||||
'methods' => $this->methods,
|
'methods' => $this->methods,
|
||||||
|
'condition' => $this->condition,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,6 +123,7 @@ class Route implements \Serializable
|
|||||||
$this->options = $data['options'];
|
$this->options = $data['options'];
|
||||||
$this->schemes = $data['schemes'];
|
$this->schemes = $data['schemes'];
|
||||||
$this->methods = $data['methods'];
|
$this->methods = $data['methods'];
|
||||||
|
$this->condition = $data['condition'];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -543,6 +549,33 @@ class Route implements \Serializable
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the condition.
|
||||||
|
*
|
||||||
|
* @return string The condition
|
||||||
|
*/
|
||||||
|
public function getCondition()
|
||||||
|
{
|
||||||
|
return $this->condition;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the condition.
|
||||||
|
*
|
||||||
|
* This method implements a fluent interface.
|
||||||
|
*
|
||||||
|
* @param string $condition The condition
|
||||||
|
*
|
||||||
|
* @return Route The current Route instance
|
||||||
|
*/
|
||||||
|
public function setCondition($condition)
|
||||||
|
{
|
||||||
|
$this->condition = (string) $condition;
|
||||||
|
$this->compiled = null;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compiles the route.
|
* Compiles the route.
|
||||||
*
|
*
|
||||||
|
@ -177,6 +177,20 @@ class RouteCollection implements \IteratorAggregate, \Countable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a condition on all routes.
|
||||||
|
*
|
||||||
|
* Existing conditions will be overridden.
|
||||||
|
*
|
||||||
|
* @param string $condition The condition
|
||||||
|
*/
|
||||||
|
public function setCondition($condition)
|
||||||
|
{
|
||||||
|
foreach ($this->routes as $route) {
|
||||||
|
$route->setCondition($condition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds defaults to all routes.
|
* Adds defaults to all routes.
|
||||||
*
|
*
|
||||||
|
@ -35,15 +35,16 @@ class RouteTest extends \PHPUnit_Framework_TestCase
|
|||||||
public function getValidParameters()
|
public function getValidParameters()
|
||||||
{
|
{
|
||||||
return array(
|
return array(
|
||||||
array('value', '/Blog', 'getPattern'),
|
array('value', '/Blog', 'getPattern'),
|
||||||
array('value', '/Blog', 'getPath'),
|
array('value', '/Blog', 'getPath'),
|
||||||
array('requirements', array('_method' => 'GET'), 'getRequirements'),
|
array('requirements', array('_method' => 'GET'), 'getRequirements'),
|
||||||
array('options', array('compiler_class' => 'RouteCompiler'), 'getOptions'),
|
array('options', array('compiler_class' => 'RouteCompiler'), 'getOptions'),
|
||||||
array('name', 'blog_index', 'getName'),
|
array('name', 'blog_index', 'getName'),
|
||||||
array('defaults', array('_controller' => 'MyBlogBundle:Blog:index'), 'getDefaults'),
|
array('defaults', array('_controller' => 'MyBlogBundle:Blog:index'), 'getDefaults'),
|
||||||
array('schemes', array('https'), 'getSchemes'),
|
array('schemes', array('https'), 'getSchemes'),
|
||||||
array('methods', array('GET', 'POST'), 'getMethods'),
|
array('methods', array('GET', 'POST'), 'getMethods'),
|
||||||
array('host', array('{locale}.example.com'), 'getHost')
|
array('host', array('{locale}.example.com'), 'getHost'),
|
||||||
|
array('condition', array('context.getMethod() == "GET"'), 'getCondition'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,8 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
|
|||||||
{
|
{
|
||||||
$allow = array();
|
$allow = array();
|
||||||
$pathinfo = rawurldecode($pathinfo);
|
$pathinfo = rawurldecode($pathinfo);
|
||||||
|
$context = $this->context;
|
||||||
|
$request = $this->request;
|
||||||
|
|
||||||
// foo
|
// foo
|
||||||
if (0 === strpos($pathinfo, '/foo') && preg_match('#^/foo/(?P<bar>baz|symfony)$#s', $pathinfo, $matches)) {
|
if (0 === strpos($pathinfo, '/foo') && preg_match('#^/foo/(?P<bar>baz|symfony)$#s', $pathinfo, $matches)) {
|
||||||
|
@ -24,6 +24,8 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
|
|||||||
{
|
{
|
||||||
$allow = array();
|
$allow = array();
|
||||||
$pathinfo = rawurldecode($pathinfo);
|
$pathinfo = rawurldecode($pathinfo);
|
||||||
|
$context = $this->context;
|
||||||
|
$request = $this->request;
|
||||||
|
|
||||||
// foo
|
// foo
|
||||||
if (0 === strpos($pathinfo, '/foo') && preg_match('#^/foo/(?P<bar>baz|symfony)$#s', $pathinfo, $matches)) {
|
if (0 === strpos($pathinfo, '/foo') && preg_match('#^/foo/(?P<bar>baz|symfony)$#s', $pathinfo, $matches)) {
|
||||||
|
@ -24,6 +24,8 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
|
|||||||
{
|
{
|
||||||
$allow = array();
|
$allow = array();
|
||||||
$pathinfo = rawurldecode($pathinfo);
|
$pathinfo = rawurldecode($pathinfo);
|
||||||
|
$context = $this->context;
|
||||||
|
$request = $this->request;
|
||||||
|
|
||||||
if (0 === strpos($pathinfo, '/rootprefix')) {
|
if (0 === strpos($pathinfo, '/rootprefix')) {
|
||||||
// static
|
// static
|
||||||
@ -38,6 +40,11 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// with-condition
|
||||||
|
if ($pathinfo === '/with-condition' && ($context->getMethod() == "GET")) {
|
||||||
|
return array('_route' => 'with-condition');
|
||||||
|
}
|
||||||
|
|
||||||
throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException();
|
throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,14 +10,18 @@ $collection->add('blog_show', new Route(
|
|||||||
array('compiler_class' => 'RouteCompiler'),
|
array('compiler_class' => 'RouteCompiler'),
|
||||||
'{locale}.example.com',
|
'{locale}.example.com',
|
||||||
array('https'),
|
array('https'),
|
||||||
array('GET','POST','put','OpTiOnS')
|
array('GET','POST','put','OpTiOnS'),
|
||||||
|
'context.getMethod() == "GET"'
|
||||||
));
|
));
|
||||||
$collection->add('blog_show_legacy', new Route(
|
$collection->add('blog_show_legacy', new Route(
|
||||||
'/blog/{slug}',
|
'/blog/{slug}',
|
||||||
array('_controller' => 'MyBlogBundle:Blog:show'),
|
array('_controller' => 'MyBlogBundle:Blog:show'),
|
||||||
array('_method' => 'GET|POST|put|OpTiOnS', '_scheme' => 'https', 'locale' => '\w+',),
|
array('_method' => 'GET|POST|put|OpTiOnS', '_scheme' => 'https', 'locale' => '\w+',),
|
||||||
array('compiler_class' => 'RouteCompiler'),
|
array('compiler_class' => 'RouteCompiler'),
|
||||||
'{locale}.example.com'
|
'{locale}.example.com',
|
||||||
|
array(),
|
||||||
|
array(),
|
||||||
|
'context.getMethod() == "GET"'
|
||||||
));
|
));
|
||||||
|
|
||||||
return $collection;
|
return $collection;
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
<default key="_controller">MyBundle:Blog:show</default>
|
<default key="_controller">MyBundle:Blog:show</default>
|
||||||
<requirement key="locale">\w+</requirement>
|
<requirement key="locale">\w+</requirement>
|
||||||
<option key="compiler_class">RouteCompiler</option>
|
<option key="compiler_class">RouteCompiler</option>
|
||||||
|
<condition>context.getMethod() == "GET"</condition>
|
||||||
</route>
|
</route>
|
||||||
|
|
||||||
<route id="blog_show_legacy" pattern="/blog/{slug}" host="{locale}.example.com">
|
<route id="blog_show_legacy" pattern="/blog/{slug}" host="{locale}.example.com">
|
||||||
@ -17,5 +18,6 @@
|
|||||||
<requirement key="_scheme">hTTps</requirement>
|
<requirement key="_scheme">hTTps</requirement>
|
||||||
<requirement key="locale">\w+</requirement>
|
<requirement key="locale">\w+</requirement>
|
||||||
<option key="compiler_class">RouteCompiler</option>
|
<option key="compiler_class">RouteCompiler</option>
|
||||||
|
<condition>context.getMethod() == "GET"</condition>
|
||||||
</route>
|
</route>
|
||||||
</routes>
|
</routes>
|
||||||
|
@ -5,6 +5,7 @@ blog_show:
|
|||||||
requirements: { 'locale': '\w+' }
|
requirements: { 'locale': '\w+' }
|
||||||
methods: ['GET','POST','put','OpTiOnS']
|
methods: ['GET','POST','put','OpTiOnS']
|
||||||
schemes: ['https']
|
schemes: ['https']
|
||||||
|
condition: 'context.getMethod() == "GET"'
|
||||||
options:
|
options:
|
||||||
compiler_class: RouteCompiler
|
compiler_class: RouteCompiler
|
||||||
|
|
||||||
@ -13,5 +14,6 @@ blog_show_legacy:
|
|||||||
defaults: { _controller: "MyBundle:Blog:show" }
|
defaults: { _controller: "MyBundle:Blog:show" }
|
||||||
host: "{locale}.example.com"
|
host: "{locale}.example.com"
|
||||||
requirements: { '_method': 'GET|POST|put|OpTiOnS', _scheme: https, 'locale': '\w+' }
|
requirements: { '_method': 'GET|POST|put|OpTiOnS', _scheme: https, 'locale': '\w+' }
|
||||||
|
condition: 'context.getMethod() == "GET"'
|
||||||
options:
|
options:
|
||||||
compiler_class: RouteCompiler
|
compiler_class: RouteCompiler
|
||||||
|
@ -84,7 +84,12 @@ class AnnotationClassLoaderTest extends AbstractAnnotationLoaderTest
|
|||||||
array(
|
array(
|
||||||
'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass',
|
'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass',
|
||||||
array('name' => 'route1', 'defaults' => array('arg2' => 'foobar')),
|
array('name' => 'route1', 'defaults' => array('arg2' => 'foobar')),
|
||||||
array('arg2' => false, 'arg3' => 'defaultValue3')
|
array('arg2' => 'defaultValue2', 'arg3' =>'defaultValue3')
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass',
|
||||||
|
array('name' => 'route1', 'defaults' => array('arg2' => 'foo'), 'condition' => 'context.getMethod() == "GET"'),
|
||||||
|
array('arg2' => 'defaultValue2', 'arg3' =>'defaultValue3')
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -102,6 +107,7 @@ class AnnotationClassLoaderTest extends AbstractAnnotationLoaderTest
|
|||||||
'defaults' => array(),
|
'defaults' => array(),
|
||||||
'schemes' => array(),
|
'schemes' => array(),
|
||||||
'methods' => array(),
|
'methods' => array(),
|
||||||
|
'condition' => null,
|
||||||
), $routeDatas);
|
), $routeDatas);
|
||||||
|
|
||||||
$this->reader
|
$this->reader
|
||||||
@ -116,6 +122,7 @@ class AnnotationClassLoaderTest extends AbstractAnnotationLoaderTest
|
|||||||
$this->assertSame($routeDatas['requirements'],$route->getRequirements(), '->load preserves requirements annotation');
|
$this->assertSame($routeDatas['requirements'],$route->getRequirements(), '->load preserves requirements annotation');
|
||||||
$this->assertCount(0, array_intersect($route->getOptions(), $routeDatas['options']), '->load preserves options annotation');
|
$this->assertCount(0, array_intersect($route->getOptions(), $routeDatas['options']), '->load preserves options annotation');
|
||||||
$this->assertSame(array_replace($methodArgs, $routeDatas['defaults']), $route->getDefaults(), '->load preserves defaults annotation');
|
$this->assertSame(array_replace($methodArgs, $routeDatas['defaults']), $route->getDefaults(), '->load preserves defaults annotation');
|
||||||
|
$this->assertEquals($routeDatas['condition'], $route->getCondition(), '->load preserves condition annotation');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getAnnotatedRoute($datas)
|
private function getAnnotatedRoute($datas)
|
||||||
|
@ -45,6 +45,7 @@ class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertSame('RouteCompiler', $route->getOption('compiler_class'));
|
$this->assertSame('RouteCompiler', $route->getOption('compiler_class'));
|
||||||
$this->assertEquals(array('GET', 'POST', 'PUT', 'OPTIONS'), $route->getMethods());
|
$this->assertEquals(array('GET', 'POST', 'PUT', 'OPTIONS'), $route->getMethods());
|
||||||
$this->assertEquals(array('https'), $route->getSchemes());
|
$this->assertEquals(array('https'), $route->getSchemes());
|
||||||
|
$this->assertEquals('context.getMethod() == "GET"', $route->getCondition());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,6 +79,7 @@ class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertSame('RouteCompiler', $route->getOption('compiler_class'));
|
$this->assertSame('RouteCompiler', $route->getOption('compiler_class'));
|
||||||
$this->assertEquals(array('GET', 'POST', 'PUT', 'OPTIONS'), $route->getMethods());
|
$this->assertEquals(array('GET', 'POST', 'PUT', 'OPTIONS'), $route->getMethods());
|
||||||
$this->assertEquals(array('https'), $route->getSchemes());
|
$this->assertEquals(array('https'), $route->getSchemes());
|
||||||
|
$this->assertEquals('context.getMethod() == "GET"', $route->getCondition());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,6 +251,9 @@ class PhpMatcherDumperTest extends \PHPUnit_Framework_TestCase
|
|||||||
$rootprefixCollection->add('static', new Route('/test'));
|
$rootprefixCollection->add('static', new Route('/test'));
|
||||||
$rootprefixCollection->add('dynamic', new Route('/{var}'));
|
$rootprefixCollection->add('dynamic', new Route('/{var}'));
|
||||||
$rootprefixCollection->addPrefix('rootprefix');
|
$rootprefixCollection->addPrefix('rootprefix');
|
||||||
|
$route = new Route('/with-condition');
|
||||||
|
$route->setCondition('context.getMethod() == "GET"');
|
||||||
|
$rootprefixCollection->add('with-condition', $route);
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
array($collection, 'url_matcher1.php', array()),
|
array($collection, 'url_matcher1.php', array()),
|
||||||
|
@ -26,13 +26,14 @@ class TraceableUrlMatcherTest extends \PHPUnit_Framework_TestCase
|
|||||||
$coll->add('bar1', new Route('/bar/{name}', array(), array('id' => '\w+', '_method' => 'POST')));
|
$coll->add('bar1', new Route('/bar/{name}', array(), array('id' => '\w+', '_method' => 'POST')));
|
||||||
$coll->add('bar2', new Route('/foo', array(), array(), array(), 'baz'));
|
$coll->add('bar2', new Route('/foo', array(), array(), array(), 'baz'));
|
||||||
$coll->add('bar3', new Route('/foo1', array(), array(), array(), 'baz'));
|
$coll->add('bar3', new Route('/foo1', array(), array(), array(), 'baz'));
|
||||||
|
$coll->add('bar4', new Route('/foo2', array(), array(), array(), 'baz', array(), array(), 'context.getMethod() == "GET"'));
|
||||||
|
|
||||||
$context = new RequestContext();
|
$context = new RequestContext();
|
||||||
$context->setHost('baz');
|
$context->setHost('baz');
|
||||||
|
|
||||||
$matcher = new TraceableUrlMatcher($coll, $context);
|
$matcher = new TraceableUrlMatcher($coll, $context);
|
||||||
$traces = $matcher->getTraces('/babar');
|
$traces = $matcher->getTraces('/babar');
|
||||||
$this->assertEquals(array(0, 0, 0, 0, 0), $this->getLevels($traces));
|
$this->assertEquals(array(0, 0, 0, 0, 0, 0), $this->getLevels($traces));
|
||||||
|
|
||||||
$traces = $matcher->getTraces('/foo');
|
$traces = $matcher->getTraces('/foo');
|
||||||
$this->assertEquals(array(1, 0, 0, 2), $this->getLevels($traces));
|
$this->assertEquals(array(1, 0, 0, 2), $this->getLevels($traces));
|
||||||
@ -41,7 +42,7 @@ class TraceableUrlMatcherTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertEquals(array(0, 2), $this->getLevels($traces));
|
$this->assertEquals(array(0, 2), $this->getLevels($traces));
|
||||||
|
|
||||||
$traces = $matcher->getTraces('/bar/dd');
|
$traces = $matcher->getTraces('/bar/dd');
|
||||||
$this->assertEquals(array(0, 1, 1, 0, 0), $this->getLevels($traces));
|
$this->assertEquals(array(0, 1, 1, 0, 0, 0), $this->getLevels($traces));
|
||||||
|
|
||||||
$traces = $matcher->getTraces('/foo1');
|
$traces = $matcher->getTraces('/foo1');
|
||||||
$this->assertEquals(array(0, 0, 0, 0, 2), $this->getLevels($traces));
|
$this->assertEquals(array(0, 0, 0, 0, 2), $this->getLevels($traces));
|
||||||
@ -52,6 +53,9 @@ class TraceableUrlMatcherTest extends \PHPUnit_Framework_TestCase
|
|||||||
|
|
||||||
$traces = $matcher->getTraces('/bar/dd');
|
$traces = $matcher->getTraces('/bar/dd');
|
||||||
$this->assertEquals(array(0, 1, 2), $this->getLevels($traces));
|
$this->assertEquals(array(0, 1, 2), $this->getLevels($traces));
|
||||||
|
|
||||||
|
$traces = $matcher->getTraces('/foo2');
|
||||||
|
$this->assertEquals(array(0, 0, 0, 0, 0, 1), $this->getLevels($traces));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getLevels($traces)
|
public function getLevels($traces)
|
||||||
|
@ -321,6 +321,19 @@ class UrlMatcherTest extends \PHPUnit_Framework_TestCase
|
|||||||
$matcher->match('/foo');
|
$matcher->match('/foo');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException
|
||||||
|
*/
|
||||||
|
public function testCondition()
|
||||||
|
{
|
||||||
|
$coll = new RouteCollection();
|
||||||
|
$route = new Route('/foo');
|
||||||
|
$route->setCondition('context.getMethod() == "POST"');
|
||||||
|
$coll->add('foo', $route);
|
||||||
|
$matcher = new UrlMatcher($coll, new RequestContext());
|
||||||
|
$matcher->match('/foo');
|
||||||
|
}
|
||||||
|
|
||||||
public function testDecodeOnce()
|
public function testDecodeOnce()
|
||||||
{
|
{
|
||||||
$coll = new RouteCollection();
|
$coll = new RouteCollection();
|
||||||
|
@ -244,4 +244,18 @@ class RouteCollectionTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertEquals('{locale}.example.com', $routea->getHost());
|
$this->assertEquals('{locale}.example.com', $routea->getHost());
|
||||||
$this->assertEquals('{locale}.example.com', $routeb->getHost());
|
$this->assertEquals('{locale}.example.com', $routeb->getHost());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testSetCondition()
|
||||||
|
{
|
||||||
|
$collection = new RouteCollection();
|
||||||
|
$routea = new Route('/a');
|
||||||
|
$routeb = new Route('/b', array(), array(), array(), '{locale}.example.net', array(), array(), 'context.getMethod() == "GET"');
|
||||||
|
$collection->add('a', $routea);
|
||||||
|
$collection->add('b', $routeb);
|
||||||
|
|
||||||
|
$collection->setCondition('context.getMethod() == "POST"');
|
||||||
|
|
||||||
|
$this->assertEquals('context.getMethod() == "POST"', $routea->getCondition());
|
||||||
|
$this->assertEquals('context.getMethod() == "POST"', $routeb->getCondition());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,9 +24,10 @@ class RouteTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertEquals('bar', $route->getOption('foo'), '__construct() takes options as its fourth argument');
|
$this->assertEquals('bar', $route->getOption('foo'), '__construct() takes options as its fourth argument');
|
||||||
$this->assertEquals('{locale}.example.com', $route->getHost(), '__construct() takes a host pattern as its fifth argument');
|
$this->assertEquals('{locale}.example.com', $route->getHost(), '__construct() takes a host pattern as its fifth argument');
|
||||||
|
|
||||||
$route = new Route('/', array(), array(), array(), '', array('Https'), array('POST', 'put'));
|
$route = new Route('/', array(), array(), array(), '', array('Https'), array('POST', 'put'), 'context.getMethod() == "GET"');
|
||||||
$this->assertEquals(array('https'), $route->getSchemes(), '__construct() takes schemes as its sixth argument and lowercases it');
|
$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');
|
$this->assertEquals(array('POST', 'PUT'), $route->getMethods(), '__construct() takes methods as its seventh argument and uppercases it');
|
||||||
|
$this->assertEquals('context.getMethod() == "GET"', $route->getCondition(), '__construct() takes a condition as its eight argument');
|
||||||
|
|
||||||
$route = new Route('/', array(), array(), array(), '', 'Https', 'Post');
|
$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('https'), $route->getSchemes(), '__construct() takes a single scheme as its sixth argument');
|
||||||
@ -181,6 +182,14 @@ class RouteTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertNull($route->getRequirement('_method'));
|
$this->assertNull($route->getRequirement('_method'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testCondition()
|
||||||
|
{
|
||||||
|
$route = new Route('/');
|
||||||
|
$this->assertEquals(null, $route->getCondition());
|
||||||
|
$route->setCondition('context.getMethod() == "GET"');
|
||||||
|
$this->assertEquals('context.getMethod() == "GET"', $route->getCondition());
|
||||||
|
}
|
||||||
|
|
||||||
public function testCompile()
|
public function testCompile()
|
||||||
{
|
{
|
||||||
$route = new Route('/{foo}');
|
$route = new Route('/{foo}');
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
"require-dev": {
|
"require-dev": {
|
||||||
"symfony/config": "~2.2",
|
"symfony/config": "~2.2",
|
||||||
"symfony/yaml": "~2.0",
|
"symfony/yaml": "~2.0",
|
||||||
|
"symfony/expression-language": "~2.4",
|
||||||
"doctrine/common": "~2.2",
|
"doctrine/common": "~2.2",
|
||||||
"psr/log": "~1.0"
|
"psr/log": "~1.0"
|
||||||
},
|
},
|
||||||
|
Reference in New Issue
Block a user