Updated Drupal to 8.6. This goes with the following updates because it's possible...
[yaffs-website] / web / core / lib / Drupal / Core / Entity / EntityStorageBase.php
1 <?php
2
3 namespace Drupal\Core\Entity;
4
5 use Drupal\Core\Entity\Query\QueryInterface;
6 use Drupal\Core\Cache\MemoryCache\MemoryCacheInterface;
7
8 /**
9  * A base entity storage class.
10  */
11 abstract class EntityStorageBase extends EntityHandlerBase implements EntityStorageInterface, EntityHandlerInterface {
12
13   /**
14    * Entity type ID for this storage.
15    *
16    * @var string
17    */
18   protected $entityTypeId;
19
20   /**
21    * Information about the entity type.
22    *
23    * The following code returns the same object:
24    * @code
25    * \Drupal::entityManager()->getDefinition($this->entityTypeId)
26    * @endcode
27    *
28    * @var \Drupal\Core\Entity\EntityTypeInterface
29    */
30   protected $entityType;
31
32   /**
33    * Name of the entity's ID field in the entity database table.
34    *
35    * @var string
36    */
37   protected $idKey;
38
39   /**
40    * Name of entity's UUID database table field, if it supports UUIDs.
41    *
42    * Has the value FALSE if this entity does not use UUIDs.
43    *
44    * @var string
45    */
46   protected $uuidKey;
47
48   /**
49    * The name of the entity langcode property.
50    *
51    * @var string
52    */
53   protected $langcodeKey;
54
55   /**
56    * The UUID service.
57    *
58    * @var \Drupal\Component\Uuid\UuidInterface
59    */
60   protected $uuidService;
61
62   /**
63    * Name of the entity class.
64    *
65    * @var string
66    */
67   protected $entityClass;
68
69   /**
70    * The memory cache.
71    *
72    * @var \Drupal\Core\Cache\MemoryCache\MemoryCacheInterface
73    */
74   protected $memoryCache;
75
76   /**
77    * The memory cache cache tag.
78    *
79    * @var string
80    */
81   protected $memoryCacheTag;
82
83   /**
84    * Constructs an EntityStorageBase instance.
85    *
86    * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
87    *   The entity type definition.
88    * @param \Drupal\Core\Cache\MemoryCache\MemoryCacheInterface|null $memory_cache
89    *   The memory cache.
90    */
91   public function __construct(EntityTypeInterface $entity_type, MemoryCacheInterface $memory_cache = NULL) {
92     $this->entityTypeId = $entity_type->id();
93     $this->entityType = $entity_type;
94     $this->idKey = $this->entityType->getKey('id');
95     $this->uuidKey = $this->entityType->getKey('uuid');
96     $this->langcodeKey = $this->entityType->getKey('langcode');
97     $this->entityClass = $this->entityType->getClass();
98
99     if (!isset($memory_cache)) {
100       @trigger_error('The $memory_cache parameter was added in Drupal 8.6.x and will be required in 9.0.0. See https://www.drupal.org/node/2973262', E_USER_DEPRECATED);
101       $memory_cache = \Drupal::service('entity.memory_cache');
102     }
103     $this->memoryCache = $memory_cache;
104     $this->memoryCacheTag = 'entity.memory_cache:' . $this->entityTypeId;
105   }
106
107   /**
108    * {@inheritdoc}
109    */
110   public function getEntityTypeId() {
111     return $this->entityTypeId;
112   }
113
114   /**
115    * {@inheritdoc}
116    */
117   public function getEntityType() {
118     return $this->entityType;
119   }
120
121   /**
122    * Builds the cache ID for the passed in entity ID.
123    *
124    * @param int $id
125    *   Entity ID for which the cache ID should be built.
126    *
127    * @return string
128    *   Cache ID that can be passed to the cache backend.
129    */
130   protected function buildCacheId($id) {
131     return "values:{$this->entityTypeId}:$id";
132   }
133
134   /**
135    * {@inheritdoc}
136    */
137   public function loadUnchanged($id) {
138     $this->resetCache([$id]);
139     return $this->load($id);
140   }
141
142   /**
143    * {@inheritdoc}
144    */
145   public function resetCache(array $ids = NULL) {
146     if ($this->entityType->isStaticallyCacheable() && isset($ids)) {
147       foreach ($ids as $id) {
148         $this->memoryCache->delete($this->buildCacheId($id));
149       }
150     }
151     else {
152       // Call the backend method directly.
153       $this->memoryCache->invalidateTags([$this->memoryCacheTag]);
154     }
155   }
156
157   /**
158    * Gets entities from the static cache.
159    *
160    * @param array $ids
161    *   If not empty, return entities that match these IDs.
162    *
163    * @return \Drupal\Core\Entity\EntityInterface[]
164    *   Array of entities from the entity cache.
165    */
166   protected function getFromStaticCache(array $ids) {
167     $entities = [];
168     // Load any available entities from the internal cache.
169     if ($this->entityType->isStaticallyCacheable()) {
170       foreach ($ids as $id) {
171         if ($cached = $this->memoryCache->get($this->buildCacheId($id))) {
172           $entities[$id] = $cached->data;
173         }
174       }
175     }
176     return $entities;
177   }
178
179   /**
180    * Stores entities in the static entity cache.
181    *
182    * @param \Drupal\Core\Entity\EntityInterface[] $entities
183    *   Entities to store in the cache.
184    */
185   protected function setStaticCache(array $entities) {
186     if ($this->entityType->isStaticallyCacheable()) {
187       foreach ($entities as $id => $entity) {
188         $this->memoryCache->set($this->buildCacheId($entity->id()), $entity, MemoryCacheInterface::CACHE_PERMANENT, [$this->memoryCacheTag]);
189       }
190     }
191   }
192
193   /**
194    * Invokes a hook on behalf of the entity.
195    *
196    * @param string $hook
197    *   One of 'create', 'presave', 'insert', 'update', 'predelete', 'delete', or
198    *   'revision_delete'.
199    * @param \Drupal\Core\Entity\EntityInterface $entity
200    *   The entity object.
201    */
202   protected function invokeHook($hook, EntityInterface $entity) {
203     // Invoke the hook.
204     $this->moduleHandler()->invokeAll($this->entityTypeId . '_' . $hook, [$entity]);
205     // Invoke the respective entity-level hook.
206     $this->moduleHandler()->invokeAll('entity_' . $hook, [$entity]);
207   }
208
209   /**
210    * {@inheritdoc}
211    */
212   public function create(array $values = []) {
213     $entity_class = $this->entityClass;
214     $entity_class::preCreate($this, $values);
215
216     // Assign a new UUID if there is none yet.
217     if ($this->uuidKey && $this->uuidService && !isset($values[$this->uuidKey])) {
218       $values[$this->uuidKey] = $this->uuidService->generate();
219     }
220
221     $entity = $this->doCreate($values);
222     $entity->enforceIsNew();
223
224     $entity->postCreate($this);
225
226     // Modules might need to add or change the data initially held by the new
227     // entity object, for instance to fill-in default values.
228     $this->invokeHook('create', $entity);
229
230     return $entity;
231   }
232
233   /**
234    * Performs storage-specific creation of entities.
235    *
236    * @param array $values
237    *   An array of values to set, keyed by property name.
238    *
239    * @return \Drupal\Core\Entity\EntityInterface
240    */
241   protected function doCreate(array $values) {
242     return new $this->entityClass($values, $this->entityTypeId);
243   }
244
245   /**
246    * {@inheritdoc}
247    */
248   public function load($id) {
249     $entities = $this->loadMultiple([$id]);
250     return isset($entities[$id]) ? $entities[$id] : NULL;
251   }
252
253   /**
254    * {@inheritdoc}
255    */
256   public function loadMultiple(array $ids = NULL) {
257     $entities = [];
258
259     // Create a new variable which is either a prepared version of the $ids
260     // array for later comparison with the entity cache, or FALSE if no $ids
261     // were passed. The $ids array is reduced as items are loaded from cache,
262     // and we need to know if it's empty for this reason to avoid querying the
263     // database when all requested entities are loaded from cache.
264     $passed_ids = !empty($ids) ? array_flip($ids) : FALSE;
265     // Try to load entities from the static cache, if the entity type supports
266     // static caching.
267     if ($this->entityType->isStaticallyCacheable() && $ids) {
268       $entities += $this->getFromStaticCache($ids);
269       // If any entities were loaded, remove them from the ids still to load.
270       if ($passed_ids) {
271         $ids = array_keys(array_diff_key($passed_ids, $entities));
272       }
273     }
274
275     // Load any remaining entities from the database. This is the case if $ids
276     // is set to NULL (so we load all entities) or if there are any ids left to
277     // load.
278     if ($ids === NULL || $ids) {
279       $queried_entities = $this->doLoadMultiple($ids);
280     }
281
282     // Pass all entities loaded from the database through $this->postLoad(),
283     // which attaches fields (if supported by the entity type) and calls the
284     // entity type specific load callback, for example hook_node_load().
285     if (!empty($queried_entities)) {
286       $this->postLoad($queried_entities);
287       $entities += $queried_entities;
288     }
289
290     if ($this->entityType->isStaticallyCacheable()) {
291       // Add entities to the cache.
292       if (!empty($queried_entities)) {
293         $this->setStaticCache($queried_entities);
294       }
295     }
296
297     // Ensure that the returned array is ordered the same as the original
298     // $ids array if this was passed in and remove any invalid ids.
299     if ($passed_ids) {
300       // Remove any invalid ids from the array.
301       $passed_ids = array_intersect_key($passed_ids, $entities);
302       foreach ($entities as $entity) {
303         $passed_ids[$entity->id()] = $entity;
304       }
305       $entities = $passed_ids;
306     }
307
308     return $entities;
309   }
310
311   /**
312    * Performs storage-specific loading of entities.
313    *
314    * Override this method to add custom functionality directly after loading.
315    * This is always called, while self::postLoad() is only called when there are
316    * actual results.
317    *
318    * @param array|null $ids
319    *   (optional) An array of entity IDs, or NULL to load all entities.
320    *
321    * @return \Drupal\Core\Entity\EntityInterface[]
322    *   Associative array of entities, keyed on the entity ID.
323    */
324   abstract protected function doLoadMultiple(array $ids = NULL);
325
326   /**
327    * Attaches data to entities upon loading.
328    *
329    * @param array $entities
330    *   Associative array of query results, keyed on the entity ID.
331    */
332   protected function postLoad(array &$entities) {
333     $entity_class = $this->entityClass;
334     $entity_class::postLoad($this, $entities);
335     // Call hook_entity_load().
336     foreach ($this->moduleHandler()->getImplementations('entity_load') as $module) {
337       $function = $module . '_entity_load';
338       $function($entities, $this->entityTypeId);
339     }
340     // Call hook_TYPE_load().
341     foreach ($this->moduleHandler()->getImplementations($this->entityTypeId . '_load') as $module) {
342       $function = $module . '_' . $this->entityTypeId . '_load';
343       $function($entities);
344     }
345   }
346
347   /**
348    * Maps from storage records to entity objects.
349    *
350    * @param array $records
351    *   Associative array of query results, keyed on the entity ID.
352    *
353    * @return \Drupal\Core\Entity\EntityInterface[]
354    *   An array of entity objects implementing the EntityInterface.
355    */
356   protected function mapFromStorageRecords(array $records) {
357     $entities = [];
358     foreach ($records as $record) {
359       $entity = new $this->entityClass($record, $this->entityTypeId);
360       $entities[$entity->id()] = $entity;
361     }
362     return $entities;
363   }
364
365   /**
366    * Determines if this entity already exists in storage.
367    *
368    * @param int|string $id
369    *   The original entity ID.
370    * @param \Drupal\Core\Entity\EntityInterface $entity
371    *   The entity being saved.
372    *
373    * @return bool
374    */
375   abstract protected function has($id, EntityInterface $entity);
376
377   /**
378    * {@inheritdoc}
379    */
380   public function delete(array $entities) {
381     if (!$entities) {
382       // If no entities were passed, do nothing.
383       return;
384     }
385
386     // Ensure that the entities are keyed by ID.
387     $keyed_entities = [];
388     foreach ($entities as $entity) {
389       $keyed_entities[$entity->id()] = $entity;
390     }
391
392     // Allow code to run before deleting.
393     $entity_class = $this->entityClass;
394     $entity_class::preDelete($this, $keyed_entities);
395     foreach ($keyed_entities as $entity) {
396       $this->invokeHook('predelete', $entity);
397     }
398
399     // Perform the delete and reset the static cache for the deleted entities.
400     $this->doDelete($keyed_entities);
401     $this->resetCache(array_keys($keyed_entities));
402
403     // Allow code to run after deleting.
404     $entity_class::postDelete($this, $keyed_entities);
405     foreach ($keyed_entities as $entity) {
406       $this->invokeHook('delete', $entity);
407     }
408   }
409
410   /**
411    * Performs storage-specific entity deletion.
412    *
413    * @param \Drupal\Core\Entity\EntityInterface[] $entities
414    *   An array of entity objects to delete.
415    */
416   abstract protected function doDelete($entities);
417
418   /**
419    * {@inheritdoc}
420    */
421   public function save(EntityInterface $entity) {
422     // Track if this entity is new.
423     $is_new = $entity->isNew();
424
425     // Execute presave logic and invoke the related hooks.
426     $id = $this->doPreSave($entity);
427
428     // Perform the save and reset the static cache for the changed entity.
429     $return = $this->doSave($id, $entity);
430
431     // Execute post save logic and invoke the related hooks.
432     $this->doPostSave($entity, !$is_new);
433
434     return $return;
435   }
436
437   /**
438    * Performs presave entity processing.
439    *
440    * @param \Drupal\Core\Entity\EntityInterface $entity
441    *   The saved entity.
442    *
443    * @return int|string
444    *   The processed entity identifier.
445    *
446    * @throws \Drupal\Core\Entity\EntityStorageException
447    *   If the entity identifier is invalid.
448    */
449   protected function doPreSave(EntityInterface $entity) {
450     $id = $entity->id();
451
452     // Track the original ID.
453     if ($entity->getOriginalId() !== NULL) {
454       $id = $entity->getOriginalId();
455     }
456
457     // Track if this entity exists already.
458     $id_exists = $this->has($id, $entity);
459
460     // A new entity should not already exist.
461     if ($id_exists && $entity->isNew()) {
462       throw new EntityStorageException("'{$this->entityTypeId}' entity with ID '$id' already exists.");
463     }
464
465     // Load the original entity, if any.
466     if ($id_exists && !isset($entity->original)) {
467       $entity->original = $this->loadUnchanged($id);
468     }
469
470     // Allow code to run before saving.
471     $entity->preSave($this);
472     $this->invokeHook('presave', $entity);
473
474     return $id;
475   }
476
477   /**
478    * Performs storage-specific saving of the entity.
479    *
480    * @param int|string $id
481    *   The original entity ID.
482    * @param \Drupal\Core\Entity\EntityInterface $entity
483    *   The entity to save.
484    *
485    * @return bool|int
486    *   If the record insert or update failed, returns FALSE. If it succeeded,
487    *   returns SAVED_NEW or SAVED_UPDATED, depending on the operation performed.
488    */
489   abstract protected function doSave($id, EntityInterface $entity);
490
491   /**
492    * Performs post save entity processing.
493    *
494    * @param \Drupal\Core\Entity\EntityInterface $entity
495    *   The saved entity.
496    * @param bool $update
497    *   Specifies whether the entity is being updated or created.
498    */
499   protected function doPostSave(EntityInterface $entity, $update) {
500     $this->resetCache([$entity->id()]);
501
502     // The entity is no longer new.
503     $entity->enforceIsNew(FALSE);
504
505     // Allow code to run after saving.
506     $entity->postSave($this, $update);
507     $this->invokeHook($update ? 'update' : 'insert', $entity);
508
509     // After saving, this is now the "original entity", and subsequent saves
510     // will be updates instead of inserts, and updates must always be able to
511     // correctly identify the original entity.
512     $entity->setOriginalId($entity->id());
513
514     unset($entity->original);
515   }
516
517   /**
518    * Builds an entity query.
519    *
520    * @param \Drupal\Core\Entity\Query\QueryInterface $entity_query
521    *   EntityQuery instance.
522    * @param array $values
523    *   An associative array of properties of the entity, where the keys are the
524    *   property names and the values are the values those properties must have.
525    */
526   protected function buildPropertyQuery(QueryInterface $entity_query, array $values) {
527     foreach ($values as $name => $value) {
528       // Cast scalars to array so we can consistently use an IN condition.
529       $entity_query->condition($name, (array) $value, 'IN');
530     }
531   }
532
533   /**
534    * {@inheritdoc}
535    */
536   public function loadByProperties(array $values = []) {
537     // Build a query to fetch the entity IDs.
538     $entity_query = $this->getQuery();
539     $entity_query->accessCheck(FALSE);
540     $this->buildPropertyQuery($entity_query, $values);
541     $result = $entity_query->execute();
542     return $result ? $this->loadMultiple($result) : [];
543   }
544
545   /**
546    * {@inheritdoc}
547    */
548   public function hasData() {
549     return (bool) $this->getQuery()
550       ->accessCheck(FALSE)
551       ->range(0, 1)
552       ->execute();
553   }
554
555   /**
556    * {@inheritdoc}
557    */
558   public function getQuery($conjunction = 'AND') {
559     // Access the service directly rather than entity.query factory so the
560     // storage's current entity type is used.
561     return \Drupal::service($this->getQueryServiceName())->get($this->entityType, $conjunction);
562   }
563
564   /**
565    * {@inheritdoc}
566    */
567   public function getAggregateQuery($conjunction = 'AND') {
568     // Access the service directly rather than entity.query factory so the
569     // storage's current entity type is used.
570     return \Drupal::service($this->getQueryServiceName())->getAggregate($this->entityType, $conjunction);
571   }
572
573   /**
574    * Gets the name of the service for the query for this entity storage.
575    *
576    * @return string
577    *   The name of the service for the query for this entity storage.
578    */
579   abstract protected function getQueryServiceName();
580
581 }