diff --git a/core/lib/Drupal/Core/CoreBundle.php b/core/lib/Drupal/Core/CoreBundle.php index f1d0046..1771d82 100644 --- a/core/lib/Drupal/Core/CoreBundle.php +++ b/core/lib/Drupal/Core/CoreBundle.php @@ -54,6 +54,8 @@ public function build(ContainerBuilder $container) { ->setFactoryMethod('getConnection') ->addArgument('slave'); $container->register('typed_data', 'Drupal\Core\TypedData\TypedDataManager'); + $container->register('constraint', 'Drupal\Core\Validation\Constraint\ConstraintManager'); + // Add the user's storage for temporary, non-cache data. $container->register('lock', 'Drupal\Core\Lock\DatabaseLockBackend'); $container->register('user.tempstore', 'Drupal\user\TempStoreFactory') diff --git a/core/lib/Drupal/Core/Entity/EntityNG.php b/core/lib/Drupal/Core/Entity/EntityNG.php index 35ef89d..e7ea7dc 100644 --- a/core/lib/Drupal/Core/Entity/EntityNG.php +++ b/core/lib/Drupal/Core/Entity/EntityNG.php @@ -413,4 +413,31 @@ public function __clone() { } } } + + /** + * Validates the entity. + * + * @return array + * Array containing all violations + * + * @see Drupal\Core\Validation\Violation\Violation. + */ + public function validate() { + $violations = array(); + + // Loop over all properties + foreach ($this->fields as $name => $properties) { + foreach ($properties as $langcode => $property) { + $violations += $property->validate(); + } + } + + // Validate entity global constraints. + // if ($this->constraints) { + // foreach ($this->constraints as $constraint) { + // $violations[] = $constraint->validate($this); + // } + // } + return $violations; + } } diff --git a/core/lib/Drupal/Core/Entity/Field/FieldItemBase.php b/core/lib/Drupal/Core/Entity/Field/FieldItemBase.php index 5c71aa3..80e0848 100644 --- a/core/lib/Drupal/Core/Entity/Field/FieldItemBase.php +++ b/core/lib/Drupal/Core/Entity/Field/FieldItemBase.php @@ -124,7 +124,7 @@ public function getString() { * Implements TypedDataInterface::validate(). */ public function validate() { - // @todo implement + return parent::validate(); } /** diff --git a/core/lib/Drupal/Core/Entity/Field/Type/EntityReferenceItem.php b/core/lib/Drupal/Core/Entity/Field/Type/EntityReferenceItem.php index a60e65e..011cd33 100644 --- a/core/lib/Drupal/Core/Entity/Field/Type/EntityReferenceItem.php +++ b/core/lib/Drupal/Core/Entity/Field/Type/EntityReferenceItem.php @@ -39,6 +39,10 @@ public function getPropertyDefinitions() { // @todo: Lookup the entity type's ID data type and use it here. 'type' => 'integer', 'label' => t('Entity ID'), + 'constraints' => array( + 'nonnull' => TRUE, + 'min' => 9999, + ), ); self::$propertyDefinitions[$entity_type]['entity'] = array( 'type' => 'entity', diff --git a/core/lib/Drupal/Core/Entity/Field/Type/Field.php b/core/lib/Drupal/Core/Entity/Field/Type/Field.php index e7a5960..4da1ac4 100644 --- a/core/lib/Drupal/Core/Entity/Field/Type/Field.php +++ b/core/lib/Drupal/Core/Entity/Field/Type/Field.php @@ -117,7 +117,16 @@ public function getString() { * Implements TypedDataInterface::validate(). */ public function validate() { - // @todo implement + $violations = array(); + + // Iterate and use TypedData::validate(). + foreach ($this->getIterator() as $delta => $item) { + $violations += $item->validate(); + } + + // @todo: Do validation on field level. + + return $violations; } /** diff --git a/core/lib/Drupal/Core/Entity/Field/Type/IntegerItem.php b/core/lib/Drupal/Core/Entity/Field/Type/IntegerItem.php index 1f4b4e6..3830711 100644 --- a/core/lib/Drupal/Core/Entity/Field/Type/IntegerItem.php +++ b/core/lib/Drupal/Core/Entity/Field/Type/IntegerItem.php @@ -32,6 +32,10 @@ public function getPropertyDefinitions() { self::$propertyDefinitions['value'] = array( 'type' => 'integer', 'label' => t('Integer value'), + 'constraints' => array( + 'nonnull' => TRUE, + 'min' => 0, + ), ); } return self::$propertyDefinitions; diff --git a/core/lib/Drupal/Core/Plugin/Discovery/AnnotatedClassDiscovery.php b/core/lib/Drupal/Core/Plugin/Discovery/AnnotatedClassDiscovery.php index bd89a92..144eda0 100644 --- a/core/lib/Drupal/Core/Plugin/Discovery/AnnotatedClassDiscovery.php +++ b/core/lib/Drupal/Core/Plugin/Discovery/AnnotatedClassDiscovery.php @@ -21,6 +21,39 @@ class AnnotatedClassDiscovery implements DiscoveryInterface { /** + * @var array $custom_location + * + * key: namespace of you class. + * value is an array containing: + * dir: the directory to scan. + * class_base: namespace of you class. + */ + protected $custom_location = array(); + + /** + * Sets the custom location(s) to scan. + * + * @param array $custom_locations + * + * key: namespace of you class. + * value is an array containing: + * dir: the directory to scan. + * class_base: namespace of you class. + */ + public function setCustomLocation(array $custom_locations) { + $this->custom_location = $custom_locations; + } + + /** + * Gets the custom locations. + * + * @return array + */ + public function getCustomLocation() { + return $this->custom_location; + } + + /** * Constructs an AnnotatedClassDiscovery object. */ function __construct($owner, $type) { @@ -49,33 +82,42 @@ public function getDefinitions() { AnnotationRegistry::registerAutoloadNamespace('Drupal\Core\Annotation', array(DRUPAL_ROOT . '/core/lib')); // Get all PSR-0 namespaces. $namespaces = drupal_classloader()->getNamespaces(); + // Rewrite all directories. foreach ($namespaces as $ns => $namespace_dirs) { - // OS-Safe directory separators. $ns = str_replace('\\', DIRECTORY_SEPARATOR, $ns); + // Check for the pre-determined directory structure to find plugins. + $prefix = implode(DIRECTORY_SEPARATOR, array( + $ns, + 'Plugin', + $this->owner, + $this->type + )); + foreach ($namespace_dirs as $key => $dir) { + $namespaces[$ns][$key] = array( + 'dir' => $dir .= DIRECTORY_SEPARATOR . $prefix, + 'class_base' => str_replace( + DIRECTORY_SEPARATOR, + '\\', + $prefix + ), + ); + } + } + // Merge in the custom locations. + if (!empty($this->custom_location)) { + $namespaces += $this->custom_location; + } - foreach ($namespace_dirs as $dir) { - // Check for the pre-determined directory structure to find plugins. - $prefix = implode(DIRECTORY_SEPARATOR, array( - $ns, - 'Plugin', - $this->owner, - $this->type - )); - $dir .= DIRECTORY_SEPARATOR . $prefix; - - // If the directory structure exists, look for classes. + foreach ($namespaces as $ns => $namespace_dirs) { + foreach ($namespace_dirs as $dir_info) { + $dir = $dir_info['dir']; if (file_exists($dir)) { $directories = new DirectoryIterator($dir); foreach ($directories as $fileinfo) { // @todo Once core requires 5.3.6, use $fileinfo->getExtension(). if (pathinfo($fileinfo->getFilename(), PATHINFO_EXTENSION) == 'php') { - $class = str_replace( - DIRECTORY_SEPARATOR, - '\\', - $prefix . DIRECTORY_SEPARATOR . $fileinfo->getBasename('.php') - ); - + $class = $dir_info['class_base'] . '\\' . $fileinfo->getBasename('.php'); // The filename is already known, so there is no need to find the // file. However, StaticReflectionParser needs a finder, so use a // mock version. diff --git a/core/lib/Drupal/Core/TypedData/Type/Integer.php b/core/lib/Drupal/Core/TypedData/Type/Integer.php index 4e9b59a..086624c 100644 --- a/core/lib/Drupal/Core/TypedData/Type/Integer.php +++ b/core/lib/Drupal/Core/TypedData/Type/Integer.php @@ -35,6 +35,6 @@ public function setValue($value) { * Implements TypedDataInterface::validate(). */ public function validate() { - // TODO: Implement validate() method. + return parent::validate(); } } diff --git a/core/lib/Drupal/Core/TypedData/Type/TypedData.php b/core/lib/Drupal/Core/TypedData/Type/TypedData.php index 1e70c53..dda8b0d 100644 --- a/core/lib/Drupal/Core/TypedData/Type/TypedData.php +++ b/core/lib/Drupal/Core/TypedData/Type/TypedData.php @@ -70,4 +70,25 @@ public function setValue($value) { public function getString() { return (string) $this->getValue(); } + + /** + * Implements TypedDataInterface::validate(). + */ + public function validate() { + $violations = array(); + $property_definition = $this->getPropertyDefinitions(); + if (isset($property_definition['value']['constraints'])) { + $constraints = $property_definition['value']['constraints']; + foreach ($constraints as $constraint => $constraint_info) { + if ($constraint = drupal_container()->get('constraint')->createInstance($constraint, (array)$constraint_info)) { + // @todo: is there are better way for $this->value['value']? + if (!$constraint->validate($this->value['value'], 'typedata validate value')) { + $violations[] = 'oops'; + } + } + } + } + return $violations; + } + } diff --git a/core/lib/Drupal/Core/Validation/Constraint/Constraint.php b/core/lib/Drupal/Core/Validation/Constraint/Constraint.php new file mode 100644 index 0000000..1dd2bd3 --- /dev/null +++ b/core/lib/Drupal/Core/Validation/Constraint/Constraint.php @@ -0,0 +1,28 @@ +settings = $settings; + } + + public function setSettings($settings) { + $this->settings = $settings; + } +} \ No newline at end of file diff --git a/core/lib/Drupal/Core/Validation/Constraint/ConstraintBundle.php b/core/lib/Drupal/Core/Validation/Constraint/ConstraintBundle.php new file mode 100644 index 0000000..ddd04a1 --- /dev/null +++ b/core/lib/Drupal/Core/Validation/Constraint/ConstraintBundle.php @@ -0,0 +1,25 @@ +register('plugin.manager.constraint', 'Drupal\Core\Validation\Constraint\ConstraintManager'); + } +} diff --git a/core/lib/Drupal/Core/Validation/Constraint/ConstraintFactory.php b/core/lib/Drupal/Core/Validation/Constraint/ConstraintFactory.php new file mode 100644 index 0000000..89b354d --- /dev/null +++ b/core/lib/Drupal/Core/Validation/Constraint/ConstraintFactory.php @@ -0,0 +1,35 @@ +discovery->getDefinition($plugin_id)) { + $plugin_class = $definition['class']; + return new $plugin_class($configuration); + } + } +} diff --git a/core/lib/Drupal/Core/Validation/Constraint/ConstraintInterface.php b/core/lib/Drupal/Core/Validation/Constraint/ConstraintInterface.php new file mode 100644 index 0000000..5a793bb --- /dev/null +++ b/core/lib/Drupal/Core/Validation/Constraint/ConstraintInterface.php @@ -0,0 +1,26 @@ +discovery = new AnnotatedClassDiscovery('Validation', 'Constraint'); + $this->discovery->setCustomLocation(array( + 'Drupal\Core\Validation\Constraint' => array(array( + 'dir' => drupal_realpath('core/lib/Drupal/Core/Validation/Constraint'), + 'class_base' => 'Drupal\Core\Validation\Constraint', + )), + )); + $this->factory = new ConstraintFactory($this->discovery); + } + + /** + * Implements Drupal\Component\Plugin\PluginManagerInterface::createInstance(). + * + * @param string $plugin_id + * The id of a plugin, i.e. the data type. + * + * @return Drupal\Core\TypedData\TypedDataInterface + */ + public function createInstance($plugin_id, array $configuration = array()) { + return $this->factory->createInstance($plugin_id, $configuration); + } +} diff --git a/core/lib/Drupal/Core/Validation/Constraint/MinValueConstraint.php b/core/lib/Drupal/Core/Validation/Constraint/MinValueConstraint.php new file mode 100644 index 0000000..3a0ada7 --- /dev/null +++ b/core/lib/Drupal/Core/Validation/Constraint/MinValueConstraint.php @@ -0,0 +1,32 @@ += $this->settings; + } +} \ No newline at end of file diff --git a/core/lib/Drupal/Core/Validation/Constraint/NotNullConstraint.php b/core/lib/Drupal/Core/Validation/Constraint/NotNullConstraint.php new file mode 100644 index 0000000..ca80b83 --- /dev/null +++ b/core/lib/Drupal/Core/Validation/Constraint/NotNullConstraint.php @@ -0,0 +1,32 @@ +get('constraint'); +dpm($x); +$y = $x->createInstance('notnull', array()); +dpm($y); +dpm($y->validate(1), 'validate 1'); +dpm($y->validate(0), 'validate 0'); +dpm($y->validate(NULL), 'validate NULL'); + +} catch (\Exception $e) { + dpm( $e, 'ooops'); +} diff --git a/core/lib/Drupal/Core/Validation/Validator/ValidatorInterface.php b/core/lib/Drupal/Core/Validation/Validator/ValidatorInterface.php new file mode 100644 index 0000000..c3ef05f --- /dev/null +++ b/core/lib/Drupal/Core/Validation/Validator/ValidatorInterface.php @@ -0,0 +1,23 @@ + 'ValidationConstraintTest', + 'description' => "Test constraint.", + 'group' => 'Validation', + ); + } + + public function setUp() { + parent::setUp(); + } + + /** + * Tests NotNullConstraint. + */ + public function testNotNullConstraint() { + $constraint = new Constraint\NotNullConstraint(); + $this->assertTrue($constraint->validate(1)); + $this->assertTrue($constraint->validate(0)); + $this->assertFalse($constraint->validate(NULL)); + } + + /** + * Tests RequiredConstraint. + */ + public function testRequiredConstraint() { + $constraint = new Constraint\RequiredConstraint(); + $this->assertTrue($constraint->validate('a value')); + $this->assertTrue($constraint->validate(array('a'))); + $this->assertFalse($constraint->validate(NULL)); + $this->assertFalse($constraint->validate(FALSE)); + $this->assertFalse($constraint->validate('')); + } +} diff --git a/core/modules/system/tests/modules/entity_test/entity_test.info b/core/modules/system/tests/modules/entity_test/entity_test.info index ce49e8a..e460ae0 100644 --- a/core/modules/system/tests/modules/entity_test/entity_test.info +++ b/core/modules/system/tests/modules/entity_test/entity_test.info @@ -4,4 +4,4 @@ package = Testing version = VERSION core = 8.x dependencies[] = field -hidden = TRUE +;hidden = TRUE diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestFormController.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestFormController.php index a65f06f..840523c 100644 --- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestFormController.php +++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestFormController.php @@ -49,8 +49,16 @@ public function form(array $form, array &$form_state, EntityInterface $entity) { '#default_value' => $entity->language()->langcode, '#languages' => LANGUAGE_ALL, ); + } - return $form; + /** + * Overrides Drupal\Core\Entity\EntityFormController::validate(). + */ + public function validate(array $form, array &$form_state) { + $entity = parent::getEntity($form_state); + if ($violqtions = $entity->validate()) { + form_set_error('errors', print_r($entity->validate())); + } } /**