Newer
Older
Alex Pott
committed
<?php
namespace Drupal\Core\Entity;
Alex Bronstein
committed
use Drupal\Component\Plugin\Definition\PluginDefinition;
Alex Pott
committed
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Entity\Exception\EntityTypeIdLengthException;
Angie Byron
committed
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslatableMarkup;
Alex Pott
committed
/**
* Provides an implementation of an entity type and its metadata.
Angie Byron
committed
*
* @ingroup entity_api
Alex Pott
committed
*/
Alex Bronstein
committed
class EntityType extends PluginDefinition implements EntityTypeInterface {
Alex Pott
committed
Angie Byron
committed
use StringTranslationTrait;
Alex Pott
committed
/**
* Indicates whether entities should be statically cached.
*
* @var bool
*/
protected $static_cache = TRUE;
Alex Pott
committed
/**
* Indicates whether the rendered output of entities should be cached.
*
* @var bool
*/
protected $render_cache = TRUE;
Alex Pott
committed
/**
* Indicates if the persistent cache of field data should be used.
*
* @var bool
*/
protected $persistent_cache = TRUE;
Alex Pott
committed
/**
* An array of entity keys.
*
* @var array
*/
protected $entity_keys = [];
Alex Pott
committed
/**
* The unique identifier of this entity type.
*
* @var string
*/
protected $id;
Alex Pott
committed
/**
* The name of the original entity type class.
*
* This is only set if the class name is changed.
*
* @var string
*/
protected $originalClass;
Alex Pott
committed
/**
Alex Pott
committed
* An array of handlers.
Alex Pott
committed
*
* @var array
*/
protected $handlers = [];
Alex Pott
committed
/**
* The name of the default administrative permission.
*
* @var string
*/
protected $admin_permission;
/**
* The permission granularity level.
*
* The allowed values are respectively "entity_type" or "bundle".
Alex Pott
committed
*
* @var string
Alex Pott
committed
*/
protected $permission_granularity = 'entity_type';
Alex Pott
committed
/**
* Link templates using the URI template syntax.
*
* @var array
*/
protected $links = [];
Alex Pott
committed
/**
* The name of a callback that returns the label of the entity.
*
* @var callable|null
*
* @deprecated in Drupal 8.0.x-dev and will be removed before Drupal 9.0.0.
* Use Drupal\Core\Entity\EntityInterface::label() for complex label
* generation as needed.
*
* @see \Drupal\Core\Entity\EntityInterface::label()
*
* @todo Remove usages of label_callback https://www.drupal.org/node/2450793.
Alex Pott
committed
*/
protected $label_callback = NULL;
Alex Pott
committed
/**
* The name of the entity type which provides bundles.
*
* @var string
*/
protected $bundle_entity_type = NULL;
Alex Pott
committed
/**
* The name of the entity type for which bundles are provided.
*
* @var string|null
Alex Pott
committed
*/
protected $bundle_of = NULL;
Alex Pott
committed
/**
* The human-readable name of the entity bundles, e.g. Vocabulary.
*
* @var string|null
Alex Pott
committed
*/
protected $bundle_label = NULL;
Alex Pott
committed
/**
* The name of the entity type's base table.
*
* @var string|null
Alex Pott
committed
*/
protected $base_table = NULL;
Alex Pott
committed
/**
* The name of the entity type's revision data table.
*
* @var string|null
Alex Pott
committed
*/
protected $revision_data_table = NULL;
Alex Pott
committed
/**
* The name of the entity type's revision table.
*
* @var string|null
Alex Pott
committed
*/
protected $revision_table = NULL;
Alex Pott
committed
/**
* The name of the entity type's data table.
*
* @var string|null
Alex Pott
committed
*/
protected $data_table = NULL;
Alex Pott
committed
/**
* Indicates whether entities of this type have multilingual support.
*
* @var bool
*/
protected $translatable = FALSE;
/**
* Indicates whether the revision form fields should be added to the form.
*
* @var bool
*/
protected $show_revision_ui = FALSE;
Alex Pott
committed
/**
* The human-readable name of the type.
*
* @var string
*
* @see \Drupal\Core\Entity\EntityTypeInterface::getLabel()
Alex Pott
committed
*/
protected $label = '';
Alex Pott
committed
Angie Byron
committed
/**
* The human-readable label for a collection of entities of the type.
*
* @var string
*
* @see \Drupal\Core\Entity\EntityTypeInterface::getCollectionLabel()
Angie Byron
committed
*/
protected $label_collection = '';
/**
* The indefinite singular name of the type.
*
* @var string
*
* @see \Drupal\Core\Entity\EntityTypeInterface::getSingularLabel()
*/
protected $label_singular = '';
/**
* The indefinite plural name of the type.
*
* @var string
*
* @see \Drupal\Core\Entity\EntityTypeInterface::getPluralLabel()
*/
protected $label_plural = '';
/**
* A definite singular/plural name of the type.
*
* Needed keys: "singular" and "plural".
*
* @var string[]
*
* @see \Drupal\Core\Entity\EntityTypeInterface::getCountLabel()
*/
protected $label_count = [];
Alex Pott
committed
/**
* A callable that can be used to provide the entity URI.
*
* @var callable|null
Alex Pott
committed
*/
protected $uri_callback = NULL;
Alex Pott
committed
Angie Byron
committed
/**
* The machine name of the entity type group.
*/
protected $group;
/**
* The human-readable name of the entity type group.
*/
protected $group_label;
Alex Pott
committed
/**
* The route name used by field UI to attach its management pages.
*
* @var string
*/
protected $field_ui_base_route;
Angie Byron
committed
/**
* Indicates whether this entity type is commonly used as a reference target.
*
* This is used by the Entity reference field to promote an entity type in the
* add new field select list in Field UI.
*
* @var bool
*/
protected $common_reference_target = FALSE;
/**
* The list cache contexts for this entity type.
*
* @var string[]
*/
protected $list_cache_contexts = [];
/**
* The list cache tags for this entity type.
*
* @var string[]
*/
protected $list_cache_tags = [];
/**
* Entity constraint definitions.
*
* @var array[]
*/
protected $constraints = [];
Alex Pott
committed
/**
* Any additional properties and values.
*
* @var array
*/
protected $additional = [];
Alex Pott
committed
/**
* Constructs a new EntityType.
*
* @param array $definition
* An array of values from the annotation.
*
* @throws \Drupal\Core\Entity\Exception\EntityTypeIdLengthException
* Thrown when attempting to instantiate an entity type with too long ID.
Alex Pott
committed
*/
public function __construct($definition) {
// Throw an exception if the entity type ID is longer than 32 characters.
if (Unicode::strlen($definition['id']) > static::ID_MAX_LENGTH) {
catch
committed
throw new EntityTypeIdLengthException('Attempt to create an entity type with an ID longer than ' . static::ID_MAX_LENGTH . " characters: {$definition['id']}.");
}
Alex Pott
committed
foreach ($definition as $property => $value) {
Alex Pott
committed
$this->set($property, $value);
Alex Pott
committed
}
Alex Pott
committed
// Ensure defaults.
$this->entity_keys += [
Alex Pott
committed
'revision' => '',
'bundle' => '',
'langcode' => '',
Alex Pott
committed
'default_langcode' => 'default_langcode',
'revision_translation_affected' => 'revision_translation_affected',
];
$this->handlers += [
'access' => 'Drupal\Core\Entity\EntityAccessControlHandler',
];
Alex Bronstein
committed
if (isset($this->handlers['storage'])) {
$this->checkStorageClass($this->handlers['storage']);
}
// Automatically add the EntityChanged constraint if the entity type tracks
// the changed time.
if ($this->entityClassImplements(EntityChangedInterface::class)) {
$this->addConstraint('EntityChanged');
}
// Ensure a default list cache tag is set.
if (empty($this->list_cache_tags)) {
$this->list_cache_tags = [$definition['id'] . '_list'];
}
Alex Pott
committed
}
/**
* {@inheritdoc}
*/
public function get($property) {
Alex Pott
committed
if (property_exists($this, $property)) {
$value = isset($this->{$property}) ? $this->{$property} : NULL;
}
else {
$value = isset($this->additional[$property]) ? $this->additional[$property] : NULL;
}
return $value;
Alex Pott
committed
}
/**
* {@inheritdoc}
*/
public function set($property, $value) {
Alex Pott
committed
if (property_exists($this, $property)) {
$this->{$property} = $value;
}
else {
$this->additional[$property] = $value;
}
Alex Pott
committed
return $this;
}
/**
* {@inheritdoc}
*/
public function isStaticallyCacheable() {
return $this->static_cache;
Alex Pott
committed
}
/**
* {@inheritdoc}
*/
public function isRenderCacheable() {
return $this->render_cache;
Alex Pott
committed
}
/**
* {@inheritdoc}
*/
public function isPersistentlyCacheable() {
return $this->persistent_cache;
Alex Pott
committed
}
/**
* {@inheritdoc}
*/
public function getKeys() {
Alex Pott
committed
return $this->entity_keys;
Alex Pott
committed
}
/**
* {@inheritdoc}
*/
public function getKey($key) {
$keys = $this->getKeys();
return isset($keys[$key]) ? $keys[$key] : FALSE;
}
/**
* {@inheritdoc}
*/
public function hasKey($key) {
$keys = $this->getKeys();
return !empty($keys[$key]);
}
Alex Pott
committed
/**
* {@inheritdoc}
*/
public function getOriginalClass() {
return $this->originalClass ?: $this->class;
}
Alex Pott
committed
/**
* {@inheritdoc}
*/
public function setClass($class) {
Alex Pott
committed
if (!$this->originalClass && $this->class) {
// If the original class is currently not set, set it to the current
// class, assume that is the original class name.
$this->originalClass = $this->class;
}
Alex Bronstein
committed
return parent::setClass($class);
Alex Pott
committed
}
/**
* {@inheritdoc}
*/
public function entityClassImplements($interface) {
return is_subclass_of($this->getClass(), $interface);
}
Alex Pott
committed
/**
* {@inheritdoc}
*/
public function isSubclassOf($class) {
return $this->entityClassImplements($class);
Alex Pott
committed
}
/**
* {@inheritdoc}
*/
Alex Pott
committed
public function getHandlerClasses() {
return $this->handlers;
Alex Pott
committed
}
/**
* {@inheritdoc}
*/
Alex Pott
committed
public function getHandlerClass($handler_type, $nested = FALSE) {
if ($this->hasHandlerClass($handler_type, $nested)) {
$handlers = $this->getHandlerClasses();
return $nested ? $handlers[$handler_type][$nested] : $handlers[$handler_type];
}
Alex Pott
committed
}
/**
* {@inheritdoc}
*/
Alex Pott
committed
public function setHandlerClass($handler_type, $value) {
$this->handlers[$handler_type] = $value;
Alex Pott
committed
return $this;
}
/**
* {@inheritdoc}
*/
Alex Pott
committed
public function hasHandlerClass($handler_type, $nested = FALSE) {
$handlers = $this->getHandlerClasses();
if (!isset($handlers[$handler_type]) || ($nested && !isset($handlers[$handler_type][$nested]))) {
return FALSE;
}
Alex Pott
committed
$handler = $handlers[$handler_type];
if ($nested) {
Alex Pott
committed
$handler = $handler[$nested];
}
Alex Pott
committed
return class_exists($handler);
}
/**
* {@inheritdoc}
*/
public function getStorageClass() {
Alex Pott
committed
return $this->getHandlerClass('storage');
}
/**
* {@inheritdoc}
*/
public function setStorageClass($class) {
Alex Bronstein
committed
$this->checkStorageClass($class);
Alex Pott
committed
$this->handlers['storage'] = $class;
return $this;
}
Alex Bronstein
committed
/**
* Checks that the provided class is compatible with the current entity type.
Alex Bronstein
committed
*
* @param string $class
* The class to check.
*/
protected function checkStorageClass($class) {
// Nothing to check by default.
}
/**
* {@inheritdoc}
*/
public function getFormClass($operation) {
Alex Pott
committed
return $this->getHandlerClass('form', $operation);
Alex Pott
committed
}
/**
* {@inheritdoc}
*/
public function setFormClass($operation, $class) {
Alex Pott
committed
$this->handlers['form'][$operation] = $class;
Alex Pott
committed
return $this;
}
/**
* {@inheritdoc}
*/
public function hasFormClasses() {
Alex Pott
committed
return !empty($this->handlers['form']);
}
/**
* {@inheritdoc}
*/
public function hasRouteProviders() {
return !empty($this->handlers['route_provider']);
}
/**
* {@inheritdoc}
*/
public function getListBuilderClass() {
Alex Pott
committed
return $this->getHandlerClass('list_builder');
}
/**
* {@inheritdoc}
*/
public function setListBuilderClass($class) {
Alex Pott
committed
$this->handlers['list_builder'] = $class;
Alex Pott
committed
return $this;
}
/**
* {@inheritdoc}
*/
public function hasListBuilderClass() {
Alex Pott
committed
return $this->hasHandlerClass('list_builder');
}
/**
* {@inheritdoc}
*/
public function getViewBuilderClass() {
Alex Pott
committed
return $this->getHandlerClass('view_builder');
}
/**
* {@inheritdoc}
*/
public function setViewBuilderClass($class) {
Alex Pott
committed
$this->handlers['view_builder'] = $class;
return $this;
}
/**
* {@inheritdoc}
*/
public function hasViewBuilderClass() {
Alex Pott
committed
return $this->hasHandlerClass('view_builder');
}
/**
* {@inheritdoc}
*/
public function getRouteProviderClasses() {
return !empty($this->handlers['route_provider']) ? $this->handlers['route_provider'] : [];
}
/**
* {@inheritdoc}
*/
public function getAccessControlClass() {
Alex Pott
committed
return $this->getHandlerClass('access');
}
/**
* {@inheritdoc}
*/
public function setAccessClass($class) {
Alex Pott
committed
$this->handlers['access'] = $class;
return $this;
}
Alex Pott
committed
/**
* {@inheritdoc}
*/
public function getAdminPermission() {
return $this->admin_permission ?: FALSE;
}
/**
* {@inheritdoc}
*/
public function getPermissionGranularity() {
return $this->permission_granularity;
Alex Pott
committed
}
/**
* {@inheritdoc}
*/
public function getLinkTemplates() {
return $this->links;
Alex Pott
committed
}
/**
* {@inheritdoc}
*/
public function getLinkTemplate($key) {
$links = $this->getLinkTemplates();
return isset($links[$key]) ? $links[$key] : FALSE;
}
/**
* {@inheritdoc}
*/
public function hasLinkTemplate($key) {
$links = $this->getLinkTemplates();
return isset($links[$key]);
}
/**
* {@inheritdoc}
*/
public function setLinkTemplate($key, $path) {
if ($path[0] !== '/') {
throw new \InvalidArgumentException('Link templates accepts paths, which have to start with a leading slash.');
}
$this->links[$key] = $path;
Alex Pott
committed
return $this;
}
/**
* {@inheritdoc}
*/
public function getLabelCallback() {
return $this->label_callback;
Alex Pott
committed
}
/**
* {@inheritdoc}
*/
public function setLabelCallback($callback) {
$this->label_callback = $callback;
return $this;
}
/**
* {@inheritdoc}
*/
public function hasLabelCallback() {
return isset($this->label_callback);
}
/**
* {@inheritdoc}
*/
public function getBundleEntityType() {
return $this->bundle_entity_type;
Alex Pott
committed
}
/**
* {@inheritdoc}
*/
public function getBundleOf() {
return $this->bundle_of;
Alex Pott
committed
}
/**
* {@inheritdoc}
*/
public function getBundleLabel() {
// If there is no bundle label defined, try to provide some sensible
// fallbacks.
if (!empty($this->bundle_label)) {
return (string) $this->bundle_label;
}
elseif ($bundle_entity_type_id = $this->getBundleEntityType()) {
return (string) \Drupal::entityTypeManager()->getDefinition($bundle_entity_type_id)->getLabel();
}
return (string) new TranslatableMarkup('@type_label bundle', ['@type_label' => $this->getLabel()], [], $this->getStringTranslation());
Alex Pott
committed
}
/**
* {@inheritdoc}
*/
public function getBaseTable() {
return $this->base_table;
Alex Pott
committed
}
/**
* {@inheritdoc}
*/
public function showRevisionUi() {
return $this->isRevisionable() && $this->show_revision_ui;
}
Alex Pott
committed
/**
* {@inheritdoc}
*/
public function isTranslatable() {
return !empty($this->translatable);
}
catch
committed
/**
* {@inheritdoc}
*/
public function isRevisionable() {
// Entity types are revisionable if a revision key has been specified.
return $this->hasKey('revision');
}
Alex Pott
committed
/**
* {@inheritdoc}
*/
public function getRevisionDataTable() {
return $this->revision_data_table;
Alex Pott
committed
}
/**
* {@inheritdoc}
*/
public function getRevisionTable() {
return $this->revision_table;
Alex Pott
committed
}
/**
* {@inheritdoc}
*/
public function getDataTable() {
return $this->data_table;
Alex Pott
committed
}
/**
* {@inheritdoc}
*/
public function getLabel() {
return $this->label;
Alex Pott
committed
}
/**
* {@inheritdoc}
*/
public function getLowercaseLabel() {
return Unicode::strtolower($this->getLabel());
}
Angie Byron
committed
/**
* {@inheritdoc}
*/
public function getCollectionLabel() {
if (empty($this->label_collection)) {
$label = $this->getLabel();
$this->label_collection = new TranslatableMarkup('@label entities', ['@label' => $label], [], $this->getStringTranslation());
}
return $this->label_collection;
}
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
/**
* {@inheritdoc}
*/
public function getSingularLabel() {
if (empty($this->label_singular)) {
$lowercase_label = $this->getLowercaseLabel();
$this->label_singular = $lowercase_label;
}
return $this->label_singular;
}
/**
* {@inheritdoc}
*/
public function getPluralLabel() {
if (empty($this->label_plural)) {
$lowercase_label = $this->getLowercaseLabel();
$this->label_plural = new TranslatableMarkup('@label entities', ['@label' => $lowercase_label], [], $this->getStringTranslation());
}
return $this->label_plural;
}
/**
* {@inheritdoc}
*/
public function getCountLabel($count) {
if (empty($this->label_count)) {
return $this->formatPlural($count, '@count @label', '@count @label entities', ['@label' => $this->getLowercaseLabel()], ['context' => 'Entity type label']);
}
$context = isset($this->label_count['context']) ? $this->label_count['context'] : 'Entity type label';
return $this->formatPlural($count, $this->label_count['singular'], $this->label_count['plural'], ['context' => $context]);
}
Alex Pott
committed
/**
* {@inheritdoc}
*/
public function getUriCallback() {
return $this->uri_callback;
Alex Pott
committed
}
/**
* {@inheritdoc}
*/
public function setUriCallback($callback) {
$this->uri_callback = $callback;
return $this;
}
Angie Byron
committed
/**
* {@inheritdoc}
*/
public function getGroup() {
return $this->group;
}
/**
* {@inheritdoc}
*/
public function getGroupLabel() {
return !empty($this->group_label) ? $this->group_label : $this->t('Other', [], ['context' => 'Entity type group']);
Angie Byron
committed
}
/**
* {@inheritdoc}
*/
public function getListCacheContexts() {
return $this->list_cache_contexts;
}
/**
* {@inheritdoc}
*/
public function getListCacheTags() {
return $this->list_cache_tags;
}
Angie Byron
committed
/**
* {@inheritdoc}
*/
public function getConfigDependencyKey() {
// Return 'content' for the default implementation as important distinction
// is that dependencies on other configuration entities are hard
// dependencies and have to exist before creating the dependent entity.
return 'content';
}
Angie Byron
committed
/**
* {@inheritdoc}
*/
public function isCommonReferenceTarget() {
return $this->common_reference_target;
}
/**
* {@inheritdoc}
*/
public function getConstraints() {
return $this->constraints;
}
/**
* {@inheritdoc}
*/
public function setConstraints(array $constraints) {
$this->constraints = $constraints;
return $this;
}
/**
* {@inheritdoc}
*/
public function addConstraint($constraint_name, $options = NULL) {
$this->constraints[$constraint_name] = $options;
return $this;
}
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
/**
* {@inheritdoc}
*/
public function getBundleConfigDependency($bundle) {
// If this entity type uses entities to manage its bundles then depend on
// the bundle entity.
if ($bundle_entity_type_id = $this->getBundleEntityType()) {
if (!$bundle_entity = \Drupal::entityManager()->getStorage($bundle_entity_type_id)->load($bundle)) {
throw new \LogicException(sprintf('Missing bundle entity, entity type %s, entity id %s.', $bundle_entity_type_id, $bundle));
}
$config_dependency = [
'type' => 'config',
'name' => $bundle_entity->getConfigDependencyName(),
];
}
else {
// Depend on the provider of the entity type.
$config_dependency = [
'type' => 'module',
'name' => $this->getProvider(),
];
}
return $config_dependency;
}