diff --git a/core/lib/Drupal/Core/TypedData/Validation/ConstraintViolationBuilder.php b/core/lib/Drupal/Core/TypedData/Validation/ConstraintViolationBuilder.php index 0091066..eb6d2ec 100644 --- a/core/lib/Drupal/Core/TypedData/Validation/ConstraintViolationBuilder.php +++ b/core/lib/Drupal/Core/TypedData/Validation/ConstraintViolationBuilder.php @@ -92,6 +92,28 @@ class ConstraintViolationBuilder implements ConstraintViolationBuilderInterface */ protected $cause; + /** + * Constructs a new ConstraintViolationBuilder instance. + * + * @param \Symfony\Component\Validator\ConstraintViolationList $violations + * The violation list. + * @param \Symfony\Component\Validator\Constraint $constraint + * The constraint. + * @param string $message + * The message. + * @param array $parameters + * The message parameters. + * @param mixed $root + * The root. + * @param string $propertyPath + * The property string. + * @param mixed $invalidValue + * The invalid value. + * @param \Symfony\Component\Translation\TranslatorInterface $translator + * The translator. + * @param null $translationDomain + * (optional) The translation domain. + */ public function __construct(ConstraintViolationList $violations, Constraint $constraint, $message, array $parameters, $root, $propertyPath, $invalidValue, TranslatorInterface $translator, $translationDomain = NULL) { $this->violations = $violations; $this->message = $message; diff --git a/core/lib/Drupal/Core/TypedData/Validation/ExecutionContext.php b/core/lib/Drupal/Core/TypedData/Validation/ExecutionContext.php index 020d205..f966810 100644 --- a/core/lib/Drupal/Core/TypedData/Validation/ExecutionContext.php +++ b/core/lib/Drupal/Core/TypedData/Validation/ExecutionContext.php @@ -108,16 +108,16 @@ class ExecutionContext implements ExecutionContextInterface { protected $validatedConstraints = array(); /** - * Creates a new execution context. + * Creates a new ExecutionContext. * * @param \Symfony\Component\Validator\Validator\ValidatorInterface $validator * The validator. * @param mixed $root - * The root value of the validated object graph - * @param TranslatorInterface $translator The translator - * @param string|null $translationDomain The translation domain to - * use for translating - * violation messages + * The root. + * @param \Symfony\Component\Translation\TranslatorInterface $translator + * The translator. + * @param string $translationDomain + * (optional) The translation domain. * * @internal Called by {@link ExecutionContextFactory}. Should not be used * in user code. diff --git a/core/lib/Drupal/Core/TypedData/Validation/ExecutionContextFactory.php b/core/lib/Drupal/Core/TypedData/Validation/ExecutionContextFactory.php index 2eda012..a032c1e 100644 --- a/core/lib/Drupal/Core/TypedData/Validation/ExecutionContextFactory.php +++ b/core/lib/Drupal/Core/TypedData/Validation/ExecutionContextFactory.php @@ -29,12 +29,12 @@ class ExecutionContextFactory implements ExecutionContextFactoryInterface { protected $translationDomain; /** - * Creates a new context factory. + * Constructs a new ExecutionContextFactory instance. * - * @param TranslatorInterface $translator The translator - * @param string|null $translationDomain The translation domain to - * use for translating - * violation messages + * @param \Symfony\Component\Translation\TranslatorInterface $translator + * The translator instance. + * @param string $translationDomain + * (optional) The translation domain. */ public function __construct(TranslatorInterface $translator, $translationDomain = NULL) { $this->translator = $translator; diff --git a/core/lib/Drupal/Core/TypedData/Validation/RecursiveContextualValidator.php b/core/lib/Drupal/Core/TypedData/Validation/RecursiveContextualValidator.php index 60cb26d..68c582c 100644 --- a/core/lib/Drupal/Core/TypedData/Validation/RecursiveContextualValidator.php +++ b/core/lib/Drupal/Core/TypedData/Validation/RecursiveContextualValidator.php @@ -15,7 +15,6 @@ use Symfony\Component\Validator\ConstraintValidatorFactoryInterface; use Symfony\Component\Validator\Context\ExecutionContextInterface; use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface; -use Symfony\Component\Validator\Validator\ContextualValidatorInterface; /** * Defines a recursive contextual validator for Typed Data. @@ -75,7 +74,7 @@ public function atPath($path) { /** * {@inheritdoc} */ - public function validate($data, $constraints = NULL, $groups = NULL, $root_call = TRUE) { + public function validate($data, $constraints = NULL, $groups = NULL, $is_root_call = TRUE) { if (isset($groups)) { throw new \LogicException('Passing custom groups is not supported.'); } @@ -90,7 +89,7 @@ public function validate($data, $constraints = NULL, $groups = NULL, $root_call $constraints = array($constraints); } - $this->validateNode($data, $constraints, $root_call); + $this->validateNode($data, $constraints, $is_root_call); return $this; } @@ -106,12 +105,12 @@ public function validate($data, $constraints = NULL, $groups = NULL, $root_call * The data to validated. * @param \Symfony\Component\Validator\Constraint[]|null $constraints * (optional) If set, an array of constraints to validate. - * @param bool $root_call + * @param bool $is_root_call * (optional) Whether its the most upper call in the type data tree. * * @return $this */ - protected function validateNode(TypedDataInterface $data, $constraints = NULL, $root_call = FALSE) { + protected function validateNode(TypedDataInterface $data, $constraints = NULL, $is_root_call = FALSE) { $previous_value = $this->context->getValue(); $previous_object = $this->context->getObject(); $previous_metadata = $this->context->getMetadata(); @@ -119,7 +118,7 @@ protected function validateNode(TypedDataInterface $data, $constraints = NULL, $ $metadata = $this->metadataFactory->getMetadataFor($data); $cache_key = spl_object_hash($data); - $property_path = $root_call ? '' : PropertyPath::append($previous_path, $data->getName()); + $property_path = $is_root_call ? '' : PropertyPath::append($previous_path, $data->getName()); // Pass the canonical representation of the data as validated value to // constraint validators, such that they do not have to care about Typed // Data. @@ -192,10 +191,13 @@ public function validateProperty($object, $propertyName, $groups = NULL) { if (!is_object($object)) { throw new \InvalidArgumentException('Passing class name is not supported.'); } + elseif (!$object instanceof TypedDataInterface) { + throw new \InvalidArgumentException('The passed in object has to be typed data.'); + } elseif (!$object instanceof ListInterface && !$object instanceof ComplexDataInterface) { throw new \InvalidArgumentException('Passed data does not contain properties.'); } - return $this->validateNode($object->get($propertyName)); + return $this->validateNode($object->get($propertyName), NULL, TRUE); } /** @@ -205,12 +207,16 @@ public function validatePropertyValue($object, $property_name, $value, $groups = if (!is_object($object)) { throw new \InvalidArgumentException('Passing class name is not supported.'); } + elseif (!$object instanceof TypedDataInterface) { + throw new \InvalidArgumentException('The passed in object has to be typed data.'); + } elseif (!$object instanceof ListInterface && !$object instanceof ComplexDataInterface) { throw new \InvalidArgumentException('Passed data does not contain properties.'); } $data = $object->get($property_name); - $constraints = $data->getDataDefinition()->getConstraints(); - return $this->validate($value, $constraints, $groups); + $metadata = $this->metadataFactory->getMetadataFor($data); + $constraints = $metadata->findConstraints(Constraint::DEFAULT_GROUP); + return $this->validate($value, $constraints, $groups, TRUE); } } diff --git a/core/tests/Drupal/Tests/Core/TypedData/RecursiveContextualValidatorTest.php b/core/tests/Drupal/Tests/Core/TypedData/RecursiveContextualValidatorTest.php index 0f11114..36b0b53 100644 --- a/core/tests/Drupal/Tests/Core/TypedData/RecursiveContextualValidatorTest.php +++ b/core/tests/Drupal/Tests/Core/TypedData/RecursiveContextualValidatorTest.php @@ -113,7 +113,7 @@ public function testValidateWithoutTypedData() { public function testBasicValidateWithoutConstraints() { $typed_data = $this->typedDataManager->create(DataDefinition::create('string')); $violations = $this->recursiveValidator->validate($typed_data); - $this->assertEquals(0, $violations->count()); + $this->assertCount(0, $violations); } /** @@ -131,7 +131,7 @@ public function testBasicValidateWithConstraint() { $typed_data->setValue('foo'); $violations = $this->recursiveValidator->validate($typed_data); - $this->assertEquals(1, $violations->count()); + $this->assertCount(1, $violations); // Ensure that the right value is passed into the validator. $this->assertEquals('test violation: foo', $violations->get(0)->getMessage()); } @@ -151,7 +151,7 @@ public function testBasicValidateWithMultipleConstraints() { ->addConstraint('NotNull') ); $violations = $this->recursiveValidator->validate($typed_data); - $this->assertEquals(2, $violations->count()); + $this->assertCount(2, $violations); } /** @@ -159,30 +159,10 @@ public function testBasicValidateWithMultipleConstraints() { */ public function testPropertiesValidateWithMultipleLevels() { - $tree = ['value' => ['key1' => 'value1', 'key2' => 'value2', 'key_with_properties' => ['subkey1' => 'subvalue1', 'subkey2' => 'subvalue2']]]; - $tree['properties'] = [ - 'key1' => [ - 'value' => 'value1', - ], - 'key2' => [ - 'value' => 'value2', - ], - 'key_with_properties' => [ - 'value' => ['subkey1' => 'subvalue1', 'subkey2' => 'subvalue2'], - 'properties' => [ - 'subkey1' => [ - 'value' => 'subvalue1', - ], - 'subkey2' => [ - 'value' => 'subvalue2', - ], - ] - ], - ]; + $typed_data = $this->buildExampleTypedDataWithProperties(); - $typed_data = $this->setupTypedData($tree, 'test_name'); $violations = $this->recursiveValidator->validate($typed_data); - $this->assertEquals(6, $violations->count()); + $this->assertCount(6, $violations); $this->assertEquals('violation: 3', $violations->get(0)->getMessage()); $this->assertEquals('violation: value1', $violations->get(1)->getMessage()); @@ -240,4 +220,128 @@ protected function setupTypedData(array $tree, $name = '') { return $typed_data; } + /** + * @covers ::validateProperty + * + * @expectedException \LogicException + */ + public function testValidatePropertyWithCustomGroup() { + $tree = [ + 'value' => [], + 'properties' => [ + 'key1' => ['value' => 'value1'], + ], + ]; + $typed_data = $this->setupTypedData($tree, 'test_name'); + $this->recursiveValidator->validateProperty($typed_data, 'key1', 'test group'); + } + + /** + * @covers ::validateProperty + * + * @dataProvider providerTestValidatePropertyWithInvalidObjects + * + * @expectedException \InvalidArgumentException + */ + public function testValidatePropertyWithInvalidObjects($object) { + $this->recursiveValidator->validateProperty($object, 'key1', NULL); + } + + /** + * Provides data for testValidatePropertyWithInvalidObjects. + * @return array + */ + public function providerTestValidatePropertyWithInvalidObjects() { + $data = []; + $data[] = [new \stdClass()]; + $data[] = [new TestClass()]; + + $data[] = [$this->getMock('Drupal\Core\TypedData\TypedDataInterface')]; + + return $data; + } + + /** + * @covers ::validateProperty + */ + public function testValidateProperty() { + $typed_data = $this->buildExampleTypedDataWithProperties(); + + $violations = $this->recursiveValidator->validateProperty($typed_data, 'key_with_properties'); + $this->assertCount(3, $violations); + + $this->assertEquals('violation: 2', $violations->get(0)->getMessage()); + $this->assertEquals('violation: subvalue1', $violations->get(1)->getMessage()); + $this->assertEquals('violation: subvalue2', $violations->get(2)->getMessage()); + + $this->assertEquals('', $violations->get(0)->getPropertyPath()); + $this->assertEquals('subkey1', $violations->get(1)->getPropertyPath()); + $this->assertEquals('subkey2', $violations->get(2)->getPropertyPath()); + } + + /** + * @covers ::validatePropertyValue + * + * @dataProvider providerTestValidatePropertyWithInvalidObjects + * + * @expectedException \InvalidArgumentException + */ + public function testValidatePropertyValueWithInvalidObjects($object) { + $this->recursiveValidator->validatePropertyValue($object, 'key1', [], NULL); + } + + /** + * @covers ::validatePropertyValue + */ + public function testValidatePropertyValue() { + $typed_data = $this->buildExampleTypedDataWithProperties(['subkey1' => 'subvalue11', 'subkey2' => 'subvalue22']); + + $violations = $this->recursiveValidator->validatePropertyValue($typed_data, 'key_with_properties', $typed_data->get('key_with_properties')); + $this->assertCount(3, $violations); + + $this->assertEquals('violation: 2', $violations->get(0)->getMessage()); + $this->assertEquals('violation: subvalue11', $violations->get(1)->getMessage()); + $this->assertEquals('violation: subvalue22', $violations->get(2)->getMessage()); + + $this->assertEquals('', $violations->get(0)->getPropertyPath()); + $this->assertEquals('subkey1', $violations->get(1)->getPropertyPath()); + $this->assertEquals('subkey2', $violations->get(2)->getPropertyPath()); + } + + /** + * + * Builds some example type data object. + * + * @return \Drupal\Core\TypedData\TypedDataInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected function buildExampleTypedDataWithProperties($subkey_value = NULL) { + $subkey_value = $subkey_value ?: ['subkey1' => 'subvalue1', 'subkey2' => 'subvalue2']; + $tree = [ + 'value' => [ + 'key1' => 'value1', + 'key2' => 'value2', + 'key_with_properties' => $subkey_value + ], + ]; + $tree['properties'] = [ + 'key1' => [ + 'value' => 'value1', + ], + 'key2' => [ + 'value' => 'value2', + ], + 'key_with_properties' => [ + 'value' => $subkey_value ?: ['subkey1' => 'subvalue1', 'subkey2' => 'subvalue2'], + ], + ]; + $tree['properties']['key_with_properties']['properties']['subkey1'] = ['value' => $tree['properties']['key_with_properties']['value']['subkey1']]; + $tree['properties']['key_with_properties']['properties']['subkey2'] = ['value' => $tree['properties']['key_with_properties']['value']['subkey2']]; + + return $this->setupTypedData($tree, 'test_name'); + } + +} + +class TestClass { + }