Skip to content

Commit

Permalink
[Validator] Made ExecutionContext immutable and introduced new class …
Browse files Browse the repository at this point in the history
…GlobalExecutionContext

A new ExecutionContext is now created everytime that GraphWalker::walkConstraint() is
launched. Because of this, a validator B launched from within a validator A can't break
A anymore by changing the context.

Because we have a new ExecutionContext for every constraint validation, there is no point
in modifying its state anymore. Because of this it is now immutable.
  • Loading branch information
webmozart committed Jan 31, 2012
1 parent fe85bbd commit a30a679
Show file tree
Hide file tree
Showing 19 changed files with 557 additions and 386 deletions.
1 change: 1 addition & 0 deletions CHANGELOG-2.1.md
Expand Up @@ -299,6 +299,7 @@ To get the diff between two versions, go to https://github.com/symfony/symfony/c
individual fields
* changed default value for `extraFieldsMessage` and `missingFieldsMessage`
in Collection constraint
* made ExecutionContext immutable

### Yaml

Expand Down
Expand Up @@ -102,10 +102,7 @@ public function isValid($entity, Constraint $constraint)
return true;
}

$oldPath = $this->context->getPropertyPath();
$this->context->setPropertyPath( empty($oldPath) ? $fields[0] : $oldPath.'.'.$fields[0]);
$this->context->addViolation($constraint->message, array(), $criteria[$fields[0]]);
$this->context->setPropertyPath($oldPath);
$this->context->addNestedViolationAt($fields[0], $constraint->message, array(), $criteria[$fields[0]]);

return true; // all true, we added the violation already!
}
Expand Down
Expand Up @@ -111,10 +111,6 @@ static public function validateFormData(FormInterface $form, ExecutionContext $c
$propertyPath = $context->getPropertyPath();
$graphWalker = $context->getGraphWalker();

// The Execute constraint is called on class level, so we need to
// set the property manually
$context->setCurrentProperty('data');

// Adjust the property path accordingly
if (!empty($propertyPath)) {
$propertyPath .= '.';
Expand All @@ -134,10 +130,6 @@ static public function validateFormChildren(FormInterface $form, ExecutionContex
$propertyPath = $context->getPropertyPath();
$graphWalker = $context->getGraphWalker();

// The Execute constraint is called on class level, so we need to
// set the property manually
$context->setCurrentProperty('children');

// Adjust the property path accordingly
if (!empty($propertyPath)) {
$propertyPath .= '.';
Expand Down
6 changes: 3 additions & 3 deletions src/Symfony/Component/Validator/ConstraintViolationList.php
Expand Up @@ -78,13 +78,13 @@ public function add(ConstraintViolation $violation)
/**
* Merge an existing ConstraintViolationList into this list.
*
* @param ConstraintViolationList $violations
* @param ConstraintViolationList $otherList
*
* @api
*/
public function addAll(ConstraintViolationList $violations)
public function addAll(ConstraintViolationList $otherList)
{
foreach ($violations->violations as $violation) {
foreach ($otherList->violations as $violation) {
$this->violations[] = $violation;
}
}
Expand Down
17 changes: 2 additions & 15 deletions src/Symfony/Component/Validator/Constraints/CallbackValidator.php
Expand Up @@ -48,34 +48,21 @@ public function isValid($object, Constraint $constraint)
}

$methods = $constraint->methods;
$context = $this->context;

// save context state
$currentClass = $context->getCurrentClass();
$currentProperty = $context->getCurrentProperty();
$group = $context->getGroup();
$propertyPath = $context->getPropertyPath();

foreach ($methods as $method) {
if (is_array($method) || $method instanceof \Closure) {
if (!is_callable($method)) {
throw new ConstraintDefinitionException(sprintf('"%s::%s" targeted by Callback constraint is not a valid callable', $method[0], $method[1]));
}

call_user_func($method, $object, $context);
call_user_func($method, $object, $this->context);
} else {
if (!method_exists($object, $method)) {
throw new ConstraintDefinitionException(sprintf('Method "%s" targeted by Callback constraint does not exist', $method));
}

$object->$method($context);
$object->$method($this->context);
}

// restore context state
$context->setCurrentClass($currentClass);
$context->setCurrentProperty($currentProperty);
$context->setGroup($group);
$context->setPropertyPath($propertyPath);
}

return true;
Expand Down
Expand Up @@ -68,7 +68,7 @@ public function isValid($value, Constraint $constraint)
$walker->walkConstraint($constr, $value[$field], $group, $propertyPath.'['.$field.']');
}
} elseif (!$fieldConstraint instanceof Optional && !$constraint->allowMissingFields) {
$this->context->addViolationAt($propertyPath.'['.$field.']', $constraint->missingFieldsMessage, array(
$this->context->addNestedViolationAt('['.$field.']', $constraint->missingFieldsMessage, array(
'{{ field }}' => '"'.$field.'"'
), null);
$valid = false;
Expand All @@ -78,7 +78,7 @@ public function isValid($value, Constraint $constraint)
if (!$constraint->allowExtraFields) {
foreach ($value as $field => $fieldValue) {
if (!isset($constraint->fields[$field])) {
$this->context->addViolationAt($propertyPath.'['.$field.']', $constraint->extraFieldsMessage, array(
$this->context->addNestedViolationAt('['.$field.']', $constraint->extraFieldsMessage, array(
'{{ field }}' => '"'.$field.'"'
), $fieldValue);
$valid = false;
Expand Down
124 changes: 69 additions & 55 deletions src/Symfony/Component/Validator/ExecutionContext.php
Expand Up @@ -16,8 +16,10 @@
/**
* Stores the state of the current node in the validation graph.
*
* This object is used by the GraphWalker to initialize validation of different
* items and keep track of the violations.
* This class is immutable by design.
*
* It is used by the GraphWalker to initialize validation of different items
* and keep track of the violations.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Bernhard Schussek <bschussek@gmail.com>
Expand All @@ -26,120 +28,132 @@
*/
class ExecutionContext
{
protected $root;
protected $propertyPath;
protected $class;
protected $property;
protected $value;
protected $group;
protected $violations;
protected $graphWalker;
protected $metadataFactory;

public function __construct(
$root,
GraphWalker $graphWalker,
ClassMetadataFactoryInterface $metadataFactory
)
private $globalContext;
private $propertyPath;
private $value;
private $group;
private $class;
private $property;

public function __construct(GlobalExecutionContext $globalContext, $value, $propertyPath, $group, $class = null, $property = null)
{
$this->root = $root;
$this->graphWalker = $graphWalker;
$this->metadataFactory = $metadataFactory;
$this->violations = new ConstraintViolationList();
$this->globalContext = $globalContext;
$this->value = $value;
$this->propertyPath = $propertyPath;
$this->group = $group;
$this->class = $class;
$this->property = $property;
}

public function __clone()
{
$this->violations = clone $this->violations;
$this->globalContext = clone $this->globalContext;
}

/**
* Adds a violation at the current node of the validation graph.
*
* @param string $message The error message.
* @param array $params The parameters parsed into the error message.
* @param mixed $invalidValue The invalid, validated value.
*
* @api
*/
public function addViolation($message, array $params = array(), $invalidValue = null)
{
$this->violations->add(new ConstraintViolation(
$this->globalContext->addViolation(new ConstraintViolation(
$message,
$params,
$this->root,
$this->globalContext->getRoot(),
$this->propertyPath,
// check using func_num_args() to allow passing null values
func_num_args() === 3 ? $invalidValue : $this->value
));
}

/**
* Adds a violation at the validation graph node with the given property
* path.
*
* @param string $propertyPath The property path for the violation.
* @param string $message The error message.
* @param array $params The parameters parsed into the error message.
* @param mixed $invalidValue The invalid, validated value.
*/
public function addViolationAt($propertyPath, $message, array $params = array(), $invalidValue = null)
{
$this->violations->add(new ConstraintViolation(
$this->globalContext->addViolation(new ConstraintViolation(
$message,
$params,
$this->root,
$this->globalContext->getRoot(),
$propertyPath,
// check using func_num_args() to allow passing null values
func_num_args() === 4 ? $invalidValue : $this->value
));
}

/**
* Adds a violation at the child of the current validation graph node with
* the given property path.
*
* @param string $childPropertyPath The property path of the child node.
* @param string $message The error message.
* @param array $params The parameters parsed into the error message.
* @param mixed $invalidValue The invalid, validated value.
*/
public function addNestedViolationAt($childPropertyPath, $message, array $params = array(), $invalidValue = null)
{
$propertyPath = $this->propertyPath;

if ('' !== $propertyPath && '' !== $childPropertyPath && '[' !== $childPropertyPath[0]) {
$propertyPath .= '.';
}

$this->globalContext->addViolation(new ConstraintViolation(
$message,
$params,
$this->globalContext->getRoot(),
$propertyPath . $childPropertyPath,
// check using func_num_args() to allow passing null values
func_num_args() === 4 ? $invalidValue : $this->value
));
}

/**
* @return ConstraintViolationList
*
* @api
*/
public function getViolations()
{
return $this->violations;
return $this->globalContext->getViolations();
}

public function getRoot()
{
return $this->root;
}

public function setPropertyPath($propertyPath)
{
$this->propertyPath = $propertyPath;
return $this->globalContext->getRoot();
}

public function getPropertyPath()
{
return $this->propertyPath;
}

public function setCurrentClass($class)
{
$this->class = $class;
}

public function getCurrentClass()
{
return $this->class;
}

public function setCurrentProperty($property)
{
$this->property = $property;
}

public function getCurrentProperty()
{
return $this->property;
}

public function setCurrentValue($value)
{
$this->value = $value;
}

public function getCurrentValue()
{
return $this->value;
}

public function setGroup($group)
{
$this->group = $group;
}

public function getGroup()
{
return $this->group;
Expand All @@ -150,14 +164,14 @@ public function getGroup()
*/
public function getGraphWalker()
{
return $this->graphWalker;
return $this->globalContext->getGraphWalker();
}

/**
* @return ClassMetadataFactoryInterface
*/
public function getMetadataFactory()
{
return $this->metadataFactory;
return $this->globalContext->getMetadataFactory();
}
}

0 comments on commit a30a679

Please sign in to comment.