257fdf0fdb54412f3995cd1142cc028c227d798c
[yaffs-website] / web / core / lib / Drupal / Core / Config / Entity / ConfigEntityStorage.php
1 <?php
2
3 namespace Drupal\Core\Config\Entity;
4
5 use Drupal\Core\Cache\CacheableMetadata;
6 use Drupal\Core\Cache\MemoryCache\MemoryCacheInterface;
7 use Drupal\Core\Config\ConfigFactoryInterface;
8 use Drupal\Core\Config\ConfigImporterException;
9 use Drupal\Core\Entity\EntityInterface;
10 use Drupal\Core\Entity\EntityMalformedException;
11 use Drupal\Core\Entity\EntityStorageBase;
12 use Drupal\Core\Config\Config;
13 use Drupal\Core\Config\Entity\Exception\ConfigEntityIdLengthException;
14 use Drupal\Core\Entity\EntityTypeInterface;
15 use Drupal\Component\Uuid\UuidInterface;
16 use Drupal\Core\Language\LanguageManagerInterface;
17 use Symfony\Component\DependencyInjection\ContainerInterface;
18
19 /**
20  * Defines the storage class for configuration entities.
21  *
22  * Configuration object names of configuration entities are comprised of two
23  * parts, separated by a dot:
24  * - config_prefix: A string denoting the owner (module/extension) of the
25  *   configuration object, followed by arbitrary other namespace identifiers
26  *   that are declared by the owning extension; e.g., 'node.type'. The
27  *   config_prefix does NOT contain a trailing dot. It is defined by the entity
28  *   type's annotation.
29  * - ID: A string denoting the entity ID within the entity type namespace; e.g.,
30  *   'article'. Entity IDs may contain dots/periods. The entire remaining string
31  *   after the config_prefix in a config name forms the entity ID. Additional or
32  *   custom suffixes are not possible.
33  *
34  * @ingroup entity_api
35  */
36 class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStorageInterface, ImportableEntityStorageInterface {
37
38   /**
39    * Length limit of the configuration entity ID.
40    *
41    * Most file systems limit a file name's length to 255 characters, so
42    * ConfigBase::MAX_NAME_LENGTH restricts the full configuration object name
43    * to 250 characters (leaving 5 for the file extension). The config prefix
44    * is limited by ConfigEntityType::PREFIX_LENGTH to 83 characters, so this
45    * leaves 166 remaining characters for the configuration entity ID, with 1
46    * additional character needed for the joining dot.
47    *
48    * @see \Drupal\Core\Config\ConfigBase::MAX_NAME_LENGTH
49    * @see \Drupal\Core\Config\Entity\ConfigEntityType::PREFIX_LENGTH
50    */
51   const MAX_ID_LENGTH = 166;
52
53   /**
54    * {@inheritdoc}
55    */
56   protected $uuidKey = 'uuid';
57
58   /**
59    * The config factory service.
60    *
61    * @var \Drupal\Core\Config\ConfigFactoryInterface
62    */
63   protected $configFactory;
64
65   /**
66    * The config storage service.
67    *
68    * @var \Drupal\Core\Config\StorageInterface
69    */
70   protected $configStorage;
71
72   /**
73    * The language manager.
74    *
75    * @var \Drupal\Core\Language\LanguageManagerInterface
76    */
77   protected $languageManager;
78
79   /**
80    * Static cache of entities, keyed first by entity ID, then by an extra key.
81    *
82    * The additional cache key is to maintain separate caches for different
83    * states of config overrides.
84    *
85    * @var array
86    * @see \Drupal\Core\Config\ConfigFactoryInterface::getCacheKeys().
87    */
88   protected $entities = [];
89
90   /**
91    * Determines if the underlying configuration is retrieved override free.
92    *
93    * @var bool
94    */
95   protected $overrideFree = FALSE;
96
97   /**
98    * Constructs a ConfigEntityStorage object.
99    *
100    * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
101    *   The entity type definition.
102    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
103    *   The config factory service.
104    * @param \Drupal\Component\Uuid\UuidInterface $uuid_service
105    *   The UUID service.
106    * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
107    *   The language manager.
108    * @param \Drupal\Core\Cache\MemoryCache\MemoryCacheInterface|null $memory_cache
109    *   The memory cache backend.
110    */
111   public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager, MemoryCacheInterface $memory_cache = NULL) {
112     parent::__construct($entity_type, $memory_cache);
113
114     $this->configFactory = $config_factory;
115     $this->uuidService = $uuid_service;
116     $this->languageManager = $language_manager;
117   }
118
119   /**
120    * {@inheritdoc}
121    */
122   public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
123     return new static(
124       $entity_type,
125       $container->get('config.factory'),
126       $container->get('uuid'),
127       $container->get('language_manager'),
128       $container->get('entity.memory_cache')
129     );
130   }
131
132   /**
133    * {@inheritdoc}
134    */
135   public function loadRevision($revision_id) {
136     return NULL;
137   }
138
139   /**
140    * {@inheritdoc}
141    */
142   public function deleteRevision($revision_id) {
143     return NULL;
144   }
145
146   /**
147    * Returns the prefix used to create the configuration name.
148    *
149    * The prefix consists of the config prefix from the entity type plus a dot
150    * for separating from the ID.
151    *
152    * @return string
153    *   The full configuration prefix, for example 'views.view.'.
154    */
155   protected function getPrefix() {
156     return $this->entityType->getConfigPrefix() . '.';
157   }
158
159   /**
160    * {@inheritdoc}
161    */
162   public static function getIDFromConfigName($config_name, $config_prefix) {
163     return substr($config_name, strlen($config_prefix . '.'));
164   }
165
166   /**
167    * {@inheritdoc}
168    */
169   protected function doLoadMultiple(array $ids = NULL) {
170     $prefix = $this->getPrefix();
171
172     // Get the names of the configuration entities we are going to load.
173     if ($ids === NULL) {
174       $names = $this->configFactory->listAll($prefix);
175     }
176     else {
177       $names = [];
178       foreach ($ids as $id) {
179         // Add the prefix to the ID to serve as the configuration object name.
180         $names[] = $prefix . $id;
181       }
182     }
183
184     // Load all of the configuration entities.
185     /** @var \Drupal\Core\Config\Config[] $configs */
186     $configs = [];
187     $records = [];
188     foreach ($this->configFactory->loadMultiple($names) as $config) {
189       $id = $config->get($this->idKey);
190       $records[$id] = $this->overrideFree ? $config->getOriginal(NULL, FALSE) : $config->get();
191       $configs[$id] = $config;
192     }
193     $entities = $this->mapFromStorageRecords($records, $configs);
194
195     // Config entities wrap config objects, and therefore they need to inherit
196     // the cacheability metadata of config objects (to ensure e.g. additional
197     // cacheability metadata added by config overrides is not lost).
198     foreach ($entities as $id => $entity) {
199       // But rather than simply inheriting all cacheability metadata of config
200       // objects, we need to make sure the self-referring cache tag that is
201       // present on Config objects is not added to the Config entity. It must be
202       // removed for 3 reasons:
203       // 1. When renaming/duplicating a Config entity, the cache tag of the
204       //    original config object would remain present, which would be wrong.
205       // 2. Some Config entities choose to not use the cache tag that the under-
206       //    lying Config object provides by default (For performance and
207       //    cacheability reasons it may not make sense to have a unique cache
208       //    tag for every Config entity. The DateFormat Config entity specifies
209       //    the 'rendered' cache tag for example, because A) date formats are
210       //    changed extremely rarely, so invalidating all render cache items is
211       //    fine, B) it means fewer cache tags per page.).
212       // 3. Fewer cache tags is better for performance.
213       $self_referring_cache_tag = ['config:' . $configs[$id]->getName()];
214       $config_cacheability = CacheableMetadata::createFromObject($configs[$id]);
215       $config_cacheability->setCacheTags(array_diff($config_cacheability->getCacheTags(), $self_referring_cache_tag));
216       $entity->addCacheableDependency($config_cacheability);
217     }
218
219     return $entities;
220   }
221
222   /**
223    * {@inheritdoc}
224    */
225   protected function doCreate(array $values) {
226     // Set default language to current language if not provided.
227     $values += [$this->langcodeKey => $this->languageManager->getCurrentLanguage()->getId()];
228     $entity = new $this->entityClass($values, $this->entityTypeId);
229
230     return $entity;
231   }
232
233   /**
234    * {@inheritdoc}
235    */
236   protected function doDelete($entities) {
237     foreach ($entities as $entity) {
238       $this->configFactory->getEditable($this->getPrefix() . $entity->id())->delete();
239     }
240   }
241
242   /**
243    * Implements Drupal\Core\Entity\EntityStorageInterface::save().
244    *
245    * @throws EntityMalformedException
246    *   When attempting to save a configuration entity that has no ID.
247    */
248   public function save(EntityInterface $entity) {
249     // Configuration entity IDs are strings, and '0' is a valid ID.
250     $id = $entity->id();
251     if ($id === NULL || $id === '') {
252       throw new EntityMalformedException('The entity does not have an ID.');
253     }
254
255     // Check the configuration entity ID length.
256     // @see \Drupal\Core\Config\Entity\ConfigEntityStorage::MAX_ID_LENGTH
257     // @todo Consider moving this to a protected method on the parent class, and
258     //   abstracting it for all entity types.
259     if (strlen($entity->get($this->idKey)) > self::MAX_ID_LENGTH) {
260       throw new ConfigEntityIdLengthException("Configuration entity ID {$entity->get($this->idKey)} exceeds maximum allowed length of " . self::MAX_ID_LENGTH . " characters.");
261     }
262
263     return parent::save($entity);
264   }
265
266   /**
267    * {@inheritdoc}
268    */
269   protected function doSave($id, EntityInterface $entity) {
270     $is_new = $entity->isNew();
271     $prefix = $this->getPrefix();
272     $config_name = $prefix . $entity->id();
273     if ($id !== $entity->id()) {
274       // Renaming a config object needs to cater for:
275       // - Storage needs to access the original object.
276       // - The object needs to be renamed/copied in ConfigFactory and reloaded.
277       // - All instances of the object need to be renamed.
278       $this->configFactory->rename($prefix . $id, $config_name);
279     }
280     $config = $this->configFactory->getEditable($config_name);
281
282     // Retrieve the desired properties and set them in config.
283     $config->setData($this->mapToStorageRecord($entity));
284     $config->save($entity->hasTrustedData());
285
286     // Update the entity with the values stored in configuration. It is possible
287     // that configuration schema has casted some of the values.
288     if (!$entity->hasTrustedData()) {
289       $data = $this->mapFromStorageRecords([$config->get()]);
290       $updated_entity = current($data);
291
292       foreach (array_keys($config->get()) as $property) {
293         $value = $updated_entity->get($property);
294         $entity->set($property, $value);
295       }
296     }
297
298     return $is_new ? SAVED_NEW : SAVED_UPDATED;
299   }
300
301   /**
302    * Maps from an entity object to the storage record.
303    *
304    * @param \Drupal\Core\Entity\EntityInterface $entity
305    *   The entity object.
306    *
307    * @return array
308    *   The record to store.
309    */
310   protected function mapToStorageRecord(EntityInterface $entity) {
311     return $entity->toArray();
312   }
313
314   /**
315    * {@inheritdoc}
316    */
317   protected function has($id, EntityInterface $entity) {
318     $prefix = $this->getPrefix();
319     $config = $this->configFactory->get($prefix . $id);
320     return !$config->isNew();
321   }
322
323   /**
324    * {@inheritdoc}
325    */
326   public function hasData() {
327     return (bool) $this->configFactory->listAll($this->getPrefix());
328   }
329
330   /**
331    * {@inheritdoc}
332    */
333   protected function buildCacheId($id) {
334     return parent::buildCacheId($id) . ':' . ($this->overrideFree ? '' : implode(':', $this->configFactory->getCacheKeys()));
335   }
336
337   /**
338    * Invokes a hook on behalf of the entity.
339    *
340    * @param $hook
341    *   One of 'presave', 'insert', 'update', 'predelete', or 'delete'.
342    * @param $entity
343    *   The entity object.
344    */
345   protected function invokeHook($hook, EntityInterface $entity) {
346     // Invoke the hook.
347     $this->moduleHandler->invokeAll($this->entityTypeId . '_' . $hook, [$entity]);
348     // Invoke the respective entity-level hook.
349     $this->moduleHandler->invokeAll('entity_' . $hook, [$entity, $this->entityTypeId]);
350   }
351
352   /**
353    * {@inheritdoc}
354    */
355   protected function getQueryServiceName() {
356     return 'entity.query.config';
357   }
358
359   /**
360    * {@inheritdoc}
361    */
362   public function importCreate($name, Config $new_config, Config $old_config) {
363     $entity = $this->_doCreateFromStorageRecord($new_config->get(), TRUE);
364     $entity->save();
365     return TRUE;
366   }
367
368   /**
369    * {@inheritdoc}
370    */
371   public function importUpdate($name, Config $new_config, Config $old_config) {
372     $id = static::getIDFromConfigName($name, $this->entityType->getConfigPrefix());
373     $entity = $this->load($id);
374     if (!$entity) {
375       throw new ConfigImporterException("Attempt to update non-existing entity '$id'.");
376     }
377     $entity->setSyncing(TRUE);
378     $entity = $this->updateFromStorageRecord($entity, $new_config->get());
379     $entity->save();
380     return TRUE;
381   }
382
383   /**
384    * {@inheritdoc}
385    */
386   public function importDelete($name, Config $new_config, Config $old_config) {
387     $id = static::getIDFromConfigName($name, $this->entityType->getConfigPrefix());
388     $entity = $this->load($id);
389     $entity->setSyncing(TRUE);
390     $entity->delete();
391     return TRUE;
392   }
393
394   /**
395    * {@inheritdoc}
396    */
397   public function importRename($old_name, Config $new_config, Config $old_config) {
398     return $this->importUpdate($old_name, $new_config, $old_config);
399   }
400
401   /**
402    * {@inheritdoc}
403    */
404   public function createFromStorageRecord(array $values) {
405     return $this->_doCreateFromStorageRecord($values);
406   }
407
408   /**
409    * Helps create a configuration entity from storage values.
410    *
411    * Allows the configuration entity storage to massage storage values before
412    * creating an entity.
413    *
414    * @param array $values
415    *   The array of values from the configuration storage.
416    * @param bool $is_syncing
417    *   Is the configuration entity being created as part of a config sync.
418    *
419    * @return \Drupal\Core\Config\ConfigEntityInterface
420    *   The configuration entity.
421    *
422    * @see \Drupal\Core\Config\Entity\ConfigEntityStorageInterface::createFromStorageRecord()
423    * @see \Drupal\Core\Config\Entity\ImportableEntityStorageInterface::importCreate()
424    */
425   protected function _doCreateFromStorageRecord(array $values, $is_syncing = FALSE) {
426     // Assign a new UUID if there is none yet.
427     if ($this->uuidKey && $this->uuidService && !isset($values[$this->uuidKey])) {
428       $values[$this->uuidKey] = $this->uuidService->generate();
429     }
430     $data = $this->mapFromStorageRecords([$values]);
431     $entity = current($data);
432     $entity->original = clone $entity;
433     $entity->setSyncing($is_syncing);
434     $entity->enforceIsNew();
435     $entity->postCreate($this);
436
437     // Modules might need to add or change the data initially held by the new
438     // entity object, for instance to fill-in default values.
439     $this->invokeHook('create', $entity);
440     return $entity;
441
442   }
443
444   /**
445    * {@inheritdoc}
446    */
447   public function updateFromStorageRecord(ConfigEntityInterface $entity, array $values) {
448     $entity->original = clone $entity;
449
450     $data = $this->mapFromStorageRecords([$values]);
451     $updated_entity = current($data);
452
453     /** @var \Drupal\Core\Config\Entity\ConfigEntityTypeInterface $entity_type */
454     $entity_type = $this->getEntityType();
455     $id_key = $entity_type->getKey('id');
456     $properties = $entity_type->getPropertiesToExport($updated_entity->get($id_key));
457
458     if (empty($properties)) {
459       // Fallback to using the provided values. If the properties cannot be
460       // determined for the config entity type annotation or configuration
461       // schema.
462       $properties = array_keys($values);
463     }
464     foreach ($properties as $property) {
465       if ($property === $this->uuidKey) {
466         // During an update the UUID field should not be copied. Under regular
467         // circumstances the values will be equal. If configuration is written
468         // twice during configuration install the updated entity will not have a
469         // UUID.
470         // @see \Drupal\Core\Config\ConfigInstaller::createConfiguration()
471         continue;
472       }
473       $entity->set($property, $updated_entity->get($property));
474     }
475
476     return $entity;
477   }
478
479   /**
480    * {@inheritdoc}
481    */
482   public function loadOverrideFree($id) {
483     $entities = $this->loadMultipleOverrideFree([$id]);
484     return isset($entities[$id]) ? $entities[$id] : NULL;
485   }
486
487   /**
488    * {@inheritdoc}
489    */
490   public function loadMultipleOverrideFree(array $ids = NULL) {
491     $this->overrideFree = TRUE;
492     $entities = $this->loadMultiple($ids);
493     $this->overrideFree = FALSE;
494     return $entities;
495   }
496
497 }