9ac018c37efdb48cfd06d528e88542ea65311e49
[yaffs-website] / web / core / lib / Drupal / Core / TypedData / TypedDataManager.php
1 <?php
2
3 namespace Drupal\Core\TypedData;
4
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;
17
18 /**
19  * Manages data type plugins.
20  */
21 class TypedDataManager extends DefaultPluginManager implements TypedDataManagerInterface {
22   use DependencySerializationTrait;
23
24   /**
25    * The validator used for validating typed data.
26    *
27    * @var \Symfony\Component\Validator\Validator\ValidatorInterface
28    */
29   protected $validator;
30
31   /**
32    * The validation constraint manager to use for instantiating constraints.
33    *
34    * @var \Drupal\Core\Validation\ConstraintManager
35    */
36   protected $constraintManager;
37
38   /**
39    * An array of typed data property prototypes.
40    *
41    * @var array
42    */
43   protected $prototypes = [];
44
45   /**
46    * The class resolver.
47    *
48    * @var \Drupal\Core\DependencyInjection\ClassResolverInterface
49    */
50   protected $classResolver;
51
52   /**
53    * Constructs a new TypedDataManager.
54    *
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
61    *   The module handler.
62    * @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver
63    *   The class resolver.
64    */
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;
69
70     parent::__construct('Plugin/DataType', $namespaces, $module_handler, NULL, 'Drupal\Core\TypedData\Annotation\DataType');
71   }
72
73   /**
74    * {@inheritdoc}
75    */
76   public function createInstance($data_type, array $configuration = []) {
77     $data_definition = $configuration['data_definition'];
78     $type_definition = $this->getDefinition($data_type);
79
80     if (!isset($type_definition)) {
81       throw new \InvalidArgumentException("Invalid data type '$data_type' has been given");
82     }
83
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();
87
88     if (!isset($class)) {
89       throw new PluginException(sprintf('The plugin (%s) did not specify an instance class.', $data_type));
90     }
91     $typed_data = $class::createInstance($data_definition, $configuration['name'], $configuration['parent']);
92     $typed_data->setTypedDataManager($this);
93     return $typed_data;
94   }
95
96   /**
97    * {@inheritdoc}
98    */
99   public function create(DataDefinitionInterface $definition, $value = NULL, $name = NULL, $parent = NULL) {
100     $typed_data = $this->createInstance($definition->getDataType(), [
101       'data_definition' => $definition,
102       'name' => $name,
103       'parent' => $parent,
104     ]);
105     if (isset($value)) {
106       $typed_data->setValue($value, FALSE);
107     }
108     return $typed_data;
109   }
110
111   /**
112    * {@inheritdoc}
113    */
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");
118     }
119     $class = $type_definition['definition_class'];
120     $data_definition = $class::createFromDataType($data_type);
121
122     if (method_exists($data_definition, 'setTypedDataManager')) {
123       $data_definition->setTypedDataManager($this);
124     }
125
126     return $data_definition;
127   }
128
129   /**
130    * {@inheritdoc}
131    */
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");
136     }
137     $class = $type_definition['list_definition_class'];
138     return $class::createFromItemType($item_type);
139   }
140
141   /**
142    * {@inheritdoc}
143    */
144   public function getInstance(array $options) {
145     return $this->getPropertyInstance($options['object'], $options['property'], $options['value']);
146   }
147
148   /**
149    * {@inheritdoc}
150    */
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();
162     }
163
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);
170     }
171     // Property path for the requested data object.
172     $parts[] = $object->getPropertyPath() . '.' . $property_name;
173     $key = implode(':', $parts);
174
175     // Create the prototype if needed.
176     if (!isset($this->prototypes[$key])) {
177       // Fetch the data definition for the child object from the parent.
178       if ($object instanceof ComplexDataInterface) {
179         $definition = $object->getDataDefinition()->getPropertyDefinition($property_name);
180       }
181       elseif ($object instanceof ListInterface) {
182         $definition = $object->getItemDefinition();
183       }
184       else {
185         throw new \InvalidArgumentException("The passed object has to either implement the ComplexDataInterface or the ListInterface.");
186       }
187       if (!$definition) {
188         throw new \InvalidArgumentException("Property $property_name is unknown.");
189       }
190       // Create the prototype without any value, but with initial parenting
191       // so that constructors can set up the objects correctly.
192       $this->prototypes[$key] = $this->create($definition, NULL, $property_name, $object);
193     }
194
195     // Clone the prototype, update its parenting information, and assign the
196     // value.
197     $property = clone $this->prototypes[$key];
198     $property->setContext($property_name, $object);
199     if (isset($value)) {
200       $property->setValue($value, FALSE);
201     }
202     return $property;
203   }
204
205   /**
206    * Sets the validator for validating typed data.
207    *
208    * @param \Symfony\Component\Validator\Validator\ValidatorInterface $validator
209    *   The validator object to set.
210    */
211   public function setValidator(ValidatorInterface $validator) {
212     $this->validator = $validator;
213   }
214
215   /**
216    * {@inheritdoc}
217    */
218   public function getValidator() {
219     if (!isset($this->validator)) {
220       $this->validator = new RecursiveValidator(
221         new ExecutionContextFactory(new DrupalTranslator()),
222         new ConstraintValidatorFactory($this->classResolver),
223         $this
224       );
225     }
226     return $this->validator;
227   }
228
229   /**
230    * {@inheritdoc}
231    */
232   public function setValidationConstraintManager(ConstraintManager $constraintManager) {
233     $this->constraintManager = $constraintManager;
234   }
235
236   /**
237    * {@inheritdoc}
238    */
239   public function getValidationConstraintManager() {
240     return $this->constraintManager;
241   }
242
243   /**
244    * {@inheritdoc}
245    */
246   public function getDefaultConstraints(DataDefinitionInterface $definition) {
247     $constraints = [];
248     $type_definition = $this->getDefinition($definition->getDataType());
249     // Auto-generate a constraint for data types implementing a primitive
250     // interface.
251     if (is_subclass_of($type_definition['class'], '\Drupal\Core\TypedData\PrimitiveInterface')) {
252       $constraints['PrimitiveType'] = [];
253     }
254     // Add in constraints specified by the data type.
255     if (isset($type_definition['constraints'])) {
256       $constraints += $type_definition['constraints'];
257     }
258     // Add the NotNull constraint for required data.
259     if ($definition->isRequired()) {
260       $constraints['NotNull'] = [];
261     }
262     // Check if the class provides allowed values.
263     if (is_subclass_of($definition->getClass(), 'Drupal\Core\TypedData\OptionsProviderInterface')) {
264       $constraints['AllowedValues'] = [];
265     }
266     return $constraints;
267   }
268
269   /**
270    * {@inheritdoc}
271    */
272   public function clearCachedDefinitions() {
273     parent::clearCachedDefinitions();
274     $this->prototypes = [];
275   }
276
277   /**
278    * {@inheritdoc}
279    */
280   public function getCanonicalRepresentation(TypedDataInterface $data) {
281     $data_definition = $data->getDataDefinition();
282     // In case a list is passed, respect the 'wrapped' key of its data type.
283     if ($data_definition instanceof ListDataDefinitionInterface) {
284       $data_definition = $data_definition->getItemDefinition();
285     }
286     // Get the plugin definition of the used data type.
287     $type_definition = $this->getDefinition($data_definition->getDataType());
288     if (!empty($type_definition['unwrap_for_canonical_representation'])) {
289       return $data->getValue();
290     }
291     return $data;
292   }
293
294 }