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