dbf0d5fec544b28be2684756fe0261d9d08f8a83
[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. 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);
175
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);
181       }
182       elseif ($object instanceof ListInterface) {
183         $definition = $object->getItemDefinition();
184       }
185       else {
186         throw new \InvalidArgumentException("The passed object has to either implement the ComplexDataInterface or the ListInterface.");
187       }
188       if (!$definition) {
189         throw new \InvalidArgumentException("Property $property_name is unknown.");
190       }
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);
194     }
195
196     // Clone the prototype, update its parenting information, and assign the
197     // value.
198     $property = clone $this->prototypes[$key];
199     $property->setContext($property_name, $object);
200     if (isset($value)) {
201       $property->setValue($value, FALSE);
202     }
203     return $property;
204   }
205
206   /**
207    * Sets the validator for validating typed data.
208    *
209    * @param \Symfony\Component\Validator\Validator\ValidatorInterface $validator
210    *   The validator object to set.
211    */
212   public function setValidator(ValidatorInterface $validator) {
213     $this->validator = $validator;
214   }
215
216   /**
217    * {@inheritdoc}
218    */
219   public function getValidator() {
220     if (!isset($this->validator)) {
221       $this->validator = new RecursiveValidator(
222         new ExecutionContextFactory(new DrupalTranslator()),
223         new ConstraintValidatorFactory($this->classResolver),
224         $this
225       );
226     }
227     return $this->validator;
228   }
229
230   /**
231    * {@inheritdoc}
232    */
233   public function setValidationConstraintManager(ConstraintManager $constraintManager) {
234     $this->constraintManager = $constraintManager;
235   }
236
237   /**
238    * {@inheritdoc}
239    */
240   public function getValidationConstraintManager() {
241     return $this->constraintManager;
242   }
243
244   /**
245    * {@inheritdoc}
246    */
247   public function getDefaultConstraints(DataDefinitionInterface $definition) {
248     $constraints = [];
249     $type_definition = $this->getDefinition($definition->getDataType());
250     // Auto-generate a constraint for data types implementing a primitive
251     // interface.
252     if (is_subclass_of($type_definition['class'], '\Drupal\Core\TypedData\PrimitiveInterface')) {
253       $constraints['PrimitiveType'] = [];
254     }
255     // Add in constraints specified by the data type.
256     if (isset($type_definition['constraints'])) {
257       $constraints += $type_definition['constraints'];
258     }
259     // Add the NotNull constraint for required data.
260     if ($definition->isRequired()) {
261       $constraints['NotNull'] = [];
262     }
263     // Check if the class provides allowed values.
264     if (is_subclass_of($definition->getClass(), 'Drupal\Core\TypedData\OptionsProviderInterface')) {
265       $constraints['AllowedValues'] = [];
266     }
267     return $constraints;
268   }
269
270   /**
271    * {@inheritdoc}
272    */
273   public function clearCachedDefinitions() {
274     parent::clearCachedDefinitions();
275     $this->prototypes = [];
276   }
277
278   /**
279    * {@inheritdoc}
280    */
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();
286     }
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();
291     }
292     return $data;
293   }
294
295 }