Entity.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665
  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\Core\Config\Entity\Exception\ConfigEntityIdLengthException;
  7. use Drupal\Core\Entity\Exception\UndefinedLinkTemplateException;
  8. use Drupal\Core\Language\Language;
  9. use Drupal\Core\Language\LanguageInterface;
  10. use Drupal\Core\Link;
  11. use Drupal\Core\Session\AccountInterface;
  12. use Drupal\Core\Url;
  13. use Symfony\Component\Routing\Exception\MissingMandatoryParametersException;
  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. catch (MissingMandatoryParametersException $e) {
  306. return FALSE;
  307. }
  308. return TRUE;
  309. });
  310. }
  311. /**
  312. * {@inheritdoc}
  313. */
  314. public function access($operation, AccountInterface $account = NULL, $return_as_object = FALSE) {
  315. if ($operation == 'create') {
  316. return $this->entityTypeManager()
  317. ->getAccessControlHandler($this->entityTypeId)
  318. ->createAccess($this->bundle(), $account, [], $return_as_object);
  319. }
  320. return $this->entityTypeManager()
  321. ->getAccessControlHandler($this->entityTypeId)
  322. ->access($this, $operation, $account, $return_as_object);
  323. }
  324. /**
  325. * {@inheritdoc}
  326. */
  327. public function language() {
  328. if ($key = $this->getEntityType()->getKey('langcode')) {
  329. $langcode = $this->$key;
  330. $language = $this->languageManager()->getLanguage($langcode);
  331. if ($language) {
  332. return $language;
  333. }
  334. }
  335. // Make sure we return a proper language object.
  336. $langcode = !empty($this->langcode) ? $this->langcode : LanguageInterface::LANGCODE_NOT_SPECIFIED;
  337. $language = new Language(['id' => $langcode]);
  338. return $language;
  339. }
  340. /**
  341. * {@inheritdoc}
  342. */
  343. public function save() {
  344. $storage = $this->entityTypeManager()->getStorage($this->entityTypeId);
  345. return $storage->save($this);
  346. }
  347. /**
  348. * {@inheritdoc}
  349. */
  350. public function delete() {
  351. if (!$this->isNew()) {
  352. $this->entityTypeManager()->getStorage($this->entityTypeId)->delete([$this->id() => $this]);
  353. }
  354. }
  355. /**
  356. * {@inheritdoc}
  357. */
  358. public function createDuplicate() {
  359. $duplicate = clone $this;
  360. $entity_type = $this->getEntityType();
  361. // Reset the entity ID and indicate that this is a new entity.
  362. $duplicate->{$entity_type->getKey('id')} = NULL;
  363. $duplicate->enforceIsNew();
  364. // Check if the entity type supports UUIDs and generate a new one if so.
  365. if ($entity_type->hasKey('uuid')) {
  366. $duplicate->{$entity_type->getKey('uuid')} = $this->uuidGenerator()->generate();
  367. }
  368. return $duplicate;
  369. }
  370. /**
  371. * {@inheritdoc}
  372. */
  373. public function getEntityType() {
  374. return $this->entityTypeManager()->getDefinition($this->getEntityTypeId());
  375. }
  376. /**
  377. * {@inheritdoc}
  378. */
  379. public function preSave(EntityStorageInterface $storage) {
  380. // Check if this is an entity bundle.
  381. if ($this->getEntityType()->getBundleOf()) {
  382. // Throw an exception if the bundle ID is longer than 32 characters.
  383. if (mb_strlen($this->id()) > EntityTypeInterface::BUNDLE_MAX_LENGTH) {
  384. throw new ConfigEntityIdLengthException("Attempt to create a bundle with an ID longer than " . EntityTypeInterface::BUNDLE_MAX_LENGTH . " characters: $this->id().");
  385. }
  386. }
  387. }
  388. /**
  389. * {@inheritdoc}
  390. */
  391. public function postSave(EntityStorageInterface $storage, $update = TRUE) {
  392. $this->invalidateTagsOnSave($update);
  393. }
  394. /**
  395. * {@inheritdoc}
  396. */
  397. public static function preCreate(EntityStorageInterface $storage, array &$values) {
  398. }
  399. /**
  400. * {@inheritdoc}
  401. */
  402. public function postCreate(EntityStorageInterface $storage) {
  403. }
  404. /**
  405. * {@inheritdoc}
  406. */
  407. public static function preDelete(EntityStorageInterface $storage, array $entities) {
  408. }
  409. /**
  410. * {@inheritdoc}
  411. */
  412. public static function postDelete(EntityStorageInterface $storage, array $entities) {
  413. static::invalidateTagsOnDelete($storage->getEntityType(), $entities);
  414. }
  415. /**
  416. * {@inheritdoc}
  417. */
  418. public static function postLoad(EntityStorageInterface $storage, array &$entities) {
  419. }
  420. /**
  421. * {@inheritdoc}
  422. */
  423. public function referencedEntities() {
  424. return [];
  425. }
  426. /**
  427. * {@inheritdoc}
  428. */
  429. public function getCacheContexts() {
  430. return $this->cacheContexts;
  431. }
  432. /**
  433. * {@inheritdoc}
  434. */
  435. public function getCacheTagsToInvalidate() {
  436. // @todo Add bundle-specific listing cache tag?
  437. // https://www.drupal.org/node/2145751
  438. if ($this->isNew()) {
  439. return [];
  440. }
  441. return [$this->entityTypeId . ':' . $this->id()];
  442. }
  443. /**
  444. * {@inheritdoc}
  445. */
  446. public function getCacheTags() {
  447. if ($this->cacheTags) {
  448. return Cache::mergeTags($this->getCacheTagsToInvalidate(), $this->cacheTags);
  449. }
  450. return $this->getCacheTagsToInvalidate();
  451. }
  452. /**
  453. * {@inheritdoc}
  454. */
  455. public function getCacheMaxAge() {
  456. return $this->cacheMaxAge;
  457. }
  458. /**
  459. * {@inheritdoc}
  460. */
  461. public static function load($id) {
  462. $entity_type_repository = \Drupal::service('entity_type.repository');
  463. $entity_type_manager = \Drupal::entityTypeManager();
  464. $storage = $entity_type_manager->getStorage($entity_type_repository->getEntityTypeFromClass(get_called_class()));
  465. return $storage->load($id);
  466. }
  467. /**
  468. * {@inheritdoc}
  469. */
  470. public static function loadMultiple(array $ids = NULL) {
  471. $entity_type_repository = \Drupal::service('entity_type.repository');
  472. $entity_type_manager = \Drupal::entityTypeManager();
  473. $storage = $entity_type_manager->getStorage($entity_type_repository->getEntityTypeFromClass(get_called_class()));
  474. return $storage->loadMultiple($ids);
  475. }
  476. /**
  477. * {@inheritdoc}
  478. */
  479. public static function create(array $values = []) {
  480. $entity_type_repository = \Drupal::service('entity_type.repository');
  481. $entity_type_manager = \Drupal::entityTypeManager();
  482. $storage = $entity_type_manager->getStorage($entity_type_repository->getEntityTypeFromClass(get_called_class()));
  483. return $storage->create($values);
  484. }
  485. /**
  486. * Invalidates an entity's cache tags upon save.
  487. *
  488. * @param bool $update
  489. * TRUE if the entity has been updated, or FALSE if it has been inserted.
  490. */
  491. protected function invalidateTagsOnSave($update) {
  492. // An entity was created or updated: invalidate its list cache tags. (An
  493. // updated entity may start to appear in a listing because it now meets that
  494. // listing's filtering requirements. A newly created entity may start to
  495. // appear in listings because it did not exist before.)
  496. $tags = $this->getEntityType()->getListCacheTags();
  497. if ($this->hasLinkTemplate('canonical')) {
  498. // Creating or updating an entity may change a cached 403 or 404 response.
  499. $tags = Cache::mergeTags($tags, ['4xx-response']);
  500. }
  501. if ($update) {
  502. // An existing entity was updated, also invalidate its unique cache tag.
  503. $tags = Cache::mergeTags($tags, $this->getCacheTagsToInvalidate());
  504. }
  505. Cache::invalidateTags($tags);
  506. }
  507. /**
  508. * Invalidates an entity's cache tags upon delete.
  509. *
  510. * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
  511. * The entity type definition.
  512. * @param \Drupal\Core\Entity\EntityInterface[] $entities
  513. * An array of entities.
  514. */
  515. protected static function invalidateTagsOnDelete(EntityTypeInterface $entity_type, array $entities) {
  516. $tags = $entity_type->getListCacheTags();
  517. foreach ($entities as $entity) {
  518. // An entity was deleted: invalidate its own cache tag, but also its list
  519. // cache tags. (A deleted entity may cause changes in a paged list on
  520. // other pages than the one it's on. The one it's on is handled by its own
  521. // cache tag, but subsequent list pages would not be invalidated, hence we
  522. // must invalidate its list cache tags as well.)
  523. $tags = Cache::mergeTags($tags, $entity->getCacheTagsToInvalidate());
  524. }
  525. Cache::invalidateTags($tags);
  526. }
  527. /**
  528. * {@inheritdoc}
  529. */
  530. public function getOriginalId() {
  531. // By default, entities do not support renames and do not have original IDs.
  532. return NULL;
  533. }
  534. /**
  535. * {@inheritdoc}
  536. */
  537. public function setOriginalId($id) {
  538. // By default, entities do not support renames and do not have original IDs.
  539. // If the specified ID is anything except NULL, this should mark this entity
  540. // as no longer new.
  541. if ($id !== NULL) {
  542. $this->enforceIsNew(FALSE);
  543. }
  544. return $this;
  545. }
  546. /**
  547. * {@inheritdoc}
  548. */
  549. public function toArray() {
  550. return [];
  551. }
  552. /**
  553. * {@inheritdoc}
  554. */
  555. public function getTypedData() {
  556. if (!isset($this->typedData)) {
  557. $class = \Drupal::typedDataManager()->getDefinition('entity')['class'];
  558. $this->typedData = $class::createFromEntity($this);
  559. }
  560. return $this->typedData;
  561. }
  562. /**
  563. * {@inheritdoc}
  564. */
  565. public function __sleep() {
  566. $this->typedData = NULL;
  567. return $this->traitSleep();
  568. }
  569. /**
  570. * {@inheritdoc}
  571. */
  572. public function getConfigDependencyKey() {
  573. return $this->getEntityType()->getConfigDependencyKey();
  574. }
  575. /**
  576. * {@inheritdoc}
  577. */
  578. public function getConfigDependencyName() {
  579. return $this->getEntityTypeId() . ':' . $this->bundle() . ':' . $this->uuid();
  580. }
  581. /**
  582. * {@inheritdoc}
  583. */
  584. public function getConfigTarget() {
  585. // For content entities, use the UUID for the config target identifier.
  586. // This ensures that references to the target can be deployed reliably.
  587. return $this->uuid();
  588. }
  589. }