diff --git a/core/lib/Drupal/Core/TypedData/Validation/RecursiveContextualValidator.php b/core/lib/Drupal/Core/TypedData/Validation/RecursiveContextualValidator.php index 22bc398..c42d152 100644 --- a/core/lib/Drupal/Core/TypedData/Validation/RecursiveContextualValidator.php +++ b/core/lib/Drupal/Core/TypedData/Validation/RecursiveContextualValidator.php @@ -69,7 +69,7 @@ public function atPath($path) { /** * {@inheritdoc} */ - public function validate($data, $constraints = NULL, $groups = NULL) { + public function validate($data, $constraints = NULL, $groups = NULL, $root_call = TRUE) { if (isset($groups)) { throw new \LogicException('Passing custom groups is not supported.'); } @@ -84,7 +84,7 @@ public function validate($data, $constraints = NULL, $groups = NULL) { $constraints = array($constraints); } - $this->validateNode($data, $constraints); + $this->validateNode($data, $constraints, $root_call); return $this; } @@ -100,10 +100,12 @@ public function validate($data, $constraints = NULL, $groups = NULL) { * The data to validated. * @param \Symfony\Component\Validator\Constraint[]|null $constraints * (optional) If set, an array of constraints to validate. + * @param bool $root_call + * (optional) Whether its the most upper call in the type data tree. * * @return $this */ - protected function validateNode(TypedDataInterface $data, $constraints = NULL) { + protected function validateNode(TypedDataInterface $data, $constraints = NULL, $root_call = FALSE) { $previous_value = $this->context->getValue(); $previous_object = $this->context->getObject(); $previous_metadata = $this->context->getMetadata(); @@ -111,7 +113,7 @@ protected function validateNode(TypedDataInterface $data, $constraints = NULL) { $metadata = $this->metadataFactory->getMetadataFor($data); $cache_key = spl_object_hash($data); - $property_path = PropertyPath::append($previous_path, $data->getName()); + $property_path = $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. diff --git a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/ComplexDataConstraintValidator.php b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/ComplexDataConstraintValidator.php index b29a231..8398ae8 100755 --- a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/ComplexDataConstraintValidator.php +++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/ComplexDataConstraintValidator.php @@ -38,7 +38,7 @@ public function validate($data, Constraint $constraint) { foreach ($constraint->properties as $name => $constraints) { $this->context->getValidator() ->inContext($this->context) - ->validate($data->get($name), $constraints); + ->validate($data->get($name), $constraints, NULL, FALSE); } } diff --git a/core/tests/Drupal/Tests/Core/TypedData/RecursiveContextualValidatorTest.php b/core/tests/Drupal/Tests/Core/TypedData/RecursiveContextualValidatorTest.php index 4d75cdc..5befa85 100644 --- a/core/tests/Drupal/Tests/Core/TypedData/RecursiveContextualValidatorTest.php +++ b/core/tests/Drupal/Tests/Core/TypedData/RecursiveContextualValidatorTest.php @@ -10,6 +10,7 @@ use Drupal\Core\Cache\NullBackend; use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\TypedData\DataDefinition; +use Drupal\Core\TypedData\MapDataDefinition; use Drupal\Core\TypedData\TraversableTypedDataInterface; use Drupal\Core\TypedData\TypedDataInterface; use Drupal\Core\TypedData\TypedDataManager; @@ -152,54 +153,46 @@ public function testBasicValidateWithMultipleConstraints() { * @covers ::validate * @todo: Fix. */ - public function SKIPtestPropertiesValidateWithMultipleLevels() { - $callback_constraint = new Callback([ - 'callback' => function (TypedDataInterface $value, ExecutionContextInterface $context) { - $context->addViolation('violation: ' . $value->getValue()); - } - ]); + public function testPropertiesValidateWithMultipleLevels() { - $tree = ['value' => []]; + $tree = ['value' => ['key1' => 'value1', 'key2' => 'value2', 'key_with_properties' => ['subkey1' => 'subvalue1', 'subkey2' => 'subvalue2']]]; $tree['properties'] = [ 'key1' => [ 'value' => 'value1', - 'constraints' => [clone $callback_constraint], ], 'key2' => [ 'value' => 'value2', - 'constraints' => [clone $callback_constraint] ], 'key_with_properties' => [ - 'value' => 'value_with_properties', - 'constraints' => [clone $callback_constraint], + 'value' => ['subkey1' => 'subvalue1', 'subkey2' => 'subvalue2'], 'properties' => [ 'subkey1' => [ 'value' => 'subvalue1', - 'constraints' => [clone $callback_constraint] ], 'subkey2' => [ 'value' => 'subvalue2', - 'constraints' => [clone $callback_constraint] ], ] ], ]; - $typed_data = $this->setupTypedData($tree); + $typed_data = $this->setupTypedData($tree, 'test_name'); $violations = $this->recursiveValidator->validate($typed_data); - $this->assertEquals(5, $violations->count()); - - $this->assertEquals('violation: value1', $violations->get(0)->getMessage()); - $this->assertEquals('violation: value2', $violations->get(1)->getMessage()); - $this->assertEquals('violation: value_with_properties', $violations->get(2)->getMessage()); - $this->assertEquals('violation: subvalue1', $violations->get(3)->getMessage()); - $this->assertEquals('violation: subvalue2', $violations->get(4)->getMessage()); - - $this->assertEquals('key1', $violations->get(0)->getPropertyPath()); - $this->assertEquals('key2', $violations->get(1)->getPropertyPath()); - $this->assertEquals('key_with_properties', $violations->get(2)->getPropertyPath()); - $this->assertEquals('key_with_properties.subkey1', $violations->get(3)->getPropertyPath()); - $this->assertEquals('key_with_properties.subkey2', $violations->get(4)->getPropertyPath()); + $this->assertEquals(6, $violations->count()); + + $this->assertEquals('violation: 3', $violations->get(0)->getMessage()); + $this->assertEquals('violation: value1', $violations->get(1)->getMessage()); + $this->assertEquals('violation: value2', $violations->get(2)->getMessage()); + $this->assertEquals('violation: 2', $violations->get(3)->getMessage()); + $this->assertEquals('violation: subvalue1', $violations->get(4)->getMessage()); + $this->assertEquals('violation: subvalue2', $violations->get(5)->getMessage()); + + $this->assertEquals('', $violations->get(0)->getPropertyPath()); + $this->assertEquals('key1', $violations->get(1)->getPropertyPath()); + $this->assertEquals('key2', $violations->get(2)->getPropertyPath()); + $this->assertEquals('key_with_properties', $violations->get(3)->getPropertyPath()); + $this->assertEquals('key_with_properties.subkey1', $violations->get(4)->getPropertyPath()); + $this->assertEquals('key_with_properties.subkey2', $violations->get(5)->getPropertyPath()); } /** @@ -211,33 +204,35 @@ public function SKIPtestPropertiesValidateWithMultipleLevels() { * @return \Drupal\Core\TypedData\TypedDataInterface|\PHPUnit_Framework_MockObject_MockObject */ protected function setupTypedData(array $tree, $name = '') { + $callback = function ($value, ExecutionContextInterface $context) { + $context->addViolation('violation: ' . (is_array($value) ? count($value) : $value)); + }; + $tree += ['constraints' => []]; if (isset($tree['properties'])) { - $properties = []; + $map_data_definition = MapDataDefinition::create(); + $map_data_definition->addConstraint('Callback', ['callback' => $callback]); foreach ($tree['properties'] as $property_name => $property) { $sub_typed_data = $this->setupTypedData($property, $property_name); - $properties[$property_name] = $sub_typed_data; + $map_data_definition->setPropertyDefinition($property_name, $sub_typed_data->getDataDefinition()); } - $typed_data = $this->getMock('\Drupal\Tests\Core\TypedData\TraversableTypedDataInterfaceWithIterator'); - $typed_data->expects($this->any()) - ->method('getIterator') - ->willReturn(new \ArrayIterator($properties)); + $typed_data = $this->typedDataManager->create( + $map_data_definition, + $tree['value'], + $name + ); } else { - $typed_data = $this->getMock('\Drupal\Core\TypedData\TypedDataInterface'); + /** @var \Drupal\Core\TypedData\TypedDataInterface $typed_data */ + $typed_data = $this->typedDataManager->create( + DataDefinition::create('string') + ->addConstraint('Callback', ['callback' => $callback]), + $tree['value'], + $name + ); } - $typed_data->expects($this->any()) - ->method('getValue') - ->willReturn($tree['value']); - $typed_data->expects($this->any()) - ->method('getConstraints') - ->willReturn($tree['constraints']); - $typed_data->expects($this->any()) - ->method('getName') - ->willReturn($name); - return $typed_data; }