[Validator] Renamed Condition to Expression and added possibility to set it onto properties
This commit is contained in:
parent
a3b3a78237
commit
d4ebbfd02d
@ -48,6 +48,9 @@ class FrameworkExtension extends Extension
|
||||
// will be used and everything will still work as expected.
|
||||
$loader->load('translation.xml');
|
||||
|
||||
// Property access is used by both the Form and the Validator component
|
||||
$loader->load('property_access.xml');
|
||||
|
||||
$loader->load('debug_prod.xml');
|
||||
|
||||
if ($container->getParameter('kernel.debug')) {
|
||||
|
@ -10,7 +10,6 @@
|
||||
<parameter key="form.factory.class">Symfony\Component\Form\FormFactory</parameter>
|
||||
<parameter key="form.extension.class">Symfony\Component\Form\Extension\DependencyInjection\DependencyInjectionExtension</parameter>
|
||||
<parameter key="form.type_guesser.validator.class">Symfony\Component\Form\Extension\Validator\ValidatorTypeGuesser</parameter>
|
||||
<parameter key="property_accessor.class">Symfony\Component\PropertyAccess\PropertyAccessor</parameter>
|
||||
</parameters>
|
||||
|
||||
<services>
|
||||
@ -54,9 +53,6 @@
|
||||
<argument type="service" id="validator.mapping.class_metadata_factory" />
|
||||
</service>
|
||||
|
||||
<!-- PropertyAccessor -->
|
||||
<service id="property_accessor" class="%property_accessor.class%" />
|
||||
|
||||
<!-- CoreExtension -->
|
||||
<service id="form.type.form" class="Symfony\Component\Form\Extension\Core\Type\FormType">
|
||||
<argument type="service" id="property_accessor"/>
|
||||
|
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" ?>
|
||||
|
||||
<container xmlns="http://symfony.com/schema/dic/services"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||
|
||||
<parameters>
|
||||
<parameter key="property_accessor.class">Symfony\Component\PropertyAccess\PropertyAccessor</parameter>
|
||||
</parameters>
|
||||
|
||||
<services>
|
||||
<service id="property_accessor" class="%property_accessor.class%" />
|
||||
</services>
|
||||
</container>
|
@ -17,6 +17,7 @@
|
||||
<parameter key="validator.validator_factory.class">Symfony\Bundle\FrameworkBundle\Validator\ConstraintValidatorFactory</parameter>
|
||||
<parameter key="validator.mapping.loader.xml_files_loader.mapping_files" type="collection" />
|
||||
<parameter key="validator.mapping.loader.yaml_files_loader.mapping_files" type="collection" />
|
||||
<parameter key="validator.expression.class">Symfony\Component\Validator\Constraints\ExpressionValidator</parameter>
|
||||
</parameters>
|
||||
|
||||
<services>
|
||||
@ -63,5 +64,10 @@
|
||||
<service id="validator.mapping.loader.yaml_files_loader" class="%validator.mapping.loader.yaml_files_loader.class%" public="false">
|
||||
<argument>%validator.mapping.loader.yaml_files_loader.mapping_files%</argument>
|
||||
</service>
|
||||
|
||||
<service id="validator.expression" class="%validator.expression.class%">
|
||||
<argument type="service" id="property_accessor" />
|
||||
<tag name="validator.constraint_validator" alias="validator.expression" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
@ -11,8 +11,9 @@
|
||||
|
||||
namespace Symfony\Component\Validator;
|
||||
|
||||
use Symfony\Component\Validator\ConstraintValidatorFactoryInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
|
||||
use Symfony\Component\Validator\Constraints\ExpressionValidator;
|
||||
|
||||
/**
|
||||
* Default implementation of the ConstraintValidatorFactoryInterface.
|
||||
@ -20,11 +21,23 @@ use Symfony\Component\Validator\Constraint;
|
||||
* This enforces the convention that the validatedBy() method on any
|
||||
* Constrain will return the class name of the ConstraintValidator that
|
||||
* should validate the Constraint.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class ConstraintValidatorFactory implements ConstraintValidatorFactoryInterface
|
||||
{
|
||||
protected $validators = array();
|
||||
|
||||
/**
|
||||
* @var PropertyAccessorInterface
|
||||
*/
|
||||
private $propertyAccessor;
|
||||
|
||||
public function __construct(PropertyAccessorInterface $propertyAccessor = null)
|
||||
{
|
||||
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@ -32,8 +45,19 @@ class ConstraintValidatorFactory implements ConstraintValidatorFactoryInterface
|
||||
{
|
||||
$className = $constraint->validatedBy();
|
||||
|
||||
if (!isset($this->validators[$className]) || $className === 'Symfony\Component\Validator\Constraints\CollectionValidator') {
|
||||
$this->validators[$className] = new $className();
|
||||
// The second condition is a hack that is needed when CollectionValidator
|
||||
// calls itself recursively (Collection constraints can be nested).
|
||||
// Since the context of the validator is overwritten when initialize()
|
||||
// is called for the nested constraint, the outer validator is
|
||||
// acting on the wrong context when the nested validation terminates.
|
||||
//
|
||||
// A better solution - which should be approached in Symfony 3.0 - is to
|
||||
// remove the initialize() method and pass the context as last argument
|
||||
// to validate() instead.
|
||||
if (!isset($this->validators[$className]) || 'Symfony\Component\Validator\Constraints\CollectionValidator' === $className) {
|
||||
$this->validators[$className] = 'validator.expression' === $className
|
||||
? new ExpressionValidator($this->propertyAccessor)
|
||||
: new $className();
|
||||
}
|
||||
|
||||
return $this->validators[$className];
|
||||
|
@ -1,50 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Validator\Constraints;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class ConditionValidator extends ConstraintValidator
|
||||
{
|
||||
private $expressionLanguage;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function validate($object, Constraint $constraint)
|
||||
{
|
||||
if (null === $object) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->getExpressionLanguage()->evaluate($constraint->condition, array('this' => $object))) {
|
||||
$this->context->addViolation($constraint->message);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -17,18 +17,19 @@ use Symfony\Component\Validator\Constraint;
|
||||
* @Annotation
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class Condition extends Constraint
|
||||
class Expression extends Constraint
|
||||
{
|
||||
public $message = 'This value is not valid.';
|
||||
public $condition;
|
||||
public $expression;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getDefaultOption()
|
||||
{
|
||||
return 'condition';
|
||||
return 'expression';
|
||||
}
|
||||
|
||||
/**
|
||||
@ -36,13 +37,22 @@ class Condition extends Constraint
|
||||
*/
|
||||
public function getRequiredOptions()
|
||||
{
|
||||
return array('condition');
|
||||
return array('expression');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getTargets()
|
||||
{
|
||||
return self::CLASS_CONSTRAINT;
|
||||
return array(self::CLASS_CONSTRAINT, self::PROPERTY_CONSTRAINT);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function validatedBy()
|
||||
{
|
||||
return 'validator.expression';
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Validator\Constraints;
|
||||
|
||||
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
|
||||
use Symfony\Component\PropertyAccess\PropertyPath;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
|
||||
use Symfony\Component\Validator\Exception\RuntimeException;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Bernhard Schussek <bschussek@symfony.com>
|
||||
*/
|
||||
class ExpressionValidator extends ConstraintValidator
|
||||
{
|
||||
/**
|
||||
* @var PropertyAccessorInterface
|
||||
*/
|
||||
private $propertyAccessor;
|
||||
|
||||
/**
|
||||
* @var ExpressionLanguage
|
||||
*/
|
||||
private $expressionLanguage;
|
||||
|
||||
public function __construct(PropertyAccessorInterface $propertyAccessor)
|
||||
{
|
||||
$this->propertyAccessor = $propertyAccessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function validate($value, Constraint $constraint)
|
||||
{
|
||||
if (null === $value || '' === $value) {
|
||||
return;
|
||||
}
|
||||
|
||||
$variables = array();
|
||||
|
||||
if (null === $this->context->getPropertyName()) {
|
||||
$variables['this'] = $value;
|
||||
} else {
|
||||
// Extract the object that the property belongs to from the object
|
||||
// graph
|
||||
$path = new PropertyPath($this->context->getPropertyPath());
|
||||
$parentPath = $path->getParent();
|
||||
$root = $this->context->getRoot();
|
||||
|
||||
$variables['value'] = $value;
|
||||
$variables['this'] = $parentPath ? $this->propertyAccessor->getValue($root, $parentPath) : $root;
|
||||
}
|
||||
|
||||
if (!$this->getExpressionLanguage()->evaluate($constraint->expression, $variables)) {
|
||||
$this->context->addViolation($constraint->message);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Validator\Exception;
|
||||
|
||||
/**
|
||||
* Base RuntimeException for the Validator component.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class RuntimeException extends \RuntimeException implements ExceptionInterface
|
||||
{
|
||||
}
|
@ -0,0 +1,197 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Validator\Tests\Constraints;
|
||||
|
||||
use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||
use Symfony\Component\Validator\Constraints\Expression;
|
||||
use Symfony\Component\Validator\Constraints\ExpressionValidator;
|
||||
|
||||
class ExpressionValidatorTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
protected $context;
|
||||
protected $validator;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->context = $this->getMock('Symfony\Component\Validator\ExecutionContext', array(), array(), '', false);
|
||||
$this->validator = new ExpressionValidator(PropertyAccess::createPropertyAccessor());
|
||||
$this->validator->initialize($this->context);
|
||||
|
||||
$this->context->expects($this->any())
|
||||
->method('getClassName')
|
||||
->will($this->returnValue(__CLASS__));
|
||||
}
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
$this->context = null;
|
||||
$this->validator = null;
|
||||
}
|
||||
|
||||
public function testNullIsValid()
|
||||
{
|
||||
$this->context->expects($this->never())
|
||||
->method('addViolation');
|
||||
|
||||
$this->validator->validate(null, new Expression('value == 1'));
|
||||
}
|
||||
|
||||
public function testEmptyStringIsValid()
|
||||
{
|
||||
$this->context->expects($this->never())
|
||||
->method('addViolation');
|
||||
|
||||
$this->validator->validate('', new Expression('value == 1'));
|
||||
}
|
||||
|
||||
public function testSucceedingExpressionAtObjectLevel()
|
||||
{
|
||||
$constraint = new Expression('this.property == 1');
|
||||
|
||||
$object = (object) array('property' => '1');
|
||||
|
||||
$this->context->expects($this->any())
|
||||
->method('getPropertyName')
|
||||
->will($this->returnValue(null));
|
||||
|
||||
$this->context->expects($this->never())
|
||||
->method('addViolation');
|
||||
|
||||
$this->validator->validate($object, $constraint);
|
||||
}
|
||||
|
||||
public function testFailingExpressionAtObjectLevel()
|
||||
{
|
||||
$constraint = new Expression(array(
|
||||
'expression' => 'this.property == 1',
|
||||
'message' => 'myMessage',
|
||||
));
|
||||
|
||||
$object = (object) array('property' => '2');
|
||||
|
||||
$this->context->expects($this->any())
|
||||
->method('getPropertyName')
|
||||
->will($this->returnValue(null));
|
||||
|
||||
$this->context->expects($this->once())
|
||||
->method('addViolation')
|
||||
->with('myMessage');
|
||||
|
||||
$this->validator->validate($object, $constraint);
|
||||
}
|
||||
|
||||
public function testSucceedingExpressionAtPropertyLevel()
|
||||
{
|
||||
$constraint = new Expression('value == this.expected');
|
||||
|
||||
$object = (object) array('expected' => '1');
|
||||
|
||||
$this->context->expects($this->any())
|
||||
->method('getPropertyName')
|
||||
->will($this->returnValue('property'));
|
||||
|
||||
$this->context->expects($this->any())
|
||||
->method('getPropertyPath')
|
||||
->will($this->returnValue('property'));
|
||||
|
||||
$this->context->expects($this->any())
|
||||
->method('getRoot')
|
||||
->will($this->returnValue($object));
|
||||
|
||||
$this->context->expects($this->never())
|
||||
->method('addViolation');
|
||||
|
||||
$this->validator->validate('1', $constraint);
|
||||
}
|
||||
|
||||
public function testFailingExpressionAtPropertyLevel()
|
||||
{
|
||||
$constraint = new Expression(array(
|
||||
'expression' => 'value == this.expected',
|
||||
'message' => 'myMessage',
|
||||
));
|
||||
|
||||
$object = (object) array('expected' => '1');
|
||||
|
||||
$this->context->expects($this->any())
|
||||
->method('getPropertyName')
|
||||
->will($this->returnValue('property'));
|
||||
|
||||
$this->context->expects($this->any())
|
||||
->method('getPropertyPath')
|
||||
->will($this->returnValue('property'));
|
||||
|
||||
$this->context->expects($this->any())
|
||||
->method('getRoot')
|
||||
->will($this->returnValue($object));
|
||||
|
||||
$this->context->expects($this->once())
|
||||
->method('addViolation')
|
||||
->with('myMessage');
|
||||
|
||||
$this->validator->validate('2', $constraint);
|
||||
}
|
||||
|
||||
public function testSucceedingExpressionAtNestedPropertyLevel()
|
||||
{
|
||||
$constraint = new Expression('value == this.expected');
|
||||
|
||||
$object = (object) array('expected' => '1');
|
||||
$root = (object) array('nested' => $object);
|
||||
|
||||
$this->context->expects($this->any())
|
||||
->method('getPropertyName')
|
||||
->will($this->returnValue('property'));
|
||||
|
||||
$this->context->expects($this->any())
|
||||
->method('getPropertyPath')
|
||||
->will($this->returnValue('nested.property'));
|
||||
|
||||
$this->context->expects($this->any())
|
||||
->method('getRoot')
|
||||
->will($this->returnValue($root));
|
||||
|
||||
$this->context->expects($this->never())
|
||||
->method('addViolation');
|
||||
|
||||
$this->validator->validate('1', $constraint);
|
||||
}
|
||||
|
||||
public function testFailingExpressionAtNestedPropertyLevel()
|
||||
{
|
||||
$constraint = new Expression(array(
|
||||
'expression' => 'value == this.expected',
|
||||
'message' => 'myMessage',
|
||||
));
|
||||
|
||||
$object = (object) array('expected' => '1');
|
||||
$root = (object) array('nested' => $object);
|
||||
|
||||
$this->context->expects($this->any())
|
||||
->method('getPropertyName')
|
||||
->will($this->returnValue('property'));
|
||||
|
||||
$this->context->expects($this->any())
|
||||
->method('getPropertyPath')
|
||||
->will($this->returnValue('nested.property'));
|
||||
|
||||
$this->context->expects($this->any())
|
||||
->method('getRoot')
|
||||
->will($this->returnValue($root));
|
||||
|
||||
$this->context->expects($this->once())
|
||||
->method('addViolation')
|
||||
->with('myMessage');
|
||||
|
||||
$this->validator->validate('2', $constraint);
|
||||
}
|
||||
}
|
@ -11,6 +11,8 @@
|
||||
|
||||
namespace Symfony\Component\Validator;
|
||||
|
||||
use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
|
||||
use Symfony\Component\Validator\Mapping\ClassMetadataFactory;
|
||||
use Symfony\Component\Validator\Exception\ValidatorException;
|
||||
use Symfony\Component\Validator\Mapping\Loader\LoaderChain;
|
||||
@ -84,6 +86,11 @@ class ValidatorBuilder implements ValidatorBuilderInterface
|
||||
*/
|
||||
private $translationDomain;
|
||||
|
||||
/**
|
||||
* @var PropertyAccessorInterface
|
||||
*/
|
||||
private $propertyAccessor;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@ -253,6 +260,10 @@ class ValidatorBuilder implements ValidatorBuilderInterface
|
||||
*/
|
||||
public function setConstraintValidatorFactory(ConstraintValidatorFactoryInterface $validatorFactory)
|
||||
{
|
||||
if (null !== $this->propertyAccessor) {
|
||||
throw new ValidatorException('You cannot set a validator factory after setting a custom property accessor. Remove the call to setPropertyAccessor() if you want to call setConstraintValidatorFactory().');
|
||||
}
|
||||
|
||||
$this->validatorFactory = $validatorFactory;
|
||||
|
||||
return $this;
|
||||
@ -278,6 +289,20 @@ class ValidatorBuilder implements ValidatorBuilderInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setPropertyAccessor(PropertyAccessorInterface $propertyAccessor)
|
||||
{
|
||||
if (null !== $this->validatorFactory) {
|
||||
throw new ValidatorException('You cannot set a property accessor after setting a custom validator factory. Configure your validator factory instead.');
|
||||
}
|
||||
|
||||
$this->propertyAccessor = $propertyAccessor;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@ -319,7 +344,8 @@ class ValidatorBuilder implements ValidatorBuilderInterface
|
||||
$metadataFactory = new ClassMetadataFactory($loader, $this->metadataCache);
|
||||
}
|
||||
|
||||
$validatorFactory = $this->validatorFactory ?: new ConstraintValidatorFactory();
|
||||
$propertyAccessor = $this->propertyAccessor ?: PropertyAccess::createPropertyAccessor();
|
||||
$validatorFactory = $this->validatorFactory ?: new ConstraintValidatorFactory($propertyAccessor);
|
||||
$translator = $this->translator ?: new DefaultTranslator();
|
||||
|
||||
return new Validator($metadataFactory, $validatorFactory, $translator, $this->translationDomain, $this->initializers);
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace Symfony\Component\Validator;
|
||||
|
||||
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
|
||||
use Symfony\Component\Validator\Mapping\Cache\CacheInterface;
|
||||
use Symfony\Component\Translation\TranslatorInterface;
|
||||
use Doctrine\Common\Annotations\Reader;
|
||||
@ -159,6 +160,15 @@ interface ValidatorBuilderInterface
|
||||
*/
|
||||
public function setTranslationDomain($translationDomain);
|
||||
|
||||
/**
|
||||
* Sets the property accessor for resolving property paths.
|
||||
*
|
||||
* @param PropertyAccessorInterface $propertyAccessor The property accessor.
|
||||
*
|
||||
* @return ValidatorBuilderInterface The builder object.
|
||||
*/
|
||||
public function setPropertyAccessor(PropertyAccessorInterface $propertyAccessor);
|
||||
|
||||
/**
|
||||
* Builds and returns a new validator object.
|
||||
*
|
||||
|
@ -17,7 +17,8 @@
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3.3",
|
||||
"symfony/translation": "~2.0"
|
||||
"symfony/translation": "~2.0",
|
||||
"symfony/property-access": "~2.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/http-foundation": "~2.1",
|
||||
|
Reference in New Issue
Block a user