Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
What's new
10
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Open sidebar
project
drupal
Commits
54c2e3d5
Commit
54c2e3d5
authored
Jun 06, 2013
by
alexpott
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Issue
#2002152
by fago, amateescu: Implement entity validation based on symfony validator.
parent
5b5d5351
Changes
11
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
341 additions
and
30 deletions
+341
-30
core/lib/Drupal/Core/Entity/EntityNG.php
core/lib/Drupal/Core/Entity/EntityNG.php
+8
-0
core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php
...b/Drupal/Core/Entity/EntityStorageControllerInterface.php
+8
-0
core/lib/Drupal/Core/Entity/Field/FieldItemBase.php
core/lib/Drupal/Core/Entity/Field/FieldItemBase.php
+17
-1
core/lib/Drupal/Core/Entity/Field/Type/Field.php
core/lib/Drupal/Core/Entity/Field/Type/Field.php
+15
-0
core/lib/Drupal/Core/TypedData/TypedDataManager.php
core/lib/Drupal/Core/TypedData/TypedDataManager.php
+6
-29
core/lib/Drupal/Core/Validation/ConstraintManager.php
core/lib/Drupal/Core/Validation/ConstraintManager.php
+22
-0
core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/ComplexDataConstraint.php
...on/Plugin/Validation/Constraint/ComplexDataConstraint.php
+67
-0
core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/ComplexDataConstraintValidator.php
.../Validation/Constraint/ComplexDataConstraintValidator.php
+47
-0
core/modules/system/lib/Drupal/system/Tests/Entity/EntityValidationTest.php
...m/lib/Drupal/system/Tests/Entity/EntityValidationTest.php
+138
-0
core/modules/system/system.module
core/modules/system/system.module
+5
-0
core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestStorageController.php
...st/lib/Drupal/entity_test/EntityTestStorageController.php
+8
-0
No files found.
core/lib/Drupal/Core/Entity/EntityNG.php
View file @
54c2e3d5
...
...
@@ -527,4 +527,12 @@ public function label($langcode = NULL) {
}
return
$label
;
}
/**
* {@inheritdoc}
*/
public
function
validate
()
{
// @todo: Add the typed data manager as proper dependency.
return
\
Drupal
::
typedData
()
->
getValidator
()
->
validate
(
$this
);
}
}
core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php
View file @
54c2e3d5
...
...
@@ -153,6 +153,14 @@ public function save(EntityInterface $entity);
* - translatable: Whether the field is translatable. Defaults to FALSE.
* - configurable: A boolean indicating whether the field is configurable
* via field.module. Defaults to FALSE.
* - property_constraints: An array of constraint arrays applying to the
* field item properties, keyed by property name. E.g. the following
* validates the value property to have a maximum length of 128:
* @code
* array(
* 'value' => array('Length' => array('max' => 128)),
* )
* @endcode
*
* @see Drupal\Core\TypedData\TypedDataManager::create()
* @see \Drupal::typedData()
...
...
core/lib/Drupal/Core/Entity/Field/FieldItemBase.php
View file @
54c2e3d5
...
...
@@ -91,6 +91,7 @@ public function set($property_name, $value, $notify = TRUE) {
// value that needs to be updated.
if
(
isset
(
$this
->
properties
[
$property_name
]))
{
$this
->
properties
[
$property_name
]
->
setValue
(
$value
,
FALSE
);
unset
(
$this
->
values
[
$property_name
]);
}
// Allow setting plain values for not-defined properties also.
else
{
...
...
@@ -136,4 +137,19 @@ public function onChange($property_name) {
// updated property object.
unset
(
$this
->
values
[
$property_name
]);
}
}
\ No newline at end of file
/**
* {@inheritdoc}
*/
public
function
getConstraints
()
{
$constraints
=
parent
::
getConstraints
();
// If property constraints are present add in a ComplexData constraint for
// applying them.
if
(
!
empty
(
$this
->
definition
[
'property_constraints'
]))
{
$constraints
[]
=
\
Drupal
::
typedData
()
->
getValidationConstraintManager
()
->
create
(
'ComplexData'
,
$this
->
definition
[
'property_constraints'
]);
}
return
$constraints
;
}
}
core/lib/Drupal/Core/Entity/Field/Type/Field.php
View file @
54c2e3d5
...
...
@@ -194,4 +194,19 @@ public function defaultAccess($operation = 'view', UserInterface $account = NULL
// Grant access per default.
return
TRUE
;
}
/**
* {@inheritdoc}
*/
public
function
getConstraints
()
{
// Constraints usually apply to the field item, but required does make
// sense on the field only. So we special-case it to apply to the field for
// now.
// @todo: Separate list and list item definitions to separate constraints.
$constraints
=
array
();
if
(
!
empty
(
$this
->
definition
[
'required'
]))
{
$constraints
[]
=
\
Drupal
::
typedData
()
->
getValidationConstraintManager
()
->
create
(
'NotNull'
,
array
());
}
return
$constraints
;
}
}
core/lib/Drupal/Core/TypedData/TypedDataManager.php
View file @
54c2e3d5
...
...
@@ -303,28 +303,6 @@ public function getValidationConstraintManager() {
return
$this
->
constraintManager
;
}
/**
* Creates a validation constraint plugin.
*
* @param string $name
* The name or plugin id of the constraint.
* @param mixed $options
* The options to pass to the constraint class. Required and supported
* options depend on the constraint class.
*
* @return \Symfony\Component\Validator\Constraint
* A validation constraint plugin.
*/
public
function
createValidationConstraint
(
$name
,
$options
)
{
if
(
!
is_array
(
$options
))
{
// Plugins need an array as configuration, so make sure we have one.
// The constraint classes support passing the options as part of the
// 'value' key also.
$options
=
array
(
'value'
=>
$options
);
}
return
$this
->
getValidationConstraintManager
()
->
createInstance
(
$name
,
$options
);
}
/**
* Gets configured constraints from a data definition.
*
...
...
@@ -365,29 +343,28 @@ public function createValidationConstraint($name, $options) {
*/
public
function
getConstraints
(
$definition
)
{
$constraints
=
array
();
// @todo: Figure out how to handle nested constraint structures as
// collections.
$validation_manager
=
$this
->
getValidationConstraintManager
();
$type_definition
=
$this
->
getDefinition
(
$definition
[
'type'
]);
// Auto-generate a constraint for the primitive type if we have a mapping.
if
(
isset
(
$type_definition
[
'primitive type'
]))
{
$constraints
[]
=
$this
->
getValidationConstraintManager
()
->
createInstance
(
'PrimitiveType'
,
array
(
'type'
=>
$type_definition
[
'primitive type'
]));
$constraints
[]
=
$validation_manager
->
create
(
'PrimitiveType'
,
array
(
'type'
=>
$type_definition
[
'primitive type'
]));
}
// Add in constraints specified by the data type.
if
(
isset
(
$type_definition
[
'constraints'
]))
{
foreach
(
$type_definition
[
'constraints'
]
as
$name
=>
$options
)
{
$constraints
[]
=
$
this
->
createValidationConstraint
(
$name
,
$options
);
$constraints
[]
=
$
validation_manager
->
create
(
$name
,
$options
);
}
}
// Add any constraints specified as part of the data definition.
if
(
isset
(
$definition
[
'constraints'
]))
{
foreach
(
$definition
[
'constraints'
]
as
$name
=>
$options
)
{
$constraints
[]
=
$
this
->
createValidationConstraint
(
$name
,
$options
);
$constraints
[]
=
$
validation_manager
->
create
(
$name
,
$options
);
}
}
// Add the NotNull constraint for required data.
if
(
!
empty
(
$definition
[
'required'
])
&&
empty
(
$definition
[
'constraints'
][
'NotNull'
]))
{
$constraints
[]
=
$
this
->
createValidationConstraint
(
'NotNull'
,
array
());
$constraints
[]
=
$
validation_manager
->
create
(
'NotNull'
,
array
());
}
return
$constraints
;
}
...
...
core/lib/Drupal/Core/Validation/ConstraintManager.php
View file @
54c2e3d5
...
...
@@ -55,6 +55,28 @@ public function __construct(\Traversable $namespaces) {
$this
->
factory
=
new
DefaultFactory
(
$this
);
}
/**
* Creates a validation constraint.
*
* @param string $name
* The name or plugin id of the constraint.
* @param mixed $options
* The options to pass to the constraint class. Required and supported
* options depend on the constraint class.
*
* @return \Symfony\Component\Validator\Constraint
* A validation constraint plugin.
*/
public
function
create
(
$name
,
$options
)
{
if
(
!
is_array
(
$options
))
{
// Plugins need an array as configuration, so make sure we have one.
// The constraint classes support passing the options as part of the
// 'value' key also.
$options
=
array
(
'value'
=>
$options
);
}
return
$this
->
createInstance
(
$name
,
$options
);
}
/**
* Callback for registering definitions for constraints shipped with Symfony.
*
...
...
core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/ComplexDataConstraint.php
0 → 100644
View file @
54c2e3d5
<?php
/**
* @file
* Contains \Drupal\Core\Validation\Plugin\Validation\Constraint\CollectionConstraint.
*/
namespace
Drupal\Core\Validation\Plugin\Validation\Constraint
;
use
Drupal\Component\Annotation\Plugin
;
use
Drupal\Core\Annotation\Translation
;
use
Symfony\Component\Validator\Constraint
;
/**
* Complex data constraint.
*
* Validates properties of complex data structures.
*
* @Plugin(
* id = "ComplexData",
* label = @Translation("Complex data", context = "Validation")
* )
*/
class
ComplexDataConstraint
extends
Constraint
{
/**
* An array of constraints for contained properties, keyed by property name.
*
* @var array
*/
public
$properties
;
/**
* {@inheritdoc}
*/
public
function
__construct
(
$options
=
NULL
)
{
// Allow skipping the 'properties' key in the options.
if
(
is_array
(
$options
)
&&
!
array_key_exists
(
'properties'
,
$options
))
{
$options
=
array
(
'properties'
=>
$options
);
}
parent
::
__construct
(
$options
);
$constraint_manager
=
\
Drupal
::
service
(
'validation.constraint'
);
// Instantiate constraint objects for array definitions.
foreach
(
$this
->
properties
as
&
$constraints
)
{
foreach
(
$constraints
as
$id
=>
$options
)
{
if
(
!
is_object
(
$options
))
{
$constraints
[
$id
]
=
$constraint_manager
->
create
(
$id
,
$options
);
}
}
}
}
/**
* {@inheritdoc}
*/
public
function
getDefaultOption
()
{
return
'properties'
;
}
/**
* {@inheritdoc}
*/
public
function
getRequiredOptions
()
{
return
array
(
'properties'
);
}
}
core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/ComplexDataConstraintValidator.php
0 → 100644
View file @
54c2e3d5
<?php
/**
* @file
* Contains \Drupal\Core\Validation\Plugin\Validation\Constraint\ComplexDataConstraintValidator.
*/
namespace
Drupal\Core\Validation\Plugin\Validation\Constraint
;
use
Drupal\Core\TypedData\ComplexDataInterface
;
use
Symfony\Component\Validator\Constraint
;
use
Symfony\Component\Validator\ConstraintValidator
;
use
Symfony\Component\Validator\Exception\UnexpectedTypeException
;
/**
* Validates complex data.
*/
class
ComplexDataConstraintValidator
extends
ConstraintValidator
{
/**
* {@inheritdoc}
*/
public
function
validate
(
$value
,
Constraint
$constraint
)
{
if
(
!
isset
(
$value
))
{
return
;
}
if
(
!
$value
instanceof
ComplexDataInterface
)
{
throw
new
UnexpectedTypeException
(
$value
,
'ComplexData'
);
}
$group
=
$this
->
context
->
getGroup
();
foreach
(
$constraint
->
properties
as
$name
=>
$constraints
)
{
$property
=
$value
->
get
(
$name
);
$is_container
=
$property
instanceof
ComplexDataInterface
||
$property
instanceof
ListInterface
;
if
(
!
$is_container
)
{
$property
=
$property
->
getValue
();
}
elseif
(
$property
->
isEmpty
())
{
// @see \Drupal\Core\TypedData\Validation\PropertyContainerMetadata::accept();
$property
=
NULL
;
}
$this
->
context
->
validateValue
(
$property
,
$constraints
,
$name
,
$group
);
}
}
}
core/modules/system/lib/Drupal/system/Tests/Entity/EntityValidationTest.php
0 → 100644
View file @
54c2e3d5
<?php
/**
* @file
* Contains \Drupal\system\Tests\Entity\EntityValidationTest.
*/
namespace
Drupal\system\Tests\Entity
;
use
Drupal\Core\Entity\EntityInterface
;
use
Drupal\Core\Entity\Field\FieldInterface
;
use
Drupal\Core\Entity\Field\FieldItemInterface
;
use
Drupal\Core\TypedData\TypedDataInterface
;
/**
* Tests Entity API base functionality.
*/
class
EntityValidationTest
extends
EntityUnitTestBase
{
/**
* Modules to enable.
*
* @var array
*/
public
static
$modules
=
array
(
'filter'
,
'text'
);
public
static
function
getInfo
()
{
return
array
(
'name'
=>
'Entity Validation API'
,
'description'
=>
'Tests the Entity Validation API'
,
'group'
=>
'Entity API'
,
);
}
/**
* {@inheritdoc}
*/
public
function
setUp
()
{
parent
::
setUp
();
$this
->
installSchema
(
'user'
,
array
(
'users_roles'
,
'users_data'
));
$this
->
installSchema
(
'entity_test'
,
array
(
'entity_test_mul'
,
'entity_test_mul_property_data'
,
'entity_test_rev'
,
'entity_test_rev_revision'
,
'entity_test_mulrev'
,
'entity_test_mulrev_property_data'
,
'entity_test_mulrev_property_revision'
));
// Create the test field.
entity_test_install
();
// Install required default configuration for filter module.
$this
->
installConfig
(
array
(
'system'
,
'filter'
));
}
/**
* Creates a test entity.
*
* @param string $entity_type
* An entity type.
*
* @return \Drupal\Core\Entity\EntityInterface
* The created test entity.
*/
protected
function
createTestEntity
(
$entity_type
)
{
$this
->
entity_name
=
$this
->
randomName
();
$this
->
entity_user
=
$this
->
createUser
();
$this
->
entity_field_text
=
$this
->
randomName
();
// Pass in the value of the name field when creating. With the user
// field we test setting a field after creation.
$entity
=
entity_create
(
$entity_type
,
array
());
$entity
->
user_id
->
target_id
=
$this
->
entity_user
->
uid
;
$entity
->
name
->
value
=
$this
->
entity_name
;
// Set a value for the test field.
$entity
->
field_test_text
->
value
=
$this
->
entity_field_text
;
return
$entity
;
}
/**
* Tests validating test entity types.
*/
public
function
testValidation
()
{
// All entity variations have to have the same results.
foreach
(
entity_test_entity_types
()
as
$entity_type
)
{
$this
->
checkValidation
(
$entity_type
);
}
}
/**
* Executes the validation test set for a defined entity type.
*
* @param string $entity_type
* The entity type to run the tests with.
*/
protected
function
checkValidation
(
$entity_type
)
{
$entity
=
$this
->
createTestEntity
(
$entity_type
);
$violations
=
$entity
->
validate
();
$this
->
assertEqual
(
$violations
->
count
(),
0
,
'Validation passes.'
);
// Test triggering a fail for each of the constraints specified.
$test_entity
=
clone
$entity
;
$test_entity
->
uuid
->
value
=
$this
->
randomString
(
129
);
$violations
=
$test_entity
->
validate
();
$this
->
assertEqual
(
$violations
->
count
(),
1
,
'Validation failed.'
);
$this
->
assertEqual
(
$violations
[
0
]
->
getMessage
(),
t
(
'This value is too long. It should have %limit characters or less.'
,
array
(
'%limit'
=>
'128'
)));
$test_entity
=
clone
$entity
;
$test_entity
->
langcode
->
value
=
$this
->
randomString
(
13
);
$violations
=
$test_entity
->
validate
();
$this
->
assertEqual
(
$violations
->
count
(),
1
,
'Validation failed.'
);
$this
->
assertEqual
(
$violations
[
0
]
->
getMessage
(),
t
(
'This value is too long. It should have %limit characters or less.'
,
array
(
'%limit'
=>
'12'
)));
$test_entity
=
clone
$entity
;
$test_entity
->
type
->
value
=
NULL
;
$violations
=
$test_entity
->
validate
();
$this
->
assertEqual
(
$violations
->
count
(),
1
,
'Validation failed.'
);
$this
->
assertEqual
(
$violations
[
0
]
->
getMessage
(),
t
(
'This value should not be null.'
));
$test_entity
=
clone
$entity
;
$test_entity
->
name
->
value
=
$this
->
randomString
(
33
);
$violations
=
$test_entity
->
validate
();
$this
->
assertEqual
(
$violations
->
count
(),
1
,
'Validation failed.'
);
$this
->
assertEqual
(
$violations
[
0
]
->
getMessage
(),
t
(
'This value is too long. It should have %limit characters or less.'
,
array
(
'%limit'
=>
'32'
)));
// Make sure the information provided by a violation is correct.
$violation
=
$violations
[
0
];
$this
->
assertEqual
(
$violation
->
getRoot
(),
$test_entity
,
'Violation root is entity.'
);
$this
->
assertEqual
(
$violation
->
getPropertyPath
(),
'name.0.value'
,
'Violation property path is correct.'
);
$this
->
assertEqual
(
$violation
->
getInvalidValue
(),
$test_entity
->
name
->
value
,
'Violation contains invalid value.'
);
}
}
core/modules/system/system.module
View file @
54c2e3d5
...
...
@@ -2280,6 +2280,11 @@ function system_data_type_info() {
'description'
=>
t
(
'An entity field referencing a language.'
),
'class'
=>
'\Drupal\Core\Entity\Field\Type\LanguageItem'
,
'list class'
=>
'\Drupal\Core\Entity\Field\Type\Field'
,
'constraints'
=>
array
(
'ComplexData'
=>
array
(
'value'
=>
array
(
'Length'
=>
array
(
'max'
=>
12
)),
),
),
),
'entity_reference_field'
=>
array
(
'label'
=>
t
(
'Entity reference field item'
),
...
...
core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestStorageController.php
View file @
54c2e3d5
...
...
@@ -41,6 +41,9 @@ public function baseFieldDefinitions() {
'label'
=>
t
(
'UUID'
),
'description'
=>
t
(
'The UUID of the test entity.'
),
'type'
=>
'string_field'
,
'property_constraints'
=>
array
(
'value'
=>
array
(
'Length'
=>
array
(
'max'
=>
128
)),
),
);
$fields
[
'langcode'
]
=
array
(
'label'
=>
t
(
'Language code'
),
...
...
@@ -52,11 +55,16 @@ public function baseFieldDefinitions() {
'description'
=>
t
(
'The name of the test entity.'
),
'type'
=>
'string_field'
,
'translatable'
=>
TRUE
,
'property_constraints'
=>
array
(
'value'
=>
array
(
'Length'
=>
array
(
'max'
=>
32
)),
),
);
$fields
[
'type'
]
=
array
(
'label'
=>
t
(
'Type'
),
'description'
=>
t
(
'The bundle of the test entity.'
),
'type'
=>
'string_field'
,
'required'
=>
TRUE
,
// @todo: Add allowed values validation.
);
$fields
[
'user_id'
]
=
array
(
'label'
=>
t
(
'User ID'
),
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment