Contains \Drupal\Core\TypedData\TypedDataManager.
<?php
/**
* @file
* Contains \Drupal\Core\TypedData\TypedDataManager.
*/
namespace Drupal\Core\TypedData;
use InvalidArgumentException;
use Drupal\Component\Plugin\Discovery\ProcessDecorator;
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;
/**
* Type definition defaults which are merged in by the ProcessDecorator.
*
* @see \Drupal\Component\Plugin\PluginManagerBase::processDefinition()
*
* @var array
*/
protected $defaults = array(
'list class' => '\\Drupal\\Core\\TypedData\\ItemList',
);
/**
* An array of typed data property prototypes.
*
* @var array
*/
protected $prototypes = array();
public function __construct() {
$this->discovery = new HookDiscovery('data_type_info');
$this->discovery = new ProcessDecorator($this->discovery, array(
$this,
'processDefinition',
));
$this->discovery = new CacheDecorator($this->discovery, 'typed_data:types');
$this->factory = new TypedDataFactory($this->discovery);
}
/**
* Implements \Drupal\Component\Plugin\PluginManagerInterface::createInstance().
*
* @param string $plugin_id
* The id of a plugin, i.e. the data type.
* @param array $configuration
* The plugin configuration, i.e. the data definition.
* @param string $name
* (optional) If a property or list item is to be created, the name of the
* property or the delta of the list item.
* @param mixed $parent
* (optional) If a property or list item is to be created, the parent typed
* data object implementing either the ListInterface or the
* ComplexDataInterface.
*
* @return \Drupal\Core\TypedData\TypedDataInterface
* The instantiated typed data object.
*/
public function createInstance($plugin_id, array $configuration, $name = NULL, $parent = NULL) {
return $this->factory
->createInstance($plugin_id, $configuration, $name, $parent);
}
/**
* Creates a new typed data object instance.
*
* @param array $definition
* The data definition array with the following array keys and values:
* - type: The data type of the data to wrap. Required.
* - label: A human readable label.
* - description: A human readable description.
* - list: Whether the data is multi-valued, i.e. a list of data items.
* Defaults to FALSE.
* - computed: A boolean specifying whether the data value is computed by
* the object, e.g. depending on some other values.
* - read-only: A boolean specifying whether the data is read-only. Defaults
* to TRUE for computed properties, to FALSE otherwise.
* - class: If set and 'list' is FALSE, the class to use for creating the
* typed data object; otherwise the default class of the data type will be
* used.
* - list class: If set and 'list' is TRUE, the class to use for creating
* the typed data object; otherwise the default list class of the data
* type will be used.
* - settings: An array of settings, as required by the used 'class'. See
* the documentation of the class for supported or required settings.
* - 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 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
* \Drupal\Core\Entity\StorageControllerInterface::getPropertyDefinitions().
* @param mixed $value
* (optional) The data value. If set, it has to match one of the supported
* data type format as documented for the data type classes.
* @param string $name
* (optional) If a property or list item is to be created, the name of the
* property or the delta of the list item.
* @param mixed $parent
* (optional) If a property or list item is to be created, the parent typed
* data object implementing either the ListInterface or the
* ComplexDataInterface.
*
* @return \Drupal\Core\TypedData\TypedDataInterface
* The instantiated typed data object.
*
* @see \Drupal::typedData()
* @see \Drupal\Core\TypedData\TypedDataManager::getPropertyInstance()
* @see \Drupal\Core\TypedData\Type\Integer
* @see \Drupal\Core\TypedData\Type\Float
* @see \Drupal\Core\TypedData\Type\String
* @see \Drupal\Core\TypedData\Type\Boolean
* @see \Drupal\Core\TypedData\Type\Duration
* @see \Drupal\Core\TypedData\Type\Date
* @see \Drupal\Core\TypedData\Type\Uri
* @see \Drupal\Core\TypedData\Type\Binary
* @see \Drupal\Core\Entity\Field\EntityWrapper
*/
public function create(array $definition, $value = NULL, $name = NULL, $parent = NULL) {
$wrapper = $this->factory
->createInstance($definition['type'], $definition, $name, $parent);
if (isset($value)) {
$wrapper
->setValue($value, FALSE);
}
return $wrapper;
}
/**
* Implements \Drupal\Component\Plugin\PluginManagerInterface::getInstance().
*
* @param array $options
* An array of options with the following keys:
* - object: The parent typed data object, implementing the
* TypedDataInterface and either the ListInterface or the
* ComplexDataInterface.
* - property: The name of the property to instantiate, or the delta of the
* the list item to instantiate.
* - value: The value to set. If set, it has to match one of the supported
* data type formats as documented by the data type classes.
*
* @throws \InvalidArgumentException
* If the given property is not known, or the passed object does not
* implement the ListInterface or the ComplexDataInterface.
*
* @return \Drupal\Core\TypedData\TypedDataInterface
* The new property instance.
*
* @see \Drupal\Core\TypedData\TypedDataManager::getPropertyInstance()
*/
public function getInstance(array $options) {
return $this
->getPropertyInstance($options['object'], $options['property'], $options['value']);
}
/**
* Get a typed data instance for a property of a given typed data object.
*
* This method will use prototyping for fast and efficient instantiation of
* many property objects with the same property path; e.g.,
* when multiple comments are used comment_body.0.value needs to be
* instantiated very often.
* Prototyping is done by the root object's data type and the given
* property path, i.e. all property instances having the same property path
* and inheriting from the same data type are prototyped.
*
* @param \Drupal\Core\TypedData\TypedDataInterface $object
* The parent typed data object, implementing the TypedDataInterface and
* either the ListInterface or the ComplexDataInterface.
* @param string $property_name
* The name of the property to instantiate, or the delta of an list item.
* @param mixed $value
* (optional) The data value. If set, it has to match one of the supported
* data type formats as documented by the data type classes.
*
* @throws \InvalidArgumentException
* If the given property is not known, or the passed object does not
* implement the ListInterface or the ComplexDataInterface.
*
* @return \Drupal\Core\TypedData\TypedDataInterface
* The new property instance.
*
* @see \Drupal\Core\TypedData\TypedDataManager::create()
*
* @todo: Add type-hinting to $object once entities implement the
* TypedDataInterface.
*/
public function getPropertyInstance($object, $property_name, $value = NULL) {
if ($root = $object
->getRoot()) {
$key = $root
->getType() . ':' . $object
->getPropertyPath() . '.';
// If we are creating list items, we always use 0 in the key as all list
// items look the same.
$key .= is_numeric($property_name) ? 0 : $property_name;
}
else {
// Missing context, thus we cannot determine a unique key for prototyping.
// Fall back to create a new prototype on each call.
$key = FALSE;
}
// Make sure we have a prototype. Then, clone the prototype and set object
// specific values, i.e. the value and the context.
if (!isset($this->prototypes[$key]) || !$key) {
// Create the initial prototype. For that we need to fetch the definition
// of the to be created property instance from the parent.
if ($object instanceof ComplexDataInterface) {
$definition = $object
->getPropertyDefinition($property_name);
}
elseif ($object instanceof ListInterface) {
$definition = $object
->getItemDefinition();
}
else {
throw new InvalidArgumentException("The passed object has to either implement the ComplexDataInterface or the ListInterface.");
}
// Make sure we have got a valid definition.
if (!$definition) {
throw new InvalidArgumentException('Property ' . check_plain($property_name) . ' is unknown.');
}
// Now create the prototype using the definition, but do not pass the
// given value as it will serve as prototype for any further instance.
$this->prototypes[$key] = $this
->create($definition, NULL, $property_name, $object);
}
// Clone from the prototype, then update the parent relationship and set the
// data value if necessary.
$property = clone $this->prototypes[$key];
$property
->setContext($property_name, $object);
if (isset($value)) {
$property
->setValue($value, FALSE);
}
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;
}
/**
* 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();
$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[] = $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[] = $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[] = $validation_manager
->create($name, $options);
}
}
// Add the NotNull constraint for required data.
if (!empty($definition['required']) && empty($definition['constraints']['NotNull'])) {
$constraints[] = $validation_manager
->create('NotNull', array());
}
return $constraints;
}
}
Name | Description |
---|---|
TypedDataManager | Manages data type plugins. |