Entity.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662
  1. <?php
  2. namespace Drupal\Core\Entity;
  3. use Drupal\Core\Cache\Cache;
  4. use Drupal\Core\Cache\RefinableCacheableDependencyTrait;
  5. use Drupal\Core\DependencyInjection\DependencySerializationTrait;
  6. use Drupal\Component\Utility\Unicode;
  7. use Drupal\Core\Config\Entity\Exception\ConfigEntityIdLengthException;
  8. use Drupal\Core\Entity\Exception\UndefinedLinkTemplateException;
  9. use Drupal\Core\Language\Language;
  10. use Drupal\Core\Language\LanguageInterface;
  11. use Drupal\Core\Link;
  12. use Drupal\Core\Session\AccountInterface;
  13. use Drupal\Core\Url;
  14. use Symfony\Component\Routing\Exception\RouteNotFoundException;
  15. /**
  16. * Defines a base entity class.
  17. */
  18. abstract class Entity implements EntityInterface {
  19. use RefinableCacheableDependencyTrait;
  20. use DependencySerializationTrait {
  21. __sleep as traitSleep;
  22. }
  23. /**
  24. * The entity type.
  25. *
  26. * @var string
  27. */
  28. protected $entityTypeId;
  29. /**
  30. * Boolean indicating whether the entity should be forced to be new.
  31. *
  32. * @var bool
  33. */
  34. protected $enforceIsNew;
  35. /**
  36. * A typed data object wrapping this entity.
  37. *
  38. * @var \Drupal\Core\TypedData\ComplexDataInterface
  39. */
  40. protected $typedData;
  41. /**
  42. * Constructs an Entity object.
  43. *
  44. * @param array $values
  45. * An array of values to set, keyed by property name. If the entity type
  46. * has bundles, the bundle key has to be specified.
  47. * @param string $entity_type
  48. * The type of the entity to create.
  49. */
  50. public function __construct(array $values, $entity_type) {
  51. $this->entityTypeId = $entity_type;
  52. // Set initial values.
  53. foreach ($values as $key => $value) {
  54. $this->$key = $value;
  55. }
  56. }
  57. /**
  58. * Gets the entity manager.
  59. *
  60. * @return \Drupal\Core\Entity\EntityManagerInterface
  61. *
  62. * @deprecated in Drupal 8.0.0 and will be removed before Drupal 9.0.0.
  63. * Use \Drupal::entityTypeManager() instead in most cases. If the needed
  64. * method is not on \Drupal\Core\Entity\EntityTypeManagerInterface, see the
  65. * deprecated \Drupal\Core\Entity\EntityManager to find the
  66. * correct interface or service.
  67. */
  68. protected function entityManager() {
  69. return \Drupal::entityManager();
  70. }
  71. /**
  72. * Gets the entity type manager.
  73. *
  74. * @return \Drupal\Core\Entity\EntityTypeManagerInterface
  75. */
  76. protected function entityTypeManager() {
  77. return \Drupal::entityTypeManager();
  78. }
  79. /**
  80. * Gets the entity type bundle info service.
  81. *
  82. * @return \Drupal\Core\Entity\EntityTypeBundleInfoInterface
  83. */
  84. protected function entityTypeBundleInfo() {
  85. return \Drupal::service('entity_type.bundle.info');
  86. }
  87. /**
  88. * Gets the language manager.
  89. *
  90. * @return \Drupal\Core\Language\LanguageManagerInterface
  91. */
  92. protected function languageManager() {
  93. return \Drupal::languageManager();
  94. }
  95. /**
  96. * Gets the UUID generator.
  97. *
  98. * @return \Drupal\Component\Uuid\UuidInterface
  99. */
  100. protected function uuidGenerator() {
  101. return \Drupal::service('uuid');
  102. }
  103. /**
  104. * {@inheritdoc}
  105. */
  106. public function id() {
  107. return isset($this->id) ? $this->id : NULL;
  108. }
  109. /**
  110. * {@inheritdoc}
  111. */
  112. public function uuid() {
  113. return isset($this->uuid) ? $this->uuid : NULL;
  114. }
  115. /**
  116. * {@inheritdoc}
  117. */
  118. public function isNew() {
  119. return !empty($this->enforceIsNew) || !$this->id();
  120. }
  121. /**
  122. * {@inheritdoc}
  123. */
  124. public function enforceIsNew($value = TRUE) {
  125. $this->enforceIsNew = $value;
  126. return $this;
  127. }
  128. /**
  129. * {@inheritdoc}
  130. */
  131. public function getEntityTypeId() {
  132. return $this->entityTypeId;
  133. }
  134. /**
  135. * {@inheritdoc}
  136. */
  137. public function bundle() {
  138. return $this->entityTypeId;
  139. }
  140. /**
  141. * {@inheritdoc}
  142. */
  143. public function label() {
  144. $label = NULL;
  145. $entity_type = $this->getEntityType();
  146. if (($label_callback = $entity_type->getLabelCallback()) && is_callable($label_callback)) {
  147. $label = call_user_func($label_callback, $this);
  148. }
  149. elseif (($label_key = $entity_type->getKey('label')) && isset($this->{$label_key})) {
  150. $label = $this->{$label_key};
  151. }
  152. return $label;
  153. }
  154. /**
  155. * {@inheritdoc}
  156. */
  157. public function urlInfo($rel = 'canonical', array $options = []) {
  158. return $this->toUrl($rel, $options);
  159. }
  160. /**
  161. * {@inheritdoc}
  162. */
  163. public function toUrl($rel = 'canonical', array $options = []) {
  164. if ($this->id() === NULL) {
  165. throw new EntityMalformedException(sprintf('The "%s" entity cannot have a URI as it does not have an ID', $this->getEntityTypeId()));
  166. }
  167. // The links array might contain URI templates set in annotations.
  168. $link_templates = $this->linkTemplates();
  169. // Links pointing to the current revision point to the actual entity. So
  170. // instead of using the 'revision' link, use the 'canonical' link.
  171. if ($rel === 'revision' && $this instanceof RevisionableInterface && $this->isDefaultRevision()) {
  172. $rel = 'canonical';
  173. }
  174. if (isset($link_templates[$rel])) {
  175. $route_parameters = $this->urlRouteParameters($rel);
  176. $route_name = "entity.{$this->entityTypeId}." . str_replace(['-', 'drupal:'], ['_', ''], $rel);
  177. $uri = new Url($route_name, $route_parameters);
  178. }
  179. else {
  180. $bundle = $this->bundle();
  181. // A bundle-specific callback takes precedence over the generic one for
  182. // the entity type.
  183. $bundles = $this->entityTypeBundleInfo()->getBundleInfo($this->getEntityTypeId());
  184. if (isset($bundles[$bundle]['uri_callback'])) {
  185. $uri_callback = $bundles[$bundle]['uri_callback'];
  186. }
  187. elseif ($entity_uri_callback = $this->getEntityType()->getUriCallback()) {
  188. $uri_callback = $entity_uri_callback;
  189. }
  190. // Invoke the callback to get the URI. If there is no callback, use the
  191. // default URI format.
  192. if (isset($uri_callback) && is_callable($uri_callback)) {
  193. $uri = call_user_func($uri_callback, $this);
  194. }
  195. else {
  196. throw new UndefinedLinkTemplateException("No link template '$rel' found for the '{$this->getEntityTypeId()}' entity type");
  197. }
  198. }
  199. // Pass the entity data through as options, so that alter functions do not
  200. // need to look up this entity again.
  201. $uri
  202. ->setOption('entity_type', $this->getEntityTypeId())
  203. ->setOption('entity', $this);
  204. // Display links by default based on the current language.
  205. // Link relations that do not require an existing entity should not be
  206. // affected by this entity's language, however.
  207. if (!in_array($rel, ['collection', 'add-page', 'add-form'], TRUE)) {
  208. $options += ['language' => $this->language()];
  209. }
  210. $uri_options = $uri->getOptions();
  211. $uri_options += $options;
  212. return $uri->setOptions($uri_options);
  213. }
  214. /**
  215. * {@inheritdoc}
  216. */
  217. public function hasLinkTemplate($rel) {
  218. $link_templates = $this->linkTemplates();
  219. return isset($link_templates[$rel]);
  220. }
  221. /**
  222. * Gets an array link templates.
  223. *
  224. * @return array
  225. * An array of link templates containing paths.
  226. */
  227. protected function linkTemplates() {
  228. return $this->getEntityType()->getLinkTemplates();
  229. }
  230. /**
  231. * {@inheritdoc}
  232. */
  233. public function link($text = NULL, $rel = 'canonical', array $options = []) {
  234. return $this->toLink($text, $rel, $options)->toString();
  235. }
  236. /**
  237. * {@inheritdoc}
  238. */
  239. public function toLink($text = NULL, $rel = 'canonical', array $options = []) {
  240. if (!isset($text)) {
  241. $text = $this->label();
  242. }
  243. $url = $this->toUrl($rel);
  244. $options += $url->getOptions();
  245. $url->setOptions($options);
  246. return new Link($text, $url);
  247. }
  248. /**
  249. * {@inheritdoc}
  250. */
  251. public function url($rel = 'canonical', $options = []) {
  252. // While self::toUrl() will throw an exception if the entity has no id,
  253. // the expected result for a URL is always a string.
  254. if ($this->id() === NULL || !$this->hasLinkTemplate($rel)) {
  255. return '';
  256. }
  257. $uri = $this->toUrl($rel);
  258. $options += $uri->getOptions();
  259. $uri->setOptions($options);
  260. return $uri->toString();
  261. }
  262. /**
  263. * Gets an array of placeholders for this entity.
  264. *
  265. * Individual entity classes may override this method to add additional
  266. * placeholders if desired. If so, they should be sure to replicate the
  267. * property caching logic.
  268. *
  269. * @param string $rel
  270. * The link relationship type, for example: canonical or edit-form.
  271. *
  272. * @return array
  273. * An array of URI placeholders.
  274. */
  275. protected function urlRouteParameters($rel) {
  276. $uri_route_parameters = [];
  277. if (!in_array($rel, ['collection', 'add-page', 'add-form'], TRUE)) {
  278. // The entity ID is needed as a route parameter.
  279. $uri_route_parameters[$this->getEntityTypeId()] = $this->id();
  280. }
  281. if ($rel === 'add-form' && ($this->getEntityType()->hasKey('bundle'))) {
  282. $parameter_name = $this->getEntityType()->getBundleEntityType() ?: $this->getEntityType()->getKey('bundle');
  283. $uri_route_parameters[$parameter_name] = $this->bundle();
  284. }
  285. if ($rel === 'revision' && $this instanceof RevisionableInterface) {
  286. $uri_route_parameters[$this->getEntityTypeId() . '_revision'] = $this->getRevisionId();
  287. }
  288. return $uri_route_parameters;
  289. }
  290. /**
  291. * {@inheritdoc}
  292. */
  293. public function uriRelationships() {
  294. return array_filter(array_keys($this->linkTemplates()), function ($link_relation_type) {
  295. // It's not guaranteed that every link relation type also has a
  296. // corresponding route. For some, additional modules or configuration may
  297. // be necessary. The interface demands that we only return supported URI
  298. // relationships.
  299. try {
  300. $this->toUrl($link_relation_type)->toString(TRUE)->getGeneratedUrl();
  301. }
  302. catch (RouteNotFoundException $e) {
  303. return FALSE;
  304. }
  305. return TRUE;
  306. });
  307. }
  308. /**
  309. * {@inheritdoc}
  310. */
  311. public function access($operation, AccountInterface $account = NULL, $return_as_object = FALSE) {
  312. if ($operation == 'create') {
  313. return $this->entityTypeManager()
  314. ->getAccessControlHandler($this->entityTypeId)
  315. ->createAccess($this->bundle(), $account, [], $return_as_object);
  316. }
  317. return $this->entityTypeManager()
  318. ->getAccessControlHandler($this->entityTypeId)
  319. ->access($this, $operation, $account, $return_as_object);
  320. }
  321. /**
  322. * {@inheritdoc}
  323. */
  324. public function language() {
  325. if ($key = $this->getEntityType()->getKey('langcode')) {
  326. $langcode = $this->$key;
  327. $language = $this->languageManager()->getLanguage($langcode);
  328. if ($language) {
  329. return $language;
  330. }
  331. }
  332. // Make sure we return a proper language object.
  333. $langcode = !empty($this->langcode) ? $this->langcode : LanguageInterface::LANGCODE_NOT_SPECIFIED;
  334. $language = new Language(['id' => $langcode]);
  335. return $language;
  336. }
  337. /**
  338. * {@inheritdoc}
  339. */
  340. public function save() {
  341. $storage = $this->entityTypeManager()->getStorage($this->entityTypeId);
  342. return $storage->save($this);
  343. }
  344. /**
  345. * {@inheritdoc}
  346. */
  347. public function delete() {
  348. if (!$this->isNew()) {
  349. $this->entityTypeManager()->getStorage($this->entityTypeId)->delete([$this->id() => $this]);
  350. }
  351. }
  352. /**
  353. * {@inheritdoc}
  354. */
  355. public function createDuplicate() {
  356. $duplicate = clone $this;
  357. $entity_type = $this->getEntityType();
  358. // Reset the entity ID and indicate that this is a new entity.
  359. $duplicate->{$entity_type->getKey('id')} = NULL;
  360. $duplicate->enforceIsNew();
  361. // Check if the entity type supports UUIDs and generate a new one if so.
  362. if ($entity_type->hasKey('uuid')) {
  363. $duplicate->{$entity_type->getKey('uuid')} = $this->uuidGenerator()->generate();
  364. }
  365. return $duplicate;
  366. }
  367. /**
  368. * {@inheritdoc}
  369. */
  370. public function getEntityType() {
  371. return $this->entityTypeManager()->getDefinition($this->getEntityTypeId());
  372. }
  373. /**
  374. * {@inheritdoc}
  375. */
  376. public function preSave(EntityStorageInterface $storage) {
  377. // Check if this is an entity bundle.
  378. if ($this->getEntityType()->getBundleOf()) {
  379. // Throw an exception if the bundle ID is longer than 32 characters.
  380. if (Unicode::strlen($this->id()) > EntityTypeInterface::BUNDLE_MAX_LENGTH) {
  381. throw new ConfigEntityIdLengthException("Attempt to create a bundle with an ID longer than " . EntityTypeInterface::BUNDLE_MAX_LENGTH . " characters: $this->id().");
  382. }
  383. }
  384. }
  385. /**
  386. * {@inheritdoc}
  387. */
  388. public function postSave(EntityStorageInterface $storage, $update = TRUE) {
  389. $this->invalidateTagsOnSave($update);
  390. }
  391. /**
  392. * {@inheritdoc}
  393. */
  394. public static function preCreate(EntityStorageInterface $storage, array &$values) {
  395. }
  396. /**
  397. * {@inheritdoc}
  398. */
  399. public function postCreate(EntityStorageInterface $storage) {
  400. }
  401. /**
  402. * {@inheritdoc}
  403. */
  404. public static function preDelete(EntityStorageInterface $storage, array $entities) {
  405. }
  406. /**
  407. * {@inheritdoc}
  408. */
  409. public static function postDelete(EntityStorageInterface $storage, array $entities) {
  410. static::invalidateTagsOnDelete($storage->getEntityType(), $entities);
  411. }
  412. /**
  413. * {@inheritdoc}
  414. */
  415. public static function postLoad(EntityStorageInterface $storage, array &$entities) {
  416. }
  417. /**
  418. * {@inheritdoc}
  419. */
  420. public function referencedEntities() {
  421. return [];
  422. }
  423. /**
  424. * {@inheritdoc}
  425. */
  426. public function getCacheContexts() {
  427. return $this->cacheContexts;
  428. }
  429. /**
  430. * {@inheritdoc}
  431. */
  432. public function getCacheTagsToInvalidate() {
  433. // @todo Add bundle-specific listing cache tag?
  434. // https://www.drupal.org/node/2145751
  435. if ($this->isNew()) {
  436. return [];
  437. }
  438. return [$this->entityTypeId . ':' . $this->id()];
  439. }
  440. /**
  441. * {@inheritdoc}
  442. */
  443. public function getCacheTags() {
  444. if ($this->cacheTags) {
  445. return Cache::mergeTags($this->getCacheTagsToInvalidate(), $this->cacheTags);
  446. }
  447. return $this->getCacheTagsToInvalidate();
  448. }
  449. /**
  450. * {@inheritdoc}
  451. */
  452. public function getCacheMaxAge() {
  453. return $this->cacheMaxAge;
  454. }
  455. /**
  456. * {@inheritdoc}
  457. */
  458. public static function load($id) {
  459. $entity_type_repository = \Drupal::service('entity_type.repository');
  460. $entity_type_manager = \Drupal::entityTypeManager();
  461. $storage = $entity_type_manager->getStorage($entity_type_repository->getEntityTypeFromClass(get_called_class()));
  462. return $storage->load($id);
  463. }
  464. /**
  465. * {@inheritdoc}
  466. */
  467. public static function loadMultiple(array $ids = NULL) {
  468. $entity_type_repository = \Drupal::service('entity_type.repository');
  469. $entity_type_manager = \Drupal::entityTypeManager();
  470. $storage = $entity_type_manager->getStorage($entity_type_repository->getEntityTypeFromClass(get_called_class()));
  471. return $storage->loadMultiple($ids);
  472. }
  473. /**
  474. * {@inheritdoc}
  475. */
  476. public static function create(array $values = []) {
  477. $entity_type_repository = \Drupal::service('entity_type.repository');
  478. $entity_type_manager = \Drupal::entityTypeManager();
  479. $storage = $entity_type_manager->getStorage($entity_type_repository->getEntityTypeFromClass(get_called_class()));
  480. return $storage->create($values);
  481. }
  482. /**
  483. * Invalidates an entity's cache tags upon save.
  484. *
  485. * @param bool $update
  486. * TRUE if the entity has been updated, or FALSE if it has been inserted.
  487. */
  488. protected function invalidateTagsOnSave($update) {
  489. // An entity was created or updated: invalidate its list cache tags. (An
  490. // updated entity may start to appear in a listing because it now meets that
  491. // listing's filtering requirements. A newly created entity may start to
  492. // appear in listings because it did not exist before.)
  493. $tags = $this->getEntityType()->getListCacheTags();
  494. if ($this->hasLinkTemplate('canonical')) {
  495. // Creating or updating an entity may change a cached 403 or 404 response.
  496. $tags = Cache::mergeTags($tags, ['4xx-response']);
  497. }
  498. if ($update) {
  499. // An existing entity was updated, also invalidate its unique cache tag.
  500. $tags = Cache::mergeTags($tags, $this->getCacheTagsToInvalidate());
  501. }
  502. Cache::invalidateTags($tags);
  503. }
  504. /**
  505. * Invalidates an entity's cache tags upon delete.
  506. *
  507. * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
  508. * The entity type definition.
  509. * @param \Drupal\Core\Entity\EntityInterface[] $entities
  510. * An array of entities.
  511. */
  512. protected static function invalidateTagsOnDelete(EntityTypeInterface $entity_type, array $entities) {
  513. $tags = $entity_type->getListCacheTags();
  514. foreach ($entities as $entity) {
  515. // An entity was deleted: invalidate its own cache tag, but also its list
  516. // cache tags. (A deleted entity may cause changes in a paged list on
  517. // other pages than the one it's on. The one it's on is handled by its own
  518. // cache tag, but subsequent list pages would not be invalidated, hence we
  519. // must invalidate its list cache tags as well.)
  520. $tags = Cache::mergeTags($tags, $entity->getCacheTagsToInvalidate());
  521. }
  522. Cache::invalidateTags($tags);
  523. }
  524. /**
  525. * {@inheritdoc}
  526. */
  527. public function getOriginalId() {
  528. // By default, entities do not support renames and do not have original IDs.
  529. return NULL;
  530. }
  531. /**
  532. * {@inheritdoc}
  533. */
  534. public function setOriginalId($id) {
  535. // By default, entities do not support renames and do not have original IDs.
  536. // If the specified ID is anything except NULL, this should mark this entity
  537. // as no longer new.
  538. if ($id !== NULL) {
  539. $this->enforceIsNew(FALSE);
  540. }
  541. return $this;
  542. }
  543. /**
  544. * {@inheritdoc}
  545. */
  546. public function toArray() {
  547. return [];
  548. }
  549. /**
  550. * {@inheritdoc}
  551. */
  552. public function getTypedData() {
  553. if (!isset($this->typedData)) {
  554. $class = \Drupal::typedDataManager()->getDefinition('entity')['class'];
  555. $this->typedData = $class::createFromEntity($this);
  556. }
  557. return $this->typedData;
  558. }
  559. /**
  560. * {@inheritdoc}
  561. */
  562. public function __sleep() {
  563. $this->typedData = NULL;
  564. return $this->traitSleep();
  565. }
  566. /**
  567. * {@inheritdoc}
  568. */
  569. public function getConfigDependencyKey() {
  570. return $this->getEntityType()->getConfigDependencyKey();
  571. }
  572. /**
  573. * {@inheritdoc}
  574. */
  575. public function getConfigDependencyName() {
  576. return $this->getEntityTypeId() . ':' . $this->bundle() . ':' . $this->uuid();
  577. }
  578. /**
  579. * {@inheritdoc}
  580. */
  581. public function getConfigTarget() {
  582. // For content entities, use the UUID for the config target identifier.
  583. // This ensures that references to the target can be deployed reliably.
  584. return $this->uuid();
  585. }
  586. }