3 namespace Drupal\Core\TypedData;
5 use Drupal\Component\Plugin\Exception\PluginException;
6 use Drupal\Core\Cache\CacheBackendInterface;
7 use Drupal\Core\DependencyInjection\ClassResolverInterface;
8 use Drupal\Core\DependencyInjection\DependencySerializationTrait;
9 use Drupal\Core\Extension\ModuleHandlerInterface;
10 use Drupal\Core\Plugin\DefaultPluginManager;
11 use Drupal\Core\TypedData\Validation\ExecutionContextFactory;
12 use Drupal\Core\TypedData\Validation\RecursiveValidator;
13 use Drupal\Core\Validation\ConstraintManager;
14 use Drupal\Core\Validation\ConstraintValidatorFactory;
15 use Drupal\Core\Validation\DrupalTranslator;
16 use Symfony\Component\Validator\Validator\ValidatorInterface;
19 * Manages data type plugins.
21 class TypedDataManager extends DefaultPluginManager implements TypedDataManagerInterface {
22 use DependencySerializationTrait;
25 * The validator used for validating typed data.
27 * @var \Symfony\Component\Validator\Validator\ValidatorInterface
32 * The validation constraint manager to use for instantiating constraints.
34 * @var \Drupal\Core\Validation\ConstraintManager
36 protected $constraintManager;
39 * An array of typed data property prototypes.
43 protected $prototypes = [];
48 * @var \Drupal\Core\DependencyInjection\ClassResolverInterface
50 protected $classResolver;
53 * Constructs a new TypedDataManager.
55 * @param \Traversable $namespaces
56 * An object that implements \Traversable which contains the root paths
57 * keyed by the corresponding namespace to look for plugin implementations.
58 * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
59 * Cache backend instance to use.
60 * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
62 * @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver
65 public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, ClassResolverInterface $class_resolver) {
66 $this->alterInfo('data_type_info');
67 $this->setCacheBackend($cache_backend, 'typed_data_types_plugins');
68 $this->classResolver = $class_resolver;
70 parent::__construct('Plugin/DataType', $namespaces, $module_handler, NULL, 'Drupal\Core\TypedData\Annotation\DataType');
76 public function createInstance($data_type, array $configuration = []) {
77 $data_definition = $configuration['data_definition'];
78 $type_definition = $this->getDefinition($data_type);
80 if (!isset($type_definition)) {
81 throw new \InvalidArgumentException("Invalid data type '$data_type' has been given");
84 // Allow per-data definition overrides of the used classes, i.e. take over
85 // classes specified in the type definition.
86 $class = $data_definition->getClass();
89 throw new PluginException(sprintf('The plugin (%s) did not specify an instance class.', $data_type));
91 $typed_data = $class::createInstance($data_definition, $configuration['name'], $configuration['parent']);
92 $typed_data->setTypedDataManager($this);
99 public function create(DataDefinitionInterface $definition, $value = NULL, $name = NULL, $parent = NULL) {
100 $typed_data = $this->createInstance($definition->getDataType(), [
101 'data_definition' => $definition,
106 $typed_data->setValue($value, FALSE);
114 public function createDataDefinition($data_type) {
115 $type_definition = $this->getDefinition($data_type);
116 if (!isset($type_definition)) {
117 throw new \InvalidArgumentException("Invalid data type '$data_type' has been given");
119 $class = $type_definition['definition_class'];
120 $data_definition = $class::createFromDataType($data_type);
122 if (method_exists($data_definition, 'setTypedDataManager')) {
123 $data_definition->setTypedDataManager($this);
126 return $data_definition;
132 public function createListDataDefinition($item_type) {
133 $type_definition = $this->getDefinition($item_type);
134 if (!isset($type_definition)) {
135 throw new \InvalidArgumentException("Invalid data type '$item_type' has been given");
137 $class = $type_definition['list_definition_class'];
138 return $class::createFromItemType($item_type);
144 public function getInstance(array $options) {
145 return $this->getPropertyInstance($options['object'], $options['property'], $options['value']);
151 public function getPropertyInstance(TypedDataInterface $object, $property_name, $value = NULL) {
152 // For performance, try to reuse existing prototypes instead of
153 // constructing new objects when possible. A prototype is reused when
154 // creating a data object:
155 // - for a similar root object (same data type and settings),
156 // - at the same property path under that root object.
157 $root_definition = $object->getRoot()->getDataDefinition();
158 // If the root object is a list, we want to look at the data type and the
159 // settings of its item definition.
160 if ($root_definition instanceof ListDataDefinition) {
161 $root_definition = $root_definition->getItemDefinition();
164 // Root data type and settings.
165 $parts[] = $root_definition->getDataType();
166 if ($settings = $root_definition->getSettings()) {
167 // Include the settings serialized as JSON as part of the key. The JSON is
168 // a shorter string than the serialized form, so array access is faster.
169 $parts[] = json_encode($settings);
171 // Property path for the requested data object. When creating a list item,
172 // use 0 in the key as all items look the same.
173 $parts[] = $object->getPropertyPath() . '.' . (is_numeric($property_name) ? 0 : $property_name);
174 $key = implode(':', $parts);
176 // Create the prototype if needed.
177 if (!isset($this->prototypes[$key])) {
178 // Fetch the data definition for the child object from the parent.
179 if ($object instanceof ComplexDataInterface) {
180 $definition = $object->getDataDefinition()->getPropertyDefinition($property_name);
182 elseif ($object instanceof ListInterface) {
183 $definition = $object->getItemDefinition();
186 throw new \InvalidArgumentException("The passed object has to either implement the ComplexDataInterface or the ListInterface.");
189 throw new \InvalidArgumentException("Property $property_name is unknown.");
191 // Create the prototype without any value, but with initial parenting
192 // so that constructors can set up the objects correclty.
193 $this->prototypes[$key] = $this->create($definition, NULL, $property_name, $object);
196 // Clone the prototype, update its parenting information, and assign the
198 $property = clone $this->prototypes[$key];
199 $property->setContext($property_name, $object);
201 $property->setValue($value, FALSE);
207 * Sets the validator for validating typed data.
209 * @param \Symfony\Component\Validator\Validator\ValidatorInterface $validator
210 * The validator object to set.
212 public function setValidator(ValidatorInterface $validator) {
213 $this->validator = $validator;
219 public function getValidator() {
220 if (!isset($this->validator)) {
221 $this->validator = new RecursiveValidator(
222 new ExecutionContextFactory(new DrupalTranslator()),
223 new ConstraintValidatorFactory($this->classResolver),
227 return $this->validator;
233 public function setValidationConstraintManager(ConstraintManager $constraintManager) {
234 $this->constraintManager = $constraintManager;
240 public function getValidationConstraintManager() {
241 return $this->constraintManager;
247 public function getDefaultConstraints(DataDefinitionInterface $definition) {
249 $type_definition = $this->getDefinition($definition->getDataType());
250 // Auto-generate a constraint for data types implementing a primitive
252 if (is_subclass_of($type_definition['class'], '\Drupal\Core\TypedData\PrimitiveInterface')) {
253 $constraints['PrimitiveType'] = [];
255 // Add in constraints specified by the data type.
256 if (isset($type_definition['constraints'])) {
257 $constraints += $type_definition['constraints'];
259 // Add the NotNull constraint for required data.
260 if ($definition->isRequired()) {
261 $constraints['NotNull'] = [];
263 // Check if the class provides allowed values.
264 if (is_subclass_of($definition->getClass(), 'Drupal\Core\TypedData\OptionsProviderInterface')) {
265 $constraints['AllowedValues'] = [];
273 public function clearCachedDefinitions() {
274 parent::clearCachedDefinitions();
275 $this->prototypes = [];
281 public function getCanonicalRepresentation(TypedDataInterface $data) {
282 $data_definition = $data->getDataDefinition();
283 // In case a list is passed, respect the 'wrapped' key of its data type.
284 if ($data_definition instanceof ListDataDefinitionInterface) {
285 $data_definition = $data_definition->getItemDefinition();
287 // Get the plugin definition of the used data type.
288 $type_definition = $this->getDefinition($data_definition->getDataType());
289 if (!empty($type_definition['unwrap_for_canonical_representation'])) {
290 return $data->getValue();