Issue #1845546 by fago, EclipseGc, attiks, effulgentsia: Implement validation for the TypedData API.
parent
fb08e2f281
commit
78876b2060
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Component\Plugin\Discovery\StaticDiscoveryDecorator.
|
||||
*/
|
||||
|
||||
namespace Drupal\Component\Plugin\Discovery;
|
||||
|
||||
/**
|
||||
* A decorator that allows manual registration of undiscoverable definitions.
|
||||
*/
|
||||
class StaticDiscoveryDecorator extends StaticDiscovery {
|
||||
|
||||
/**
|
||||
* The Discovery object being decorated.
|
||||
*
|
||||
* @var \Drupal\Component\Plugin\Discovery\DiscoveryInterface
|
||||
*/
|
||||
protected $decorated;
|
||||
|
||||
/**
|
||||
* A callback or closure used for registering additional definitions.
|
||||
*
|
||||
* @var \Callable
|
||||
*/
|
||||
protected $registerDefinitions;
|
||||
|
||||
/**
|
||||
* Constructs a \Drupal\Component\Plugin\Discovery\StaticDiscoveryDecorator object.
|
||||
*
|
||||
* @param \Drupal\Component\Plugin\Discovery\DiscoveryInterface $decorated
|
||||
* The discovery object that is being decorated.
|
||||
* @param \Callable $registerDefinitions
|
||||
* (optional) A callback or closure used for registering additional
|
||||
* definitions.
|
||||
*/
|
||||
public function __construct(DiscoveryInterface $decorated, $registerDefinitions = NULL) {
|
||||
$this->decorated = $decorated;
|
||||
$this->registerDefinitions = $registerDefinitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\Plugin\Discovery\DiscoveryInterface::getDefinition().
|
||||
*/
|
||||
public function getDefinition($base_plugin_id) {
|
||||
if (isset($this->registerDefinitions)) {
|
||||
call_user_func($this->registerDefinitions);
|
||||
}
|
||||
$this->definitions += $this->decorated->getDefinitions();
|
||||
return parent::getDefinition($base_plugin_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\Plugin\Discovery\DiscoveryInterface::getDefinitions().
|
||||
*/
|
||||
public function getDefinitions() {
|
||||
if (isset($this->registerDefinitions)) {
|
||||
call_user_func($this->registerDefinitions);
|
||||
}
|
||||
$this->definitions += $this->decorated->getDefinitions();
|
||||
return parent::getDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes through all unknown calls onto the decorated object
|
||||
*/
|
||||
public function __call($method, $args) {
|
||||
return call_user_func_array(array($this->decorated, $method), $args);
|
||||
}
|
||||
}
|
|
@ -149,7 +149,10 @@ class CoreBundle extends Bundle {
|
|||
->setFactoryClass('Drupal\Core\Database\Database')
|
||||
->setFactoryMethod('getConnection')
|
||||
->addArgument('slave');
|
||||
$container->register('typed_data', 'Drupal\Core\TypedData\TypedDataManager');
|
||||
$container->register('typed_data', 'Drupal\Core\TypedData\TypedDataManager')
|
||||
->addMethodCall('setValidationConstraintManager', array(new Reference('validation.constraint')));
|
||||
$container->register('validation.constraint', 'Drupal\Core\Validation\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')
|
||||
|
|
|
@ -698,7 +698,7 @@ class DatabaseStorageController implements EntityStorageControllerInterface {
|
|||
}
|
||||
}
|
||||
|
||||
$bundle = !empty($constraints['bundle']) ? $constraints['bundle'] : FALSE;
|
||||
$bundle = !empty($constraints['Bundle']) ? $constraints['Bundle'] : FALSE;
|
||||
|
||||
// Add in per-bundle fields.
|
||||
if (!isset($this->fieldDefinitions[$bundle])) {
|
||||
|
|
|
@ -218,8 +218,8 @@ class EntityNG extends Entity {
|
|||
public function getPropertyDefinitions() {
|
||||
if (!isset($this->fieldDefinitions)) {
|
||||
$this->fieldDefinitions = drupal_container()->get('plugin.manager.entity')->getStorageController($this->entityType)->getFieldDefinitions(array(
|
||||
'entity type' => $this->entityType,
|
||||
'bundle' => $this->bundle,
|
||||
'EntityType' => $this->entityType,
|
||||
'Bundle' => $this->bundle,
|
||||
));
|
||||
}
|
||||
return $this->fieldDefinitions;
|
||||
|
|
|
@ -125,8 +125,8 @@ interface EntityStorageControllerInterface {
|
|||
* 'bundle' key. For example:
|
||||
* @code
|
||||
* array(
|
||||
* 'entity type' => 'node',
|
||||
* 'bundle' => 'article',
|
||||
* 'EntityType' => 'node',
|
||||
* 'Bundle' => 'article',
|
||||
* )
|
||||
* @endcode
|
||||
*
|
||||
|
|
|
@ -38,11 +38,14 @@ class EntityReferenceItem extends FieldItemBase {
|
|||
// @todo: Lookup the entity type's ID data type and use it here.
|
||||
'type' => 'integer',
|
||||
'label' => t('Entity ID'),
|
||||
'constraints' => array(
|
||||
'Range' => array('min' => 0),
|
||||
),
|
||||
);
|
||||
static::$propertyDefinitions[$target_type]['entity'] = array(
|
||||
'type' => 'entity',
|
||||
'constraints' => array(
|
||||
'entity type' => $target_type,
|
||||
'EntityType' => $target_type,
|
||||
),
|
||||
'label' => t('Entity'),
|
||||
'description' => t('The referenced entity'),
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
namespace Drupal\Core\Entity\Field\Type;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityNG;
|
||||
use Drupal\Core\TypedData\ComplexDataInterface;
|
||||
use Drupal\Core\TypedData\ContextAwareInterface;
|
||||
use Drupal\Core\TypedData\ContextAwareTypedData;
|
||||
|
@ -29,8 +30,8 @@ use InvalidArgumentException;
|
|||
* an 'entity type' constraint is specified.
|
||||
*
|
||||
* Supported constraints (below the definition's 'constraints' key) are:
|
||||
* - entity type: The entity type.
|
||||
* - bundle: The bundle or an array of possible bundles.
|
||||
* - EntityType: The entity type.
|
||||
* - Bundle: The bundle or an array of possible bundles.
|
||||
*
|
||||
* Supported settings (below the definition's 'settings' key) are:
|
||||
* - id source: If used as computed property, the ID property used to load
|
||||
|
@ -57,7 +58,7 @@ class EntityWrapper extends ContextAwareTypedData implements IteratorAggregate,
|
|||
*/
|
||||
public function __construct(array $definition, $name = NULL, ContextAwareInterface $parent = NULL) {
|
||||
parent::__construct($definition, $name, $parent);
|
||||
$this->entityType = isset($this->definition['constraints']['entity type']) ? $this->definition['constraints']['entity type'] : NULL;
|
||||
$this->entityType = isset($this->definition['constraints']['EntityType']) ? $this->definition['constraints']['EntityType'] : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -89,7 +90,7 @@ class EntityWrapper extends ContextAwareTypedData implements IteratorAggregate,
|
|||
$this->entityType = $value->entityType();
|
||||
$value = $value->id();
|
||||
}
|
||||
elseif (isset($value) && !(is_scalar($value) && !empty($this->definition['constraints']['entity type']))) {
|
||||
elseif (isset($value) && !(is_scalar($value) && !empty($this->definition['constraints']['EntityType']))) {
|
||||
throw new InvalidArgumentException('Value is not a valid entity.');
|
||||
}
|
||||
// Now update the value in the source or the local id property.
|
||||
|
@ -116,7 +117,9 @@ class EntityWrapper extends ContextAwareTypedData implements IteratorAggregate,
|
|||
* Implements \IteratorAggregate::getIterator().
|
||||
*/
|
||||
public function getIterator() {
|
||||
if ($entity = $this->getValue()) {
|
||||
// @todo: Remove check for EntityNG once all entity types are converted.
|
||||
$entity = $this->getValue();
|
||||
if ($entity && $entity instanceof EntityNG) {
|
||||
return $entity->getIterator();
|
||||
}
|
||||
return new ArrayIterator(array());
|
||||
|
@ -193,6 +196,6 @@ class EntityWrapper extends ContextAwareTypedData implements IteratorAggregate,
|
|||
* Implements \Drupal\Core\TypedData\ComplexDataInterface::isEmpty().
|
||||
*/
|
||||
public function isEmpty() {
|
||||
return (bool) $this->getValue();
|
||||
return !$this->getValue();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -116,6 +116,14 @@ class Field extends ContextAwareTypedData implements IteratorAggregate, FieldInt
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides \Drupal\Core\TypedData\TypedData::getConstraints().
|
||||
*/
|
||||
public function getConstraints() {
|
||||
// Apply the constraints to the list items only.
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements \ArrayAccess::offsetExists().
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Validation\Constraint\BundleConstraint.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Plugin\Validation\Constraint;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Drupal\Core\Annotation\Plugin;
|
||||
use Drupal\Core\Annotation\Translation;
|
||||
|
||||
|
||||
/**
|
||||
* Checks if a value is a valid entity type.
|
||||
*
|
||||
* @todo: Move this below the entity core component.
|
||||
*
|
||||
* @Plugin(
|
||||
* id = "Bundle",
|
||||
* label = @Translation("Bundle", context = "Validation"),
|
||||
* type = "entity"
|
||||
* )
|
||||
*/
|
||||
class BundleConstraint extends Constraint {
|
||||
|
||||
/**
|
||||
* The default violation message.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $message = 'The entity must be of bundle %bundle.';
|
||||
|
||||
/**
|
||||
* The bundle option.
|
||||
*
|
||||
* @var string|array
|
||||
*/
|
||||
public $bundle;
|
||||
|
||||
/**
|
||||
* Gets the bundle option as array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getBundleOption() {
|
||||
// Support passing the bundle as string, but force it to be an array.
|
||||
if (!is_array($this->bundle)) {
|
||||
$this->bundle = array($this->bundle);
|
||||
}
|
||||
return $this->bundle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides Constraint::getDefaultOption().
|
||||
*/
|
||||
public function getDefaultOption() {
|
||||
return 'bundle';
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides Constraint::getRequiredOptions().
|
||||
*/
|
||||
public function getRequiredOptions() {
|
||||
return array('bundle');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Plugin\Validation\Constraint\BundleConstraintValidator.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Plugin\Validation\Constraint;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
|
||||
/**
|
||||
* Validates the Bundle constraint.
|
||||
*/
|
||||
class BundleConstraintValidator extends ConstraintValidator {
|
||||
|
||||
/**
|
||||
* Implements \Symfony\Component\Validator\ConstraintValidatorInterface::validate().
|
||||
*/
|
||||
public function validate($typed_data, Constraint $constraint) {
|
||||
$entity = isset($typed_data) ? $typed_data->getValue() : FALSE;
|
||||
|
||||
if (!empty($entity) && !in_array($entity->bundle(), $constraint->getBundleOption())) {
|
||||
$this->context->addViolation($constraint->message, array('%bundle', implode(', ', $constraint->getBundleOption())));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Validation\Constraint\EntityTypeConstraint.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Plugin\Validation\Constraint;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Drupal\Core\Annotation\Plugin;
|
||||
use Drupal\Core\Annotation\Translation;
|
||||
|
||||
|
||||
/**
|
||||
* Checks if a value is a valid entity type.
|
||||
*
|
||||
* @todo: Move this below the entity core component.
|
||||
*
|
||||
* @Plugin(
|
||||
* id = "EntityType",
|
||||
* label = @Translation("Entity type", context = "Validation"),
|
||||
* type = "entity"
|
||||
* )
|
||||
*/
|
||||
class EntityTypeConstraint extends Constraint {
|
||||
|
||||
/**
|
||||
* The default violation message.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $message = 'The entity must be of type %type.';
|
||||
|
||||
/**
|
||||
* The entity type option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $type;
|
||||
|
||||
/**
|
||||
* Overrides Constraint::getDefaultOption().
|
||||
*/
|
||||
public function getDefaultOption() {
|
||||
return 'type';
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides Constraint::getRequiredOptions().
|
||||
*/
|
||||
public function getRequiredOptions() {
|
||||
return array('type');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Plugin\Validation\Constraint\EntityTypeConstraintValidator.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Plugin\Validation\Constraint;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
|
||||
/**
|
||||
* Validates the EntityType constraint.
|
||||
*/
|
||||
class EntityTypeConstraintValidator extends ConstraintValidator {
|
||||
|
||||
/**
|
||||
* Implements \Symfony\Component\Validator\ConstraintValidatorInterface::validate().
|
||||
*/
|
||||
public function validate($typed_data, Constraint $constraint) {
|
||||
$entity = isset($typed_data) ? $typed_data->getValue() : FALSE;
|
||||
|
||||
if (!empty($entity) && $entity->entityType() != $constraint->type) {
|
||||
$this->context->addViolation($constraint->message, array('%type', $constraint->type));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Plugin\Validation\Constraint\LengthConstraint.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Plugin\Validation\Constraint;
|
||||
|
||||
use Drupal\Core\Annotation\Plugin;
|
||||
use Drupal\Core\Annotation\Translation;
|
||||
use Symfony\Component\Validator\Constraints\Length;
|
||||
|
||||
/**
|
||||
* Length constraint.
|
||||
*
|
||||
* Overrides the symfony constraint to use Drupal-style replacement patterns.
|
||||
*
|
||||
* @todo: Move this below the TypedData core component.
|
||||
*
|
||||
* @Plugin(
|
||||
* id = "Length",
|
||||
* label = @Translation("Length", context = "Validation"),
|
||||
* type = { "string" }
|
||||
* )
|
||||
*/
|
||||
class LengthConstraint extends Length {
|
||||
|
||||
public $maxMessage = 'This value is too long. It should have %limit character or less.|This value is too long. It should have %limit characters or less.';
|
||||
public $minMessage = 'This value is too short. It should have %limit character or more.|This value is too short. It should have %limit characters or more.';
|
||||
public $exactMessage = 'This value should have exactly %limit character.|This value should have exactly %limit characters.';
|
||||
|
||||
/**
|
||||
* Overrides Range::validatedBy().
|
||||
*/
|
||||
public function validatedBy() {
|
||||
return '\Symfony\Component\Validator\Constraints\LengthValidator';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Plugin\Validation\Constraint\PrimitiveTypeConstraint.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Plugin\Validation\Constraint;
|
||||
|
||||
use Drupal\Core\Annotation\Plugin;
|
||||
use Drupal\Core\Annotation\Translation;
|
||||
use Symfony\Component\Validator\Constraints\Type as SymfonyConstraint;
|
||||
|
||||
/**
|
||||
* Supports validating all primitive types.
|
||||
*
|
||||
* @todo: Move this below the TypedData core component.
|
||||
*
|
||||
* @Plugin(
|
||||
* id = "PrimitiveType",
|
||||
* label = @Translation("Primitive type", context = "Validation")
|
||||
* )
|
||||
*/
|
||||
class PrimitiveTypeConstraint extends SymfonyConstraint {
|
||||
|
||||
public $message = 'This value should be of type %type.';
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Plugin\Validation\Constraint\PrimitiveTypeConstraintValidator.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Plugin\Validation\Constraint;
|
||||
|
||||
use DateInterval;
|
||||
use Drupal\Core\TypedData\Primitive;
|
||||
use Drupal\Core\Datetime\DrupalDateTime;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
|
||||
/**
|
||||
* Validates the PrimitiveType constraint.
|
||||
*/
|
||||
class PrimitiveTypeConstraintValidator extends ConstraintValidator {
|
||||
|
||||
/**
|
||||
* Implements \Symfony\Component\Validator\ConstraintValidatorInterface::validate().
|
||||
*/
|
||||
public function validate($value, Constraint $constraint) {
|
||||
|
||||
if (!isset($value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch ($constraint->type) {
|
||||
case Primitive::BINARY:
|
||||
$valid = is_resource($value);
|
||||
break;
|
||||
case Primitive::BOOLEAN:
|
||||
$valid = is_bool($value) || $value === 0 || $value === '0' || $value === 1 || $value == '1';
|
||||
break;
|
||||
case Primitive::DATE:
|
||||
$valid = $value instanceOf DrupalDateTime && !$value->hasErrors();
|
||||
break;
|
||||
case Primitive::DURATION:
|
||||
$valid = $value instanceof DateInterval;
|
||||
break;
|
||||
case Primitive::FLOAT:
|
||||
$valid = filter_var($value, FILTER_VALIDATE_FLOAT) !== FALSE;
|
||||
break;
|
||||
case Primitive::INTEGER:
|
||||
$valid = filter_var($value, FILTER_VALIDATE_INT) !== FALSE;
|
||||
break;
|
||||
case Primitive::STRING:
|
||||
// PHP integers, floats or booleans are valid strings also, so we
|
||||
// cannot use is_string() here.
|
||||
$valid = is_scalar($value);
|
||||
break;
|
||||
case Primitive::URI:
|
||||
$valid = filter_var($value, FILTER_VALIDATE_URL) ;
|
||||
break;
|
||||
default:
|
||||
$valid = FALSE;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$valid) {
|
||||
$this->context->addViolation($constraint->message, array(
|
||||
'%value' => is_object($value) ? get_class($value) : (is_array($value) ? 'Array' : (string) $value),
|
||||
'%type' => $constraint->type,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Plugin\Validation\Constraint\RangeConstraint.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Plugin\Validation\Constraint;
|
||||
|
||||
use Drupal\Core\Annotation\Plugin;
|
||||
use Drupal\Core\Annotation\Translation;
|
||||
use Symfony\Component\Validator\Constraints\Range;
|
||||
|
||||
/**
|
||||
* Range constraint.
|
||||
*
|
||||
* Overrides the symfony constraint to use Drupal-style replacement patterns.
|
||||
*
|
||||
* @todo: Move this below the TypedData core component.
|
||||
*
|
||||
* @Plugin(
|
||||
* id = "Range",
|
||||
* label = @Translation("Range", context = "Validation"),
|
||||
* type = { "integer", "float" }
|
||||
* )
|
||||
*/
|
||||
class RangeConstraint extends Range {
|
||||
|
||||
public $minMessage = 'This value should be %limit or more.';
|
||||
public $maxMessage = 'This value should be %limit or less.';
|
||||
|
||||
/**
|
||||
* Overrides Range::validatedBy().
|
||||
*/
|
||||
public function validatedBy() {
|
||||
return '\Symfony\Component\Validator\Constraints\RangeValidator';
|
||||
}
|
||||
}
|
|
@ -40,7 +40,7 @@ class Binary extends TypedData {
|
|||
// If the value has been set by (absolute) stream resource URI, access the
|
||||
// resource now.
|
||||
if (!isset($this->handle) && isset($this->uri)) {
|
||||
$this->handle = fopen($this->uri, 'rb');
|
||||
$this->handle = is_readable($this->uri) ? fopen($this->uri, 'rb') : FALSE;
|
||||
}
|
||||
return $this->handle;
|
||||
}
|
||||
|
@ -55,16 +55,14 @@ class Binary extends TypedData {
|
|||
$this->handle = NULL;
|
||||
$this->uri = NULL;
|
||||
}
|
||||
elseif (is_resource($value)) {
|
||||
$this->handle = $value;
|
||||
}
|
||||
elseif (is_string($value)) {
|
||||
// Note: For performance reasons we store the given URI and access the
|
||||
// resource upon request. See Binary::getValue()
|
||||
$this->uri = $value;
|
||||
$this->handle = NULL;
|
||||
}
|
||||
else {
|
||||
throw new InvalidArgumentException("Invalid value for binary data given.");
|
||||
$this->handle = $value;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,11 +23,4 @@ class Boolean extends TypedData {
|
|||
* @var boolean
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* Overrides TypedData::setValue().
|
||||
*/
|
||||
public function setValue($value) {
|
||||
$this->value = isset($value) ? (bool) $value : $value;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,9 +40,6 @@ class Date extends TypedData {
|
|||
}
|
||||
else {
|
||||
$this->value = $value instanceOf DrupalDateTime ? $value : new DrupalDateTime($value);
|
||||
if ($this->value->hasErrors()) {
|
||||
throw new InvalidArgumentException("Invalid date format given.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,21 +32,30 @@ class Duration extends TypedData {
|
|||
* Overrides TypedData::setValue().
|
||||
*/
|
||||
public function setValue($value) {
|
||||
if ($value instanceof DateInterval || !isset($value)) {
|
||||
$this->value = $value;
|
||||
// Catch any exceptions thrown due to invalid values being passed.
|
||||
try {
|
||||
if ($value instanceof DateInterval || !isset($value)) {
|
||||
$this->value = $value;
|
||||
}
|
||||
// Treat integer values as time spans in seconds, even if supplied as PHP
|
||||
// string.
|
||||
elseif ((string) (int) $value === (string) $value) {
|
||||
$this->value = new DateInterval('PT' . $value . 'S');
|
||||
}
|
||||
elseif (is_string($value)) {
|
||||
// @todo: Add support for negative intervals on top of the DateInterval
|
||||
// constructor.
|
||||
$this->value = new DateInterval($value);
|
||||
}
|
||||
else {
|
||||
// Unknown value given.
|
||||
$this->value = $value;
|
||||
}
|
||||
}
|
||||
// Treat integer values as time spans in seconds, even if supplied as PHP
|
||||
// string.
|
||||
elseif ((string) (int) $value === (string) $value) {
|
||||
$this->value = new DateInterval('PT' . $value . 'S');
|
||||
}
|
||||
elseif (is_string($value)) {
|
||||
// @todo: Add support for negative intervals on top of the DateInterval
|
||||
// constructor.
|
||||
$this->value = new DateInterval($value);
|
||||
}
|
||||
else {
|
||||
throw new InvalidArgumentException("Invalid duration format given.");
|
||||
catch (\Exception $e) {
|
||||
// An invalid value has been given. Setting any invalid value will let
|
||||
// validation fail.
|
||||
$this->value = $e;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,11 +14,4 @@ namespace Drupal\Core\TypedData\Type;
|
|||
*/
|
||||
class Email extends String {
|
||||
|
||||
/**
|
||||
* Implements \Drupal\Core\TypedData\TypedDataInterface::validate().
|
||||
*/
|
||||
public function validate() {
|
||||
// @todo Implement validate() method.
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -23,11 +23,4 @@ class Float extends TypedData {
|
|||
* @var float
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* Overrides TypedData::setValue().
|
||||
*/
|
||||
public function setValue($value) {
|
||||
$this->value = isset($value) ? (float) $value : $value;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,11 +23,4 @@ class Integer extends TypedData {
|
|||
* @var integer
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* Overrides TypedData::setValue().
|
||||
*/
|
||||
public function setValue($value) {
|
||||
$this->value = isset($value) ? (int) $value : $value;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,11 +23,4 @@ class String extends TypedData {
|
|||
* @var string
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* Overrides TypedData::setValue().
|
||||
*/
|
||||
public function setValue($value) {
|
||||
$this->value = isset($value) ? (string) $value : $value;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,11 +22,4 @@ class Uri extends TypedData {
|
|||
* @var string
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* Overrides TypedData::setValue().
|
||||
*/
|
||||
public function setValue($value) {
|
||||
$this->value = isset($value) ? (string) $value : $value;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,10 +69,19 @@ abstract class TypedData implements TypedDataInterface {
|
|||
return (string) $this->getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements \Drupal\Core\TypedData\TypedDataInterface::getConstraints().
|
||||
*/
|
||||
public function getConstraints() {
|
||||
// @todo: Add the typed data manager as proper dependency.
|
||||
return typed_data()->getConstraints($this->definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements \Drupal\Core\TypedData\TypedDataInterface::validate().
|
||||
*/
|
||||
public function validate() {
|
||||
// TODO: Implement validate() method.
|
||||
// @todo: Add the typed data manager as proper dependency.
|
||||
return typed_data()->getValidator()->validate($this);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,8 +56,21 @@ interface TypedDataInterface {
|
|||
*/
|
||||
public function getString();
|
||||
|
||||
/**
|
||||
* Gets a list of validation constraints.
|
||||
*
|
||||
* @return array
|
||||
* Array of constraints, each being an instance of
|
||||
* \Symfony\Component\Validator\Constraint.
|
||||
*/
|
||||
public function getConstraints();
|
||||
|
||||
/**
|
||||
* Validates the currently set data value.
|
||||
*
|
||||
* @return \Symfony\Component\Validator\ConstraintViolationListInterface
|
||||
* A list of constraint violations. If the list is empty, validation
|
||||
* succeeded.
|
||||
*/
|
||||
public function validate();
|
||||
}
|
||||
|
|
|
@ -11,12 +11,31 @@ use InvalidArgumentException;
|
|||
use Drupal\Component\Plugin\PluginManagerBase;
|
||||
use Drupal\Core\Plugin\Discovery\CacheDecorator;
|
||||
use Drupal\Core\Plugin\Discovery\HookDiscovery;
|
||||
use Drupal\Core\TypedData\Validation\MetadataFactory;
|
||||
use Drupal\Core\Validation\ConstraintManager;
|
||||
use Drupal\Core\Validation\DrupalTranslator;
|
||||
use Symfony\Component\Validator\ValidatorInterface;
|
||||
use Symfony\Component\Validator\Validation;
|
||||
|
||||
/**
|
||||
* Manages data type plugins.
|
||||
*/
|
||||
class TypedDataManager extends PluginManagerBase {
|
||||
|
||||
/**
|
||||
* The validator used for validating typed data.
|
||||
*
|
||||
* @var \Symfony\Component\Validator\ValidatorInterface
|
||||
*/
|
||||
protected $validator;
|
||||
|
||||
/**
|
||||
* The validation constraint manager to use for instantiating constraints.
|
||||
*
|
||||
* @var \Drupal\Core\Validation\ConstraintManager
|
||||
*/
|
||||
protected $constraintManager;
|
||||
|
||||
/**
|
||||
* An array of typed data property prototypes.
|
||||
*
|
||||
|
@ -76,9 +95,8 @@ class TypedDataManager extends PluginManagerBase {
|
|||
* - list settings: An array of settings as required by the used
|
||||
* 'list class'. See the documentation of the list class for support or
|
||||
* required settings.
|
||||
* - constraints: An array of type specific value constraints, e.g. for data
|
||||
* of type 'entity' the 'entity type' and 'bundle' may be specified. See
|
||||
* the documentation of the data type 'class' for supported constraints.
|
||||
* - constraints: An array of validation constraints. See
|
||||
* \Drupal\Core\TypedData\TypedDataManager::getConstraints() for details.
|
||||
* - required: A boolean specifying whether a non-NULL value is mandatory.
|
||||
* Further keys may be supported in certain usages, e.g. for further keys
|
||||
* supported for entity field definitions see
|
||||
|
@ -212,4 +230,142 @@ class TypedDataManager extends PluginManagerBase {
|
|||
}
|
||||
return $property;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the validator for validating typed data.
|
||||
*
|
||||
* @param \Symfony\Component\Validator\ValidatorInterface $validator
|
||||
* The validator object to set.
|
||||
*/
|
||||
public function setValidator(ValidatorInterface $validator) {
|
||||
$this->validator = $validator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the validator for validating typed data.
|
||||
*
|
||||
* @return \Symfony\Component\Validator\ValidatorInterface
|
||||
* The validator object.
|
||||
*/
|
||||
public function getValidator() {
|
||||
if (!isset($this->validator)) {
|
||||
$this->validator = Validation::createValidatorBuilder()
|
||||
->setMetadataFactory(new MetadataFactory())
|
||||
->setTranslator(new DrupalTranslator())
|
||||
->getValidator();
|
||||
}
|
||||
return $this->validator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the validation constraint manager.
|
||||
*
|
||||
* The validation constraint manager is used to instantiate validation
|
||||
* constraint plugins.
|
||||
*
|
||||
* @param \Drupal\Core\Validation\ConstraintManager
|
||||
* The constraint manager to set.
|
||||
*/
|
||||
public function setValidationConstraintManager(ConstraintManager $constraintManager) {
|
||||
$this->constraintManager = $constraintManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the validation constraint manager.
|
||||
*
|
||||
* @return \Drupal\Core\Validation\ConstraintManager
|
||||
* The constraint manager.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* Any constraints defined for the data type, i.e. below the 'constraint' key
|
||||
* of the type's plugin definition, or constraints defined below the data
|
||||
* definition's constraint' key are taken into account.
|
||||
*
|
||||
* Constraints are defined via an array, having constraint plugin IDs as key
|
||||
* and constraint options as values, e.g.
|
||||
* @code
|
||||
* $constraints = array(
|
||||
* 'Range' => array('min' => 5, 'max' => 10),
|
||||
* 'NotBlank' => array(),
|
||||
* );
|
||||
* @endcode
|
||||
* Options have to be specified using another array if the constraint has more
|
||||
* than one or zero options. If it has exactly one option, the value should be
|
||||
* specified without nesting it into another array:
|
||||
* @code
|
||||
* $constraints = array(
|
||||
* 'EntityType' => 'node',
|
||||
* 'Bundle' => 'article',
|
||||
* );
|
||||
* @endcode
|
||||
*
|
||||
* Note that the specified constraints must be compatible with the data type,
|
||||
* e.g. for data of type 'entity' the 'EntityType' and 'Bundle' constraints
|
||||
* may be specified.
|
||||
*
|
||||
* @see \Drupal\Core\Validation\ConstraintManager
|
||||
*
|
||||
* @param array $definition
|
||||
* A data definition array.
|
||||
*
|
||||
* @return array
|
||||
* Array of constraints, each being an instance of
|
||||
* \Symfony\Component\Validator\Constraint.
|
||||
*/
|
||||
public function getConstraints($definition) {
|
||||
$constraints = array();
|
||||
// @todo: Figure out how to handle nested constraint structures as
|
||||
// collections.
|
||||
$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']));
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
// Add the NotNull constraint for required data.
|
||||
if (!empty($definition['required']) && empty($definition['constraints']['NotNull'])) {
|
||||
$constraints[] = $this->createValidationConstraint('NotNull', array());
|
||||
}
|
||||
return $constraints;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\TypedData\Validation\Metadata.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\TypedData\Validation;
|
||||
|
||||
use Drupal\Core\TypedData\TypedDataInterface;
|
||||
use Symfony\Component\Validator\ValidationVisitorInterface;
|
||||
use Symfony\Component\Validator\PropertyMetadataInterface;
|
||||
|
||||
/**
|
||||
* Typed data implementation of the validator MetadataInterface.
|
||||
*/
|
||||
class Metadata implements PropertyMetadataInterface {
|
||||
|
||||
/**
|
||||
* The name of the property, or empty if this is the root.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* The typed data object the metadata is about.
|
||||
*
|
||||
* @var \Drupal\Core\TypedData\TypedDataInterface
|
||||
*/
|
||||
protected $typedData;
|
||||
|
||||
/**
|
||||
* The metadata factory used.
|
||||
*
|
||||
* @var \Drupal\Core\TypedData\Validation\MetadataFactory
|
||||
*/
|
||||
protected $factory;
|
||||
|
||||
/**
|
||||
* Constructs the object.
|
||||
*
|
||||
* @param \Drupal\Core\TypedData\TypedDataInterface $typed_data
|
||||
* The typed data object the metadata is about.
|
||||
* @param $name
|
||||
* The name of the property to get metadata for. Leave empty, if
|
||||
* the data is the root of the typed data tree.
|
||||
* @param \Drupal\Core\TypedData\Validation\MetadataFactory $factory
|
||||
* The factory to use for instantiating property metadata.
|
||||
*/
|
||||
public function __construct(TypedDataInterface $typed_data, $name = '', MetadataFactory $factory) {
|
||||
$this->typedData = $typed_data;
|
||||
$this->name = $name;
|
||||
$this->factory = $factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements MetadataInterface::accept().
|
||||
*/
|
||||
public function accept(ValidationVisitorInterface $visitor, $typed_data, $group, $propertyPath) {
|
||||
|
||||
// @todo: Do we have to care about groups? Symfony class metadata has
|
||||
// $propagatedGroup.
|
||||
|
||||
$visitor->visit($this, $typed_data->getValue(), $group, $propertyPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements MetadataInterface::findConstraints().
|
||||
*/
|
||||
public function findConstraints($group) {
|
||||
return $this->typedData->getConstraints();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the property.
|
||||
*
|
||||
* @return string The property name.
|
||||
*/
|
||||
public function getPropertyName() {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the value of the property from the given container.
|
||||
*
|
||||
* @param mixed $container The container to extract the property value from.
|
||||
*
|
||||
* @return mixed The value of the property.
|
||||
*/
|
||||
public function getPropertyValue($container) {
|
||||
return $this->typedData->getValue();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\TypedData\Validation\MetadataFactory.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\TypedData\Validation;
|
||||
|
||||
use Drupal\Core\TypedData\ComplexDataInterface;
|
||||
use Drupal\Core\TypedData\ListInterface;
|
||||
use Drupal\Core\TypedData\TypedDataInterface;
|
||||
use Symfony\Component\Validator\MetadataFactoryInterface;
|
||||
|
||||
/**
|
||||
* Typed data implementation of the validator MetadataFactoryInterface.
|
||||
*/
|
||||
class MetadataFactory implements MetadataFactoryInterface {
|
||||
|
||||
/**
|
||||
* Implements MetadataFactoryInterface::getMetadataFor().
|
||||
*
|
||||
* @param \Drupal\Core\TypedData\TypedDataInterface $typed_data
|
||||
* Some typed data object containing the value to validate.
|
||||
* @param $name
|
||||
* (optional) The name of the property to get metadata for. Leave empty, if
|
||||
* the data is the root of the typed data tree.
|
||||
*/
|
||||
public function getMetadataFor($typed_data, $name = '') {
|
||||
if (!$typed_data instanceof TypedDataInterface) {
|
||||
throw new \InvalidArgumentException('The passed value must be a typed data object.');
|
||||
}
|
||||
$is_container = $typed_data instanceof ComplexDataInterface || $typed_data instanceof ListInterface;
|
||||
$class = '\Drupal\Core\TypedData\Validation\\' . ($is_container ? 'PropertyContainerMetadata' : 'Metadata');
|
||||
return new $class($typed_data, $name, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements MetadataFactoryInterface::hasMetadataFor().
|
||||
*/
|
||||
public function hasMetadataFor($value) {
|
||||
return $value instanceof TypedDataInterface;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\TypedData\Validation\PropertyContainerMetadata.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\TypedData\Validation;
|
||||
|
||||
use Drupal\Core\TypedData\ComplexDataInterface;
|
||||
use Drupal\Core\TypedData\ListInterface;
|
||||
use Symfony\Component\Validator\PropertyMetadataContainerInterface;
|
||||
use Symfony\Component\Validator\ValidationVisitorInterface;
|
||||
|
||||
/**
|
||||
* Typed data implementation of the validator MetadataInterface.
|
||||
*/
|
||||
class PropertyContainerMetadata extends Metadata implements PropertyMetadataContainerInterface {
|
||||
|
||||
/**
|
||||
* Overrides Metadata::accept().
|
||||
*/
|
||||
public function accept(ValidationVisitorInterface $visitor, $typed_data, $group, $propertyPath) {
|
||||
// To let all constraints properly handle empty structures, pass on NULL
|
||||
// if the data structure is empty. That way existing NotNull or NotBlank
|
||||
// constraints work as expected.
|
||||
if ($typed_data->isEmpty()) {
|
||||
$typed_data = NULL;
|
||||
}
|
||||
$visitor->visit($this, $typed_data, $group, $propertyPath);
|
||||
$pathPrefix = empty($propertyPath) ? '' : $propertyPath . '.';
|
||||
|
||||
if ($typed_data) {
|
||||
foreach ($typed_data as $name => $data) {
|
||||
$metadata = $this->factory->getMetadataFor($data, $name);
|
||||
$metadata->accept($visitor, $data, $group, $pathPrefix . $name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements PropertyMetadataContainerInterface::getPropertyMetadata().
|
||||
*/
|
||||
public function getPropertyMetadata($property_name) {
|
||||
if ($this->typedData instanceof ListInterface) {
|
||||
return array(new Metadata($this->typedData[$property_name], $property_name));
|
||||
}
|
||||
elseif ($this->typedData instanceof ComplexDataInterface) {
|
||||
return array(new Metadata($this->typedData->get($property_name), $property_name));
|
||||
}
|
||||
else {
|
||||
throw new \LogicException("There are no known properties.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Validation\ConstraintManager.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Validation;
|
||||
|
||||
use Drupal\Component\Plugin\Factory\DefaultFactory;
|
||||
use Drupal\Component\Plugin\PluginManagerBase;
|
||||
use Drupal\Component\Plugin\Discovery\StaticDiscoveryDecorator;
|
||||
use Drupal\Component\Plugin\Discovery\DerivativeDiscoveryDecorator;
|
||||
use Drupal\Component\Plugin\Discovery\ProcessDecorator;
|
||||
use Drupal\Core\Plugin\Discovery\AlterDecorator;
|
||||
use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
|
||||
use Drupal\Core\Plugin\Discovery\CacheDecorator;
|
||||
|
||||
/**
|
||||
* Constraint plugin manager.
|
||||
*
|
||||
* Manages validation constraints based upon
|
||||
* \Symfony\Component\Validator\Constraint, whereas Symfony constraints are
|
||||
* added in manually during construction. Constraint options are passed on as
|
||||
* plugin configuration during plugin instantiation.
|
||||
*
|
||||
* While core does not prefix constraint plugins, modules have to prefix them
|
||||
* with the module name in order to avoid any naming conflicts. E.g. a "profile"
|
||||
* module would have to prefix any constraints with "Profile".
|
||||
*
|
||||
* Constraint plugins may specify data types to which support is limited via the
|
||||
* 'type' key of plugin definitions. Valid values are any types registered via
|
||||
* the typed data API, or an array of multiple type names. For supporting all
|
||||
* types FALSE may be specified. The key defaults to an empty array, i.e. no
|
||||
* types are supported.
|
||||
*/
|
||||
class ConstraintManager extends PluginManagerBase {
|
||||
|
||||
/**
|
||||
* Overrides \Drupal\Component\Plugin\PluginManagerBase::__construct().
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->discovery = new AnnotatedClassDiscovery('Validation', 'Constraint');
|
||||
$this->discovery = new StaticDiscoveryDecorator($this->discovery, array($this, 'registerDefinitions'));
|
||||
$this->discovery = new DerivativeDiscoveryDecorator($this->discovery);
|
||||
$this->discovery = new ProcessDecorator($this->discovery, array($this, 'processDefinition'));
|
||||
$this->discovery = new AlterDecorator($this->discovery, 'validation_constraint');
|
||||
$this->discovery = new CacheDecorator($this->discovery, 'validation_constraints:' . language(LANGUAGE_TYPE_INTERFACE)->langcode);
|
||||
|
||||
$this->factory = new DefaultFactory($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for registering definitions for constraints shipped with Symfony.
|
||||
*
|
||||
* @see ConstraintManager::__construct()
|
||||
*/
|
||||
public function registerDefinitions() {
|
||||
$this->discovery->setDefinition('Null', array(
|
||||
'label' => t('Null'),
|
||||
'class' => '\Symfony\Component\Validator\Constraints\Null',
|
||||
'type' => FALSE,
|
||||
));
|
||||
$this->discovery->setDefinition('NotNull', array(
|
||||
'label' => t('Not null'),
|
||||
'class' => '\Symfony\Component\Validator\Constraints\NotNull',
|
||||
'type' => FALSE,
|
||||
));
|
||||
$this->discovery->setDefinition('Blank', array(
|
||||
'label' => t('Blank'),
|
||||
'class' => '\Symfony\Component\Validator\Constraints\Blank',
|
||||
'type' => FALSE,
|
||||
));
|
||||
$this->discovery->setDefinition('NotBlank', array(
|
||||
'label' => t('Not blank'),
|
||||
'class' => '\Symfony\Component\Validator\Constraints\NotBlank',
|
||||
'type' => FALSE,
|
||||
));
|
||||
$this->discovery->setDefinition('Email', array(
|
||||
'label' => t('E-mail'),
|
||||
'class' => '\Symfony\Component\Validator\Constraints\Email',
|
||||
'type' => array('string'),
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Process definition callback for the ProcessDecorator.
|
||||
*/
|
||||
public function processDefinition(&$definition, $plugin_id) {
|
||||
// Make sure 'type' is set and either an array or FALSE.
|
||||
if (!isset($definition['type'])) {
|
||||
$definition['type'] = array();
|
||||
}
|
||||
elseif ($definition['type'] !== FALSE && !is_array($definition['type'])) {
|
||||
$definition['type'] = array($definition['type']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of constraints that support the given type.
|
||||
*
|
||||
* @param string $type
|
||||
* The type to filter on.
|
||||
*
|
||||
* @return array
|
||||
* An array of constraint plugin definitions supporting the given type,
|
||||
* keyed by constraint name (plugin ID).
|
||||
*/
|
||||
public function getDefinitionsByType($type) {
|
||||
$definitions = array();
|
||||
foreach ($this->getDefinitions() as $plugin_id => $definition) {
|
||||
if ($definition['type'] === FALSE || in_array($type, $definition['type'])) {
|
||||
$definitions[$plugin_id] = $definition;
|
||||
}
|
||||
}
|
||||
return $definitions;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Validation\DrupalTranslator.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Validation;
|
||||
|
||||
use Symfony\Component\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
* Translates strings using Drupal's translation system.
|
||||
*
|
||||
* This class is used by the Symfony validator to translate violation messages.
|
||||
*/
|
||||
class DrupalTranslator implements TranslatorInterface {
|
||||
|
||||
/**
|
||||
* The locale used for translating.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $locale;
|
||||
|
||||
/**
|
||||
* Implements \Symfony\Component\Translation\TranslatorInterface::trans().
|
||||
*/
|
||||
public function trans($id, array $parameters = array(), $domain = NULL, $locale = NULL) {
|
||||
|
||||
return t($id, $this->processParameters($parameters), $this->getOptions($domain, $locale));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements \Symfony\Component\Translation\TranslatorInterface::transChoice().
|
||||
*/
|
||||
public function transChoice($id, $number, array $parameters = array(), $domain = NULL, $locale = NULL) {
|
||||
// Violation messages can separated singular and plural versions by "|".
|
||||
$ids = explode('|', $id);
|
||||
|
||||
if (!isset($ids[1])) {
|
||||
throw new \InvalidArgumentException(sprintf('The message "%s" cannot be pluralized, because it is missing a plural (e.g. "There is one apple|There are @count apples").', $id));
|
||||
}
|
||||
return format_plural($number, $ids[0], $ids[1], $this->processParameters($parameters), $this->getOptions($domain, $locale));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements \Symfony\Component\Translation\TranslatorInterface::setLocale().
|
||||
*/
|
||||
public function setLocale($locale) {
|
||||
$this->locale = $locale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements \Symfony\Component\Translation\TranslatorInterface::getLocale().
|
||||
*/
|
||||
public function getLocale() {
|
||||
return $this->locale ? $this->locale : language(LANGUAGE_TYPE_INTERFACE)->langcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the parameters array for use with t().
|
||||
*/
|
||||
protected function processParameters(array $parameters) {
|
||||
$return = array();
|
||||
foreach ($parameters as $key => $value) {
|
||||
if (is_object($value)) {
|
||||
// t() does not work will objects being passed as replacement strings.
|
||||
}
|
||||
// Check for symfony replacement patterns in the form "{{ name }}".
|
||||
elseif (strpos($key, '{{ ') === 0 && strrpos($key, ' }}') == strlen($key) - 3) {
|
||||
// Transform it into a Drupal pattern using the format %name.
|
||||
$key = '%' . substr($key, 3, strlen($key) - 6);
|
||||
$return[$key] = $value;
|
||||
}
|
||||
else {
|
||||
$return[$key] = $value;
|
||||
}
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns options suitable for use with t().
|
||||
*/
|
||||
protected function getOptions($domain = NULL, $locale = NULL) {
|
||||
// We do not support domains, so we ignore this parameter.
|
||||
// If locale is left NULL, t() will default to the interface language.
|
||||
$locale = isset($locale) ? $locale : $this->locale;
|
||||
return array('langcode' => $locale);
|
||||
}
|
||||
}
|
|
@ -56,7 +56,7 @@ class TestItemTest extends FieldItemUnitTestBase {
|
|||
public function testTestItem() {
|
||||
// Verify entity creation.
|
||||
$entity = entity_create('entity_test', array());
|
||||
$value = $this->randomName();
|
||||
$value = rand(1, 10);
|
||||
$entity->field_test = $value;
|
||||
$entity->name->value = $this->randomName();
|
||||
$entity->save();
|
||||
|
@ -70,7 +70,7 @@ class TestItemTest extends FieldItemUnitTestBase {
|
|||
$this->assertEqual($entity->field_test[0]->value, $value);
|
||||
|
||||
// Verify changing the field value.
|
||||
$new_value = $this->randomName();
|
||||
$new_value = rand(1, 10);
|
||||
$entity->field_test->value = $new_value;
|
||||
$this->assertEqual($entity->field_test->value, $new_value);
|
||||
|
||||
|
|
|
@ -184,9 +184,9 @@ class Tables {
|
|||
$next_index_prefix = $relationship_specifier;
|
||||
}
|
||||
// Check for a valid relationship.
|
||||
if (isset($propertyDefinitions[$relationship_specifier]['constraints']['entity type']) && isset($propertyDefinitions[$relationship_specifier]['settings']['id source'])) {
|
||||
if (isset($propertyDefinitions[$relationship_specifier]['constraints']['EntityType']) && isset($propertyDefinitions[$relationship_specifier]['settings']['id source'])) {
|
||||
// If it is, use the entity type.
|
||||
$entity_type = $propertyDefinitions[$relationship_specifier]['constraints']['entity type'];
|
||||
$entity_type = $propertyDefinitions[$relationship_specifier]['constraints']['EntityType'];
|
||||
$entity_info = entity_get_info($entity_type);
|
||||
// Add the new entity base table using the table and sql column.
|
||||
$join_condition= '%alias.' . $entity_info['entity_keys']['id'] . " = $table.$sql_column";
|
||||
|
@ -196,7 +196,7 @@ class Tables {
|
|||
$index_prefix .= "$next_index_prefix.";
|
||||
}
|
||||
else {
|
||||
throw new QueryException(format_string('Invalid specifier @next.', array('@next' => $next)));
|
||||
throw new QueryException(format_string('Invalid specifier @next.', array('@next' => $relationship_specifier)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -333,9 +333,9 @@ class EntityFieldTest extends WebTestBase {
|
|||
$definition = array(
|
||||
'type' => 'entity',
|
||||
'constraints' => array(
|
||||
'entity type' => $entity_type,
|
||||
'EntityType' => $entity_type,
|
||||
),
|
||||
'label' => t('Test entity'),
|
||||
'label' => 'Test entity',
|
||||
);
|
||||
$wrapped_entity = typed_data()->create($definition);
|
||||
$definitions = $wrapped_entity->getPropertyDefinitions($definition);
|
||||
|
@ -432,8 +432,7 @@ class EntityFieldTest extends WebTestBase {
|
|||
}
|
||||
|
||||
/**
|
||||
* Tests working with entity properties based upon data structure and data
|
||||
* list interfaces.
|
||||
* Tests working with the entity based upon the TypedData API.
|
||||
*/
|
||||
public function testDataStructureInterfaces() {
|
||||
// All entity variations have to have the same results.
|
||||
|
@ -454,14 +453,15 @@ class EntityFieldTest extends WebTestBase {
|
|||
$entity_definition = array(
|
||||
'type' => 'entity',
|
||||
'constraints' => array(
|
||||
'entity type' => $entity_type,
|
||||
'EntityType' => $entity_type,
|
||||
),
|
||||
'label' => t('Test entity'),
|
||||
'label' => 'Test entity',
|
||||
);
|
||||
$wrapped_entity = typed_data()->create($entity_definition, $entity);
|
||||
|
||||
// For the test we navigate through the tree of contained properties and get
|
||||
// all contained strings, limited by a certain depth.
|
||||
// Test using the whole tree of typed data by navigating through the tree of
|
||||
// contained properties and getting all contained strings, limited by a
|
||||
// certain depth.
|
||||
$strings = array();
|
||||
$this->getContainedStrings($wrapped_entity, 0, $strings);
|
||||
|
||||
|
@ -503,6 +503,52 @@ class EntityFieldTest extends WebTestBase {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests validation constraints provided by the Entity API.
|
||||
*/
|
||||
public function testEntityConstraintValidation() {
|
||||
$entity = $this->createTestEntity('entity_test');
|
||||
$entity->save();
|
||||
$entity_definition = array(
|
||||
'type' => 'entity',
|
||||
'constraints' => array(
|
||||
'EntityType' => 'entity_test',
|
||||
),
|
||||
'label' => 'Test entity',
|
||||
);
|
||||
$wrapped_entity = typed_data()->create($entity_definition, $entity);
|
||||
|
||||
// Test validation the typed data object.
|
||||
$violations = $wrapped_entity->validate();
|
||||
$this->assertEqual($violations->count(), 0);
|
||||
|
||||
// Test validating an entity of the wrong type.
|
||||
$node = $this->drupalCreateNode(array('type' => 'page'));
|
||||
$wrapped_entity->setValue($node);
|
||||
$violations = $wrapped_entity->validate();
|
||||
$this->assertEqual($violations->count(), 1);
|
||||
|
||||
// Test bundle validation.
|
||||
$entity_definition = array(
|
||||
'type' => 'entity',
|
||||
'constraints' => array(
|
||||
'EntityType' => 'node',
|
||||
'Bundle' => 'article',
|
||||
),
|
||||
'label' => 'Test node',
|
||||
);
|
||||
$wrapped_entity = typed_data()->create($entity_definition, $node);
|
||||
|
||||
$violations = $wrapped_entity->validate();
|
||||
$this->assertEqual($violations->count(), 1);
|
||||
|
||||
$node->type = 'article';
|
||||
$node->save();
|
||||
$wrapped_entity->setValue($node);
|
||||
$violations = $wrapped_entity->validate();
|
||||
$this->assertEqual($violations->count(), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests getting processed property values via a computed property.
|
||||
*/
|
||||
|
|
|
@ -16,6 +16,13 @@ use DateInterval;
|
|||
*/
|
||||
class TypedDataTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* The typed data manager to use.
|
||||
*
|
||||
* @var \Drupal\Core\TypedData\TypedDataManager
|
||||
*/
|
||||
protected $typedData;
|
||||
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Test typed data objects',
|
||||
|
@ -24,120 +31,277 @@ class TypedDataTest extends WebTestBase {
|
|||
);
|
||||
}
|
||||
|
||||
public function setUp() {
|
||||
parent::setup();
|
||||
$this->typedData = typed_data();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the basics around constructing and working with data wrappers.
|
||||
* Tests the basics around constructing and working with typed data objects.
|
||||
*/
|
||||
public function testGetAndSet() {
|
||||
// Boolean type.
|
||||
$wrapper = $this->createTypedData(array('type' => 'boolean'), TRUE);
|
||||
$this->assertTrue($wrapper->getValue() === TRUE, 'Boolean value was fetched.');
|
||||
$wrapper->setValue(FALSE);
|
||||
$this->assertTrue($wrapper->getValue() === FALSE, 'Boolean value was changed.');
|
||||
$this->assertTrue(is_string($wrapper->getString()), 'Boolean value was converted to string');
|
||||
$wrapper->setValue(NULL);
|
||||
$this->assertNull($wrapper->getValue(), 'Boolean wrapper is null-able.');
|
||||
$typed_data = $this->createTypedData(array('type' => 'boolean'), TRUE);
|
||||
$this->assertTrue($typed_data->getValue() === TRUE, 'Boolean value was fetched.');
|
||||
$this->assertEqual($typed_data->validate()->count(), 0);
|
||||
$typed_data->setValue(FALSE);
|
||||
$this->assertTrue($typed_data->getValue() === FALSE, 'Boolean value was changed.');
|
||||
$this->assertEqual($typed_data->validate()->count(), 0);
|
||||
$this->assertTrue(is_string($typed_data->getString()), 'Boolean value was converted to string');
|
||||
$typed_data->setValue(NULL);
|
||||
$this->assertNull($typed_data->getValue(), 'Boolean wrapper is null-able.');
|
||||
$this->assertEqual($typed_data->validate()->count(), 0);
|
||||
$typed_data->setValue('invalid');
|
||||
$this->assertEqual($typed_data->validate()->count(), 1, 'Validation detected invalid value.');
|
||||
|
||||
// String type.
|
||||
$value = $this->randomString();
|
||||
$wrapper = $this->createTypedData(array('type' => 'string'), $value);
|
||||
$this->assertTrue($wrapper->getValue() === $value, 'String value was fetched.');
|
||||
$typed_data = $this->createTypedData(array('type' => 'string'), $value);
|
||||
$this->assertTrue($typed_data->getValue() === $value, 'String value was fetched.');
|
||||
$this->assertEqual($typed_data->validate()->count(), 0);
|
||||
$new_value = $this->randomString();
|
||||
$wrapper->setValue($new_value);
|
||||
$this->assertTrue($wrapper->getValue() === $new_value, 'String value was changed.');
|
||||
$typed_data->setValue($new_value);
|
||||
$this->assertTrue($typed_data->getValue() === $new_value, 'String value was changed.');
|
||||
$this->assertEqual($typed_data->validate()->count(), 0);
|
||||
// Funky test.
|
||||
$this->assertTrue(is_string($wrapper->getString()), 'String value was converted to string');
|
||||
$wrapper->setValue(NULL);
|
||||
$this->assertNull($wrapper->getValue(), 'String wrapper is null-able.');
|
||||
$this->assertTrue(is_string($typed_data->getString()), 'String value was converted to string');
|
||||
$typed_data->setValue(NULL);
|
||||
$this->assertNull($typed_data->getValue(), 'String wrapper is null-able.');
|
||||
$this->assertEqual($typed_data->validate()->count(), 0);
|
||||
$typed_data->setValue(array('no string'));
|
||||
$this->assertEqual($typed_data->validate()->count(), 1, 'Validation detected invalid value.');
|
||||
|
||||
// Integer type.
|
||||
$value = rand();
|
||||
$wrapper = $this->createTypedData(array('type' => 'integer'), $value);
|
||||
$this->assertTrue($wrapper->getValue() === $value, 'Integer value was fetched.');
|
||||
$typed_data = $this->createTypedData(array('type' => 'integer'), $value);
|
||||
$this->assertTrue($typed_data->getValue() === $value, 'Integer value was fetched.');
|
||||
$this->assertEqual($typed_data->validate()->count(), 0);
|
||||
$new_value = rand();
|
||||
$wrapper->setValue($new_value);
|
||||
$this->assertTrue($wrapper->getValue() === $new_value, 'Integer value was changed.');
|
||||
$this->assertTrue(is_string($wrapper->getString()), 'Integer value was converted to string');
|
||||
$wrapper->setValue(NULL);
|
||||
$this->assertNull($wrapper->getValue(), 'Integer wrapper is null-able.');
|
||||
$typed_data->setValue($new_value);
|
||||
$this->assertTrue($typed_data->getValue() === $new_value, 'Integer value was changed.');
|
||||
$this->assertTrue(is_string($typed_data->getString()), 'Integer value was converted to string');
|
||||
$this->assertEqual($typed_data->validate()->count(), 0);
|
||||
$typed_data->setValue(NULL);
|
||||
$this->assertNull($typed_data->getValue(), 'Integer wrapper is null-able.');
|
||||
$this->assertEqual($typed_data->validate()->count(), 0);
|
||||
$typed_data->setValue('invalid');
|
||||
$this->assertEqual($typed_data->validate()->count(), 1, 'Validation detected invalid value.');
|
||||
|
||||
// Float type.
|
||||
$value = 123.45;
|
||||
$wrapper = $this->createTypedData(array('type' => 'float'), $value);
|
||||
$this->assertTrue($wrapper->getValue() === $value, 'Float value was fetched.');
|
||||
$typed_data = $this->createTypedData(array('type' => 'float'), $value);
|
||||
$this->assertTrue($typed_data->getValue() === $value, 'Float value was fetched.');
|
||||
$this->assertEqual($typed_data->validate()->count(), 0);
|
||||
$new_value = 678.90;
|
||||
$wrapper->setValue($new_value);
|
||||
$this->assertTrue($wrapper->getValue() === $new_value, 'Float value was changed.');
|
||||
$this->assertTrue(is_string($wrapper->getString()), 'Float value was converted to string');
|
||||
$wrapper->setValue(NULL);
|
||||
$this->assertNull($wrapper->getValue(), 'Float wrapper is null-able.');
|
||||
$typed_data->setValue($new_value);
|
||||
$this->assertTrue($typed_data->getValue() === $new_value, 'Float value was changed.');
|
||||
$this->assertTrue(is_string($typed_data->getString()), 'Float value was converted to string');
|
||||
$this->assertEqual($typed_data->validate()->count(), 0);
|
||||
$typed_data->setValue(NULL);
|
||||
$this->assertNull($typed_data->getValue(), 'Float wrapper is null-able.');
|
||||
$this->assertEqual($typed_data->validate()->count(), 0);
|
||||
$typed_data->setValue('invalid');
|
||||
$this->assertEqual($typed_data->validate()->count(), 1, 'Validation detected invalid value.');
|
||||
|
||||
// Date type.
|
||||
$value = new DrupalDateTime(REQUEST_TIME);
|
||||
$wrapper = $this->createTypedData(array('type' => 'date'), $value);
|
||||
$this->assertTrue($wrapper->getValue() === $value, 'Date value was fetched.');
|
||||
$typed_data = $this->createTypedData(array('type' => 'date'), $value);
|
||||
$this->assertTrue($typed_data->getValue() === $value, 'Date value was fetched.');
|
||||
$this->assertEqual($typed_data->validate()->count(), 0);
|
||||
$new_value = REQUEST_TIME + 1;
|
||||
$wrapper->setValue($new_value);
|
||||
$this->assertTrue($wrapper->getValue()->getTimestamp() === $new_value, 'Date value was changed and set by timestamp.');
|
||||
$wrapper->setValue('2000-01-01');
|
||||
$this->assertTrue($wrapper->getValue()->format('Y-m-d') == '2000-01-01', 'Date value was changed and set by date string.');
|
||||
$this->assertTrue(is_string($wrapper->getString()), 'Date value was converted to string');
|
||||
$wrapper->setValue(NULL);
|
||||
$this->assertNull($wrapper->getValue(), 'Date wrapper is null-able.');
|
||||
$typed_data->setValue($new_value);
|
||||
$this->assertTrue($typed_data->getValue()->getTimestamp() === $new_value, 'Date value was changed and set by timestamp.');
|
||||
$this->assertEqual($typed_data->validate()->count(), 0);
|
||||
$typed_data->setValue('2000-01-01');
|
||||
$this->assertTrue($typed_data->getValue()->format('Y-m-d') == '2000-01-01', 'Date value was changed and set by date string.');
|
||||
$this->assertTrue(is_string($typed_data->getString()), 'Date value was converted to string');
|
||||
$this->assertEqual($typed_data->validate()->count(), 0);
|
||||
$typed_data->setValue(NULL);
|
||||
$this->assertNull($typed_data->getValue(), 'Date wrapper is null-able.');
|
||||
$this->assertEqual($typed_data->validate()->count(), 0);
|
||||
$typed_data->setValue('invalid');
|
||||
$this->assertEqual($typed_data->validate()->count(), 1, 'Validation detected invalid value.');
|
||||
|
||||
// Duration type.
|
||||
$value = new DateInterval('PT20S');
|
||||
$wrapper = $this->createTypedData(array('type' => 'duration'), $value);
|
||||
$this->assertTrue($wrapper->getValue() === $value, 'Duration value was fetched.');
|
||||
$wrapper->setValue(10);
|
||||
$this->assertTrue($wrapper->getValue()->s == 10, 'Duration value was changed and set by time span in seconds.');
|
||||
$wrapper->setValue('P40D');
|
||||
$this->assertTrue($wrapper->getValue()->d == 40, 'Duration value was changed and set by duration string.');
|
||||
$this->assertTrue(is_string($wrapper->getString()), 'Duration value was converted to string');
|
||||
$typed_data = $this->createTypedData(array('type' => 'duration'), $value);
|
||||
$this->assertTrue($typed_data->getValue() === $value, 'Duration value was fetched.');
|
||||
$this->assertEqual($typed_data->validate()->count(), 0);
|
||||
$typed_data->setValue(10);
|
||||
$this->assertTrue($typed_data->getValue()->s == 10, 'Duration value was changed and set by time span in seconds.');
|
||||
$this->assertEqual($typed_data->validate()->count(), 0);
|
||||
$typed_data->setValue('P40D');
|
||||
$this->assertTrue($typed_data->getValue()->d == 40, 'Duration value was changed and set by duration string.');
|
||||
$this->assertTrue(is_string($typed_data->getString()), 'Duration value was converted to string');
|
||||
$this->assertEqual($typed_data->validate()->count(), 0);
|
||||
// Test getting the string and passing it back as value.
|
||||
$duration = $wrapper->getString();
|
||||
$wrapper->setValue($duration);
|
||||
$this->assertEqual($wrapper->getString(), $duration, 'Duration formatted as string can be used to set the duration value.');
|
||||
$wrapper->setValue(NULL);
|
||||
$this->assertNull($wrapper->getValue(), 'Duration wrapper is null-able.');
|
||||
|
||||
// Generate some files that will be used to test the URI and the binary
|
||||
// data types.
|
||||
$files = $this->drupalGetTestFiles('image');
|
||||
$duration = $typed_data->getString();
|
||||
$typed_data->setValue($duration);
|
||||
$this->assertEqual($typed_data->getString(), $duration, 'Duration formatted as string can be used to set the duration value.');
|
||||
$this->assertEqual($typed_data->validate()->count(), 0);
|
||||
$typed_data->setValue(NULL);
|
||||
$this->assertNull($typed_data->getValue(), 'Duration wrapper is null-able.');
|
||||
$this->assertEqual($typed_data->validate()->count(), 0);
|
||||
$typed_data->setValue('invalid');
|
||||
$this->assertEqual($typed_data->validate()->count(), 1, 'Validation detected invalid value.');
|
||||
|
||||
// URI type.
|
||||
$wrapper = $this->createTypedData(array('type' => 'uri'), $files[0]->uri);
|
||||
$this->assertTrue($wrapper->getValue() === $files[0]->uri, 'URI value was fetched.');
|
||||
$wrapper->setValue($files[1]->uri);
|
||||
$this->assertTrue($wrapper->getValue() === $files[1]->uri, 'URI value was changed.');
|
||||
$this->assertTrue(is_string($wrapper->getString()), 'URI value was converted to string');
|
||||
$wrapper->setValue(NULL);
|
||||
$this->assertNull($wrapper->getValue(), 'URI wrapper is null-able.');
|
||||
$uri = 'http://example.com/foo/';
|
||||
$typed_data = $this->createTypedData(array('type' => 'uri'), $uri);
|
||||
$this->assertTrue($typed_data->getValue() === $uri, 'URI value was fetched.');
|
||||
$this->assertEqual($typed_data->validate()->count(), 0);
|
||||
$typed_data->setValue($uri . 'bar.txt');
|
||||
$this->assertTrue($typed_data->getValue() === $uri . 'bar.txt', 'URI value was changed.');
|
||||
$this->assertTrue(is_string($typed_data->getString()), 'URI value was converted to string');
|
||||
$this->assertEqual($typed_data->validate()->count(), 0);
|
||||
$typed_data->setValue(NULL);
|
||||
$this->assertNull($typed_data->getValue(), 'URI wrapper is null-able.');
|
||||
$this->assertEqual($typed_data->validate()->count(), 0);
|
||||
$typed_data->setValue('invalid');
|
||||
$this->assertEqual($typed_data->validate()->count(), 1, 'Validation detected invalid value.');
|
||||
|
||||
// Generate some files that will be used to test the binary data type.
|
||||
$files = $this->drupalGetTestFiles('image');
|
||||
|
||||
// Email type.
|
||||
$value = $this->randomString();
|
||||
$wrapper = $this->createTypedData(array('type' => 'email'), $value);
|
||||
$this->assertIdentical($wrapper->getValue(), $value, 'E-mail value was fetched.');
|
||||
|
||||
$typed_data = $this->createTypedData(array('type' => 'email'), $value);
|
||||
$this->assertIdentical($typed_data->getValue(), $value, 'E-mail value was fetched.');
|
||||
$new_value = 'test@example.com';
|
||||
$wrapper->setValue($new_value);
|
||||
$this->assertIdentical($wrapper->getValue(), $new_value, 'E-mail value was changed.');
|
||||
$this->assertTrue(is_string($wrapper->getString()), 'E-mail value was converted to string');
|
||||
|
||||
$wrapper->setValue(NULL);
|
||||
$this->assertNull($wrapper->getValue(), 'E-mail wrapper is null-able.');
|
||||
$typed_data->setValue($new_value);
|
||||
$this->assertIdentical($typed_data->getValue(), $new_value, 'E-mail value was changed.');
|
||||
$this->assertTrue(is_string($typed_data->getString()), 'E-mail value was converted to string');
|
||||
$this->assertEqual($typed_data->validate()->count(), 0);
|
||||
$typed_data->setValue(NULL);
|
||||
$this->assertNull($typed_data->getValue(), 'E-mail wrapper is null-able.');
|
||||
$this->assertEqual($typed_data->validate()->count(), 0);
|
||||
$typed_data->setValue('invalidATexample.com');
|
||||
$this->assertEqual($typed_data->validate()->count(), 1, 'Validation detected invalid value.');
|
||||
|
||||
// Binary type.
|
||||
$wrapper = $this->createTypedData(array('type' => 'binary'), $files[0]->uri);
|
||||
$this->assertTrue(is_resource($wrapper->getValue()), 'Binary value was fetched.');
|
||||
$typed_data = $this->createTypedData(array('type' => 'binary'), $files[0]->uri);
|
||||
$this->assertTrue(is_resource($typed_data->getValue()), 'Binary value was fetched.');
|
||||
$this->assertEqual($typed_data->validate()->count(), 0);
|
||||
// Try setting by URI.
|
||||
$wrapper->setValue($files[1]->uri);
|
||||
$this->assertEqual(is_resource($wrapper->getValue()), fopen($files[1]->uri, 'r'), 'Binary value was changed.');
|
||||
$this->assertTrue(is_string($wrapper->getString()), 'Binary value was converted to string');
|
||||
$typed_data->setValue($files[1]->uri);
|
||||
$this->assertEqual(is_resource($typed_data->getValue()), fopen($files[1]->uri, 'r'), 'Binary value was changed.');
|
||||
$this->assertTrue(is_string($typed_data->getString()), 'Binary value was converted to string');
|
||||
$this->assertEqual($typed_data->validate()->count(), 0);
|
||||
// Try setting by resource.
|
||||
$wrapper->setValue(fopen($files[2]->uri, 'r'));
|
||||
$this->assertEqual(is_resource($wrapper->getValue()), fopen($files[2]->uri, 'r'), 'Binary value was changed.');
|
||||
$this->assertTrue(is_string($wrapper->getString()), 'Binary value was converted to string');
|
||||
$wrapper->setValue(NULL);
|
||||
$this->assertNull($wrapper->getValue(), 'Binary wrapper is null-able.');
|
||||
$typed_data->setValue(fopen($files[2]->uri, 'r'));
|
||||
$this->assertEqual(is_resource($typed_data->getValue()), fopen($files[2]->uri, 'r'), 'Binary value was changed.');
|
||||
$this->assertTrue(is_string($typed_data->getString()), 'Binary value was converted to string');
|
||||
$this->assertEqual($typed_data->validate()->count(), 0);
|
||||
$typed_data->setValue(NULL);
|
||||
$this->assertNull($typed_data->getValue(), 'Binary wrapper is null-able.');
|
||||
$this->assertEqual($typed_data->validate()->count(), 0);
|
||||
$typed_data->setValue('invalid');
|
||||
$this->assertEqual($typed_data->validate()->count(), 1, 'Validation detected invalid value.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests typed data validation.
|
||||
*/
|
||||
public function testTypedDataValidation() {
|
||||
$definition = array(
|
||||
'type' => 'integer',
|
||||
'constraints' => array(
|
||||
'Range' => array('min' => 5),
|
||||
),
|
||||
);
|
||||
$violations = $this->typedData->create($definition, 10)->validate();
|
||||
$this->assertEqual($violations->count(), 0);
|
||||
|
||||
$integer = $this->typedData->create($definition, 1);
|
||||
$violations = $integer->validate();
|
||||
$this->assertEqual($violations->count(), 1);
|
||||
|
||||
// Test translating violation messages.
|
||||
$message = t('This value should be %limit or more.', array('%limit' => 5));
|
||||
$this->assertEqual($violations[0]->getMessage(), $message, 'Translated violation message retrieved.');
|
||||
$this->assertEqual($violations[0]->getPropertyPath(), '');
|
||||
$this->assertIdentical($violations[0]->getRoot(), $integer, 'Root object returned.');
|
||||
|
||||
// Test translating violation messages when pluralization is used.
|
||||
$definition = array(
|
||||
'type' => 'string',
|
||||
'constraints' => array(
|
||||
'Length' => array('min' => 10),
|
||||
),
|
||||
);
|
||||
$violations = $this->typedData->create($definition, "short")->validate();
|
||||
$this->assertEqual($violations->count(), 1);
|
||||
$message = t('This value is too short. It should have %limit characters or more.', array('%limit' => 10));
|
||||
$this->assertEqual($violations[0]->getMessage(), $message, 'Translated violation message retrieved.');
|
||||
|
||||
// Test having multiple violations.
|
||||
$definition = array(
|
||||
'type' => 'integer',
|
||||
'constraints' => array(
|
||||
'Range' => array('min' => 5),
|
||||
'Null' => array(),
|
||||
),
|
||||
);
|
||||
$violations = $this->typedData->create($definition, 10)->validate();
|
||||
$this->assertEqual($violations->count(), 1);
|
||||
$violations = $this->typedData->create($definition, 1)->validate();
|
||||
$this->assertEqual($violations->count(), 2);
|
||||
|
||||
// Test validating property containers and make sure the NotNull and Null
|
||||
// constraints work with typed data containers.
|
||||
$definition = array(
|
||||
'type' => 'integer_field',
|
||||
'constraints' => array(
|
||||
'NotNull' => array(),
|
||||
),
|
||||
);
|
||||
$field_item = $this->typedData->create($definition, array('value' => 10));
|
||||
$violations = $field_item->validate();
|
||||
$this->assertEqual($violations->count(), 0);
|
||||
|
||||
$field_item = $this->typedData->create($definition, array('value' => 'no integer'));
|
||||
$violations = $field_item->validate();
|
||||
$this->assertEqual($violations->count(), 1);
|
||||
$this->assertEqual($violations[0]->getPropertyPath(), 'value');
|
||||
|
||||
// Test that the field item may not be empty.
|
||||
$field_item = $this->typedData->create($definition);
|
||||
$violations = $field_item->validate();
|
||||
$this->assertEqual($violations->count(), 1);
|
||||
|
||||
// Test the Null constraint with typed data containers.
|
||||
$definition = array(
|
||||
'type' => 'integer_field',
|
||||
'constraints' => array(
|
||||
'Null' => array(),
|
||||
),
|
||||
);
|
||||
$field_item = $this->typedData->create($definition, array('value' => 10));
|
||||
$violations = $field_item->validate();
|
||||
$this->assertEqual($violations->count(), 1);
|
||||
$field_item = $this->typedData->create($definition);
|
||||
$violations = $field_item->validate();
|
||||
$this->assertEqual($violations->count(), 0);
|
||||
|
||||
// Test getting constraint definitions by type.
|
||||
$definitions = $this->typedData->getValidationConstraintManager()->getDefinitionsByType('entity');
|
||||
$this->assertTrue(isset($definitions['EntityType']), 'Constraint plugin found for type entity.');
|
||||
$this->assertTrue(isset($definitions['Null']), 'Constraint plugin found for type entity.');
|
||||
$this->assertTrue(isset($definitions['NotNull']), 'Constraint plugin found for type entity.');
|
||||
|
||||
$definitions = $this->typedData->getValidationConstraintManager()->getDefinitionsByType('string');
|
||||
$this->assertFalse(isset($definitions['EntityType']), 'Constraint plugin not found for type string.');
|
||||
$this->assertTrue(isset($definitions['Null']), 'Constraint plugin found for type string.');
|
||||
$this->assertTrue(isset($definitions['NotNull']), 'Constraint plugin found for type string.');
|
||||
|
||||
// Test automatic 'required' validation.
|
||||
$definition = array(
|
||||
'type' => 'integer',
|
||||
'required' => TRUE,
|
||||
);
|
||||
$violations = $this->typedData->create($definition)->validate();
|
||||
$this->assertEqual($violations->count(), 1);
|
||||
$violations = $this->typedData->create($definition, 0)->validate();
|
||||
$this->assertEqual($violations->count(), 0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -163,6 +163,8 @@ function hook_cron() {
|
|||
* primitive types in \Drupal\Core\TypedData\Primitive. If set, it must be
|
||||
* a constant defined by \Drupal\Core\TypedData\Primitive such as
|
||||
* \Drupal\Core\TypedData\Primitive::String.
|
||||
* - constraints: An array of validation constraints for this type. See
|
||||
* \Drupal\Core\TypedData\TypedDataManager::getConstraints() for details.
|
||||
*
|
||||
* @see typed_data()
|
||||
* @see Drupal\Core\TypedData\TypedDataManager::create()
|
||||
|
@ -174,6 +176,7 @@ function hook_data_type_info() {
|
|||
'label' => t('Email'),
|
||||
'class' => '\Drupal\email\Type\Email',
|
||||
'primitive type' => \Drupal\Core\TypedData\Primitive::String,
|
||||
'constraints' => array('Email' => array()),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2224,6 +2224,7 @@ function system_data_type_info() {
|
|||
'label' => t('Email'),
|
||||
'class' => '\Drupal\Core\TypedData\Type\Email',
|
||||
'primitive type' => Primitive::STRING,
|
||||
'constraints' => array('Email' => array()),
|
||||
),
|
||||
'binary' => array(
|
||||
'label' => t('Binary'),
|
||||
|
|
|
@ -35,7 +35,7 @@ class TaxonomyTermReferenceItem extends FieldItemBase {
|
|||
static::$propertyDefinitions['entity'] = array(
|
||||
'type' => 'entity',
|
||||
'constraints' => array(
|
||||
'entity type' => 'taxonomy_term',
|
||||
'EntityType' => 'taxonomy_term',
|
||||
),
|
||||
'label' => t('Term'),
|
||||
'description' => t('The referenced taxonomy term'),
|
||||
|
|
|
@ -39,7 +39,7 @@ abstract class EntityTranslationUITest extends EntityTranslationTestBase {
|
|||
$stored_value = $this->getValue($translation, $property, $default_langcode);
|
||||
$value = is_array($value) ? $value[0]['value'] : $value;
|
||||
$message = format_string('@property correctly stored in the default language.', array('@property' => $property));
|
||||
$this->assertIdentical($stored_value, $value, $message);
|
||||
$this->assertEqual($stored_value, $value, $message);
|
||||
}
|
||||
|
||||
// Add an entity translation.
|
||||
|
|
Loading…
Reference in New Issue