Newer
Older
Angie Byron
committed
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityManager.
*/
namespace Drupal\Core\Entity;
use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
Angie Byron
committed
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Component\Utility\String;
Alex Pott
committed
use Drupal\Core\DependencyInjection\ClassResolverInterface;
Alex Pott
committed
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheBackendInterface;
Alex Pott
committed
use Drupal\Core\Entity\Exception\AmbiguousEntityClassException;
use Drupal\Core\Entity\Exception\NoCorrespondingEntityClassException;
Dries Buytaert
committed
use Drupal\Core\Extension\ModuleHandlerInterface;
Alex Pott
committed
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionListenerInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Plugin\DefaultPluginManager;
Alex Pott
committed
use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
Angie Byron
committed
use Drupal\Core\StringTranslation\StringTranslationTrait;
Alex Pott
committed
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\Core\TypedData\TranslatableInterface;
use Drupal\Core\TypedData\TypedDataManager;
Alex Pott
committed
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
Angie Byron
committed
/**
* Manages entity type plugin definitions.
*
* Each entity type definition array is set in the entity type's
* annotation and altered by hook_entity_type_alter().
Angie Byron
committed
*
* The defaults for the plugin definition are provided in
Angie Byron
committed
* \Drupal\Core\Entity\EntityManagerInterface::defaults.
Angie Byron
committed
*
* @see \Drupal\Core\Entity\Annotation\EntityType
* @see \Drupal\Core\Entity\EntityInterface
Alex Pott
committed
* @see \Drupal\Core\Entity\EntityTypeInterface
* @see hook_entity_type_alter()
Angie Byron
committed
*/
class EntityManager extends DefaultPluginManager implements EntityManagerInterface, ContainerAwareInterface {
Alex Pott
committed
use ContainerAwareTrait;
Angie Byron
committed
use StringTranslationTrait;
Angie Byron
committed
/**
* Extra fields by bundle.
*
* @var array
*/
protected $extraFields = array();
Angie Byron
committed
/**
Alex Pott
committed
* Contains instantiated handlers keyed by handler type and entity type.
Angie Byron
committed
*
* @var array
*/
Alex Pott
committed
protected $handlers = array();
Angie Byron
committed
Dries Buytaert
committed
/**
* Static cache of base field definitions.
Dries Buytaert
committed
*
* @var array
*/
protected $baseFieldDefinitions;
Dries Buytaert
committed
/**
* Static cache of field definitions per bundle and entity type.
*
* @var array
*/
protected $fieldDefinitions;
Alex Pott
committed
/**
* Static cache of field storage definitions per entity type.
*
* Elements of the array:
Alex Pott
committed
* - $entity_type_id: \Drupal\Core\Field\BaseFieldDefinition[]
Alex Pott
committed
*
* @var array
*/
protected $fieldStorageDefinitions;
Alex Pott
committed
/**
* The string translationManager.
*
* @var \Drupal\Core\StringTranslation\TranslationInterface
*/
protected $translationManager;
Alex Pott
committed
/**
* The class resolver.
*
* @var \Drupal\Core\DependencyInjection\ClassResolverInterface
*/
protected $classResolver;
/**
* The typed data manager.
*
* @var \Drupal\Core\TypedData\TypedDataManager
*/
protected $typedDataManager;
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
Alex Pott
committed
/**
* The keyvalue collection for tracking installed definitions.
*
* @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
*/
protected $installedDefinitions;
Angie Byron
committed
/**
* Static cache of bundle information.
*
* @var array
*/
protected $bundleInfo;
/**
* Static cache of display modes information.
*
* @var array
*/
protected $displayModeInfo = array();
Alex Pott
committed
/**
* An array keyed by entity type. Each value is an array whose keys are
* field names and whose value is an array with two entries:
* - type: The field type.
* - bundles: The bundles in which the field appears.
*
* @return array
*/
protected $fieldMap = array();
/**
* An array keyed by field type. Each value is an array whose key are entity
* types including arrays in the same form that $fieldMap.
*
* It helps access the mapping between types and fields by the field type.
*
* @var array
*/
protected $fieldMapByFieldType = array();
Alex Pott
committed
/**
* Contains cached mappings of class names to entity types.
*
* @var array
*/
protected $classNameEntityTypeMap = array();
Angie Byron
committed
/**
* Constructs a new Entity plugin manager.
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations,
Dries Buytaert
committed
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache
* The cache backend to use.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
Dries Buytaert
committed
* The language manager.
Alex Pott
committed
* @param \Drupal\Core\StringTranslation\TranslationInterface $translation_manager
* The string translationManager.
Alex Pott
committed
* @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver
* The class resolver.
Alex Pott
committed
* @param \Drupal\Core\TypedData\TypedDataManager $typed_data_manager
* The typed data manager.
* @param \Drupal\Core\KeyValueStore\KeyValueStoreInterface $installed_definitions
* The keyvalue collection for tracking installed definitions.
Angie Byron
committed
*/
Alex Pott
committed
public function __construct(\Traversable $namespaces, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache, LanguageManagerInterface $language_manager, TranslationInterface $translation_manager, ClassResolverInterface $class_resolver, TypedDataManager $typed_data_manager, KeyValueStoreInterface $installed_definitions) {
Alex Pott
committed
parent::__construct('Entity', $namespaces, $module_handler, 'Drupal\Core\Entity\EntityInterface', 'Drupal\Core\Entity\Annotation\EntityType');
Dries Buytaert
committed
$this->setCacheBackend($cache, 'entity_type', array('entity_types' => TRUE));
$this->alterInfo('entity_type');
$this->languageManager = $language_manager;
$this->translationManager = $translation_manager;
Alex Pott
committed
$this->classResolver = $class_resolver;
$this->typedDataManager = $typed_data_manager;
Alex Pott
committed
$this->installedDefinitions = $installed_definitions;
Angie Byron
committed
}
/**
* {@inheritdoc}
*/
public function clearCachedDefinitions() {
parent::clearCachedDefinitions();
$this->clearCachedBundles();
$this->clearCachedFieldDefinitions();
Alex Pott
committed
$this->classNameEntityTypeMap = array();
Alex Pott
committed
$this->handlers = array();
}
/**
* {@inheritdoc}
*/
protected function findDefinitions() {
$definitions = $this->discovery->getDefinitions();
// Directly call the hook implementations to pass the definitions to them
// by reference, so new entity types can be added.
foreach ($this->moduleHandler->getImplementations('entity_type_build') as $module) {
$function = $module . '_' . 'entity_type_build';
$function($definitions);
}
foreach ($definitions as $plugin_id => $definition) {
$this->processDefinition($definition, $plugin_id);
}
if ($this->alterHook) {
$this->moduleHandler->alter($this->alterHook, $definitions);
}
return $definitions;
}
/**
Angie Byron
committed
* {@inheritdoc}
*/
public function getDefinition($entity_type_id, $exception_on_invalid = TRUE) {
if (($entity_type = parent::getDefinition($entity_type_id, FALSE)) && class_exists($entity_type->getClass())) {
return $entity_type;
Alex Pott
committed
}
elseif (!$exception_on_invalid) {
return NULL;
}
Angie Byron
committed
throw new PluginNotFoundException($entity_type_id, sprintf('The "%s" entity type does not exist.', $entity_type_id));
}
Angie Byron
committed
/**
Angie Byron
committed
* {@inheritdoc}
Angie Byron
committed
*/
Alex Pott
committed
public function hasHandler($entity_type, $handler_type) {
if ($definition = $this->getDefinition($entity_type, FALSE)) {
Alex Pott
committed
return $definition->hasHandlerClass($handler_type);
Angie Byron
committed
}
return FALSE;
Angie Byron
committed
}
/**
Angie Byron
committed
* {@inheritdoc}
Angie Byron
committed
*/
catch
committed
public function getStorage($entity_type) {
Alex Pott
committed
return $this->getHandler($entity_type, 'storage');
Angie Byron
committed
}
/**
Angie Byron
committed
* {@inheritdoc}
Angie Byron
committed
*/
public function getListBuilder($entity_type) {
Alex Pott
committed
return $this->getHandler($entity_type, 'list_builder');
Angie Byron
committed
}
/**
Angie Byron
committed
* {@inheritdoc}
Angie Byron
committed
*/
Alex Pott
committed
public function getFormObject($entity_type, $operation) {
Alex Pott
committed
if (!isset($this->handlers['form'][$operation][$entity_type])) {
if (!$class = $this->getDefinition($entity_type, TRUE)->getFormClass($operation)) {
throw new InvalidPluginDefinitionException($entity_type, sprintf('The "%s" entity type did not specify a "%s" form class.', $entity_type, $operation));
}
Alex Pott
committed
Alex Pott
committed
$form_object = $this->classResolver->getInstanceFromDefinition($class);
Alex Pott
committed
Alex Pott
committed
$form_object
Alex Pott
committed
->setStringTranslation($this->translationManager)
Alex Pott
committed
->setModuleHandler($this->moduleHandler)
->setOperation($operation);
Alex Pott
committed
$this->handlers['form'][$operation][$entity_type] = $form_object;
Angie Byron
committed
}
Alex Pott
committed
return $this->handlers['form'][$operation][$entity_type];
Angie Byron
committed
}
/**
Angie Byron
committed
* {@inheritdoc}
Angie Byron
committed
*/
Alex Pott
committed
public function getViewBuilder($entity_type) {
Alex Pott
committed
return $this->getHandler($entity_type, 'view_builder');
Angie Byron
committed
}
/**
Angie Byron
committed
* {@inheritdoc}
Angie Byron
committed
*/
public function getAccessControlHandler($entity_type) {
Alex Pott
committed
return $this->getHandler($entity_type, 'access');
Alex Pott
committed
}
/**
Alex Pott
committed
* {@inheritdoc}
Alex Pott
committed
*/
Alex Pott
committed
public function getHandler($entity_type, $handler_type) {
if (!isset($this->handlers[$handler_type][$entity_type])) {
$definition = $this->getDefinition($entity_type);
Alex Pott
committed
$class = $definition->getHandlerClass($handler_type);
if (!$class) {
Alex Pott
committed
throw new InvalidPluginDefinitionException($entity_type, sprintf('The "%s" entity type did not specify a %s handler.', $entity_type, $handler_type));
}
Alex Pott
committed
if (is_subclass_of($class, 'Drupal\Core\Entity\EntityHandlerInterface')) {
$handler = $class::createInstance($this->container, $definition);
}
else {
Alex Pott
committed
$handler = new $class($definition);
}
Alex Pott
committed
if (method_exists($handler, 'setModuleHandler')) {
$handler->setModuleHandler($this->moduleHandler);
}
Alex Pott
committed
if (method_exists($handler, 'setStringTranslation')) {
$handler->setStringTranslation($this->translationManager);
}
Alex Pott
committed
$this->handlers[$handler_type][$entity_type] = $handler;
Angie Byron
committed
}
Alex Pott
committed
return $this->handlers[$handler_type][$entity_type];
Angie Byron
committed
}
Dries Buytaert
committed
/**
Angie Byron
committed
* {@inheritdoc}
Dries Buytaert
committed
*/
public function getBaseFieldDefinitions($entity_type_id) {
// Check the static cache.
if (!isset($this->baseFieldDefinitions[$entity_type_id])) {
// Not prepared, try to load from cache.
$cid = 'entity_base_field_definitions:' . $entity_type_id . ':' . $this->languageManager->getCurrentLanguage()->id;
if ($cache = $this->cacheBackend->get($cid)) {
$this->baseFieldDefinitions[$entity_type_id] = $cache->data;
Dries Buytaert
committed
}
else {
// Rebuild the definitions and put it into the cache.
$this->baseFieldDefinitions[$entity_type_id] = $this->buildBaseFieldDefinitions($entity_type_id);
$this->cacheBackend->set($cid, $this->baseFieldDefinitions[$entity_type_id], Cache::PERMANENT, array('entity_types' => TRUE, 'entity_field_info' => TRUE));
}
}
return $this->baseFieldDefinitions[$entity_type_id];
}
/**
* Builds base field definitions for an entity type.
*
* @param string $entity_type_id
* The entity type ID. Only entity types that implement
Alex Pott
committed
* \Drupal\Core\Entity\ContentEntityInterface are supported.
*
* @return \Drupal\Core\Field\FieldDefinitionInterface[]
* An array of field definitions, keyed by field name.
*
* @throws \LogicException
Angie Byron
committed
* Thrown if a config entity type is given or if one of the entity keys is
* flagged as translatable.
*/
protected function buildBaseFieldDefinitions($entity_type_id) {
$entity_type = $this->getDefinition($entity_type_id);
$class = $entity_type->getClass();
Angie Byron
committed
// Fail with an exception for config entity types.
if (!$entity_type->isSubclassOf('\Drupal\Core\Entity\ContentEntityInterface')) {
throw new \LogicException(String::format('Getting the base fields is not supported for entity type @type.', array('@type' => $entity_type->getLabel())));
}
catch
committed
// Retrieve base field definitions and assign them the entity type provider.
$base_field_definitions = $class::baseFieldDefinitions($entity_type);
catch
committed
$provider = $entity_type->getProvider();
foreach ($base_field_definitions as $definition) {
Alex Pott
committed
// @todo Remove this check once FieldDefinitionInterface exposes a proper
// provider setter. See https://drupal.org/node/2225961.
Alex Pott
committed
if ($definition instanceof BaseFieldDefinition) {
catch
committed
$definition->setProvider($provider);
}
}
catch
committed
// Retrieve base field definitions from modules.
foreach ($this->moduleHandler->getImplementations('entity_base_field_info') as $module) {
$module_definitions = $this->moduleHandler->invoke($module, 'entity_base_field_info', array($entity_type));
if (!empty($module_definitions)) {
// Ensure the provider key actually matches the name of the provider
// defining the field.
foreach ($module_definitions as $field_name => $definition) {
Alex Pott
committed
// @todo Remove this check once FieldDefinitionInterface exposes a
// proper provider setter. See https://drupal.org/node/2225961.
Alex Pott
committed
if ($definition instanceof BaseFieldDefinition && $definition->getProvider() == NULL) {
catch
committed
$definition->setProvider($module);
}
$base_field_definitions[$field_name] = $definition;
}
}
}
// Automatically set the field name, target entity type and bundle
// for non-configurable fields.
foreach ($base_field_definitions as $field_name => $base_field_definition) {
Alex Pott
committed
if ($base_field_definition instanceof BaseFieldDefinition) {
$base_field_definition->setName($field_name);
$base_field_definition->setTargetEntityTypeId($entity_type_id);
$base_field_definition->setBundle(NULL);
}
}
// Invoke alter hook.
$this->moduleHandler->alter('entity_base_field_info', $base_field_definitions, $entity_type);
// Ensure defined entity keys are there and have proper revisionable and
// translatable values.
$keys = array_filter($entity_type->getKeys() + array('langcode' => 'langcode'));
foreach ($keys as $key => $field_name) {
if (isset($base_field_definitions[$field_name]) && in_array($key, array('id', 'revision', 'uuid', 'bundle')) && $base_field_definitions[$field_name]->isRevisionable()) {
throw new \LogicException(String::format('The @field field cannot be revisionable as it is used as @key entity key.', array('@field' => $base_field_definitions[$field_name]->getLabel(), '@key' => $key)));
}
if (isset($base_field_definitions[$field_name]) && in_array($key, array('id', 'revision', 'uuid', 'bundle', 'langcode')) && $base_field_definitions[$field_name]->isTranslatable()) {
throw new \LogicException(String::format('The @field field cannot be translatable as it is used as @key entity key.', array('@field' => $base_field_definitions[$field_name]->getLabel(), '@key' => $key)));
Dries Buytaert
committed
}
}
return $base_field_definitions;
}
/**
* {@inheritdoc}
*/
public function getFieldDefinitions($entity_type_id, $bundle) {
if (!isset($this->fieldDefinitions[$entity_type_id][$bundle])) {
$base_field_definitions = $this->getBaseFieldDefinitions($entity_type_id);
// Not prepared, try to load from cache.
$cid = 'entity_bundle_field_definitions:' . $entity_type_id . ':' . $bundle . ':' . $this->languageManager->getCurrentLanguage()->id;
if ($cache = $this->cacheBackend->get($cid)) {
$bundle_field_definitions = $cache->data;
}
else {
// Rebuild the definitions and put it into the cache.
$bundle_field_definitions = $this->buildBundleFieldDefinitions($entity_type_id, $bundle, $base_field_definitions);
$this->cacheBackend->set($cid, $bundle_field_definitions, Cache::PERMANENT, array('entity_types' => TRUE, 'entity_field_info' => TRUE));
}
// Field definitions consist of the bundle specific overrides and the
// base fields, merge them together. Use array_replace() to replace base
// fields with by bundle overrides and keep them in order, append
// additional by bundle fields.
$this->fieldDefinitions[$entity_type_id][$bundle] = array_replace($base_field_definitions, $bundle_field_definitions);
Dries Buytaert
committed
}
return $this->fieldDefinitions[$entity_type_id][$bundle];
}
/**
* Builds field definitions for a specific bundle within an entity type.
*
* @param string $entity_type_id
* The entity type ID. Only entity types that implement
* \Drupal\Core\Entity\ContentEntityInterface are supported.
* @param string $bundle
* The bundle.
* @param \Drupal\Core\Field\FieldDefinitionInterface[] $base_field_definitions
* The list of base field definitions.
*
* @return \Drupal\Core\Field\FieldDefinitionInterface[]
* An array of bundle field definitions, keyed by field name. Does
* not include base fields.
*/
protected function buildBundleFieldDefinitions($entity_type_id, $bundle, array $base_field_definitions) {
$entity_type = $this->getDefinition($entity_type_id);
$class = $entity_type->getClass();
// Allow the entity class to provide bundle fields and bundle-specific
// overrides of base fields.
$bundle_field_definitions = $class::bundleFieldDefinitions($entity_type, $bundle, $base_field_definitions);
// Load base field overrides from configuration. These take precedence over
// base field overrides returned above.
$base_field_override_ids = array_map(function($field_name) use ($entity_type_id, $bundle) {
return $entity_type_id . '.' . $bundle . '.' . $field_name;
}, array_keys($base_field_definitions));
$base_field_overrides = $this->getStorage('base_field_override')->loadMultiple($base_field_override_ids);
foreach ($base_field_overrides as $base_field_override) {
/** @var \Drupal\Core\Field\Entity\BaseFieldOverride $base_field_override */
$field_name = $base_field_override->getName();
$bundle_field_definitions[$field_name] = $base_field_override;
}
catch
committed
$provider = $entity_type->getProvider();
foreach ($bundle_field_definitions as $definition) {
Alex Pott
committed
// @todo Remove this check once FieldDefinitionInterface exposes a proper
// provider setter. See https://drupal.org/node/2225961.
Alex Pott
committed
if ($definition instanceof BaseFieldDefinition) {
catch
committed
$definition->setProvider($provider);
}
}
catch
committed
// Retrieve base field definitions from modules.
foreach ($this->moduleHandler->getImplementations('entity_bundle_field_info') as $module) {
$module_definitions = $this->moduleHandler->invoke($module, 'entity_bundle_field_info', array($entity_type, $bundle, $base_field_definitions));
if (!empty($module_definitions)) {
// Ensure the provider key actually matches the name of the provider
// defining the field.
foreach ($module_definitions as $field_name => $definition) {
Alex Pott
committed
// @todo Remove this check once FieldDefinitionInterface exposes a
// proper provider setter. See https://drupal.org/node/2225961.
Alex Pott
committed
if ($definition instanceof BaseFieldDefinition) {
catch
committed
$definition->setProvider($module);
}
$bundle_field_definitions[$field_name] = $definition;
}
}
}
// Automatically set the field name, target entity type and bundle
// for non-configurable fields.
foreach ($bundle_field_definitions as $field_name => $field_definition) {
Alex Pott
committed
if ($field_definition instanceof BaseFieldDefinition) {
$field_definition->setName($field_name);
$field_definition->setTargetEntityTypeId($entity_type_id);
$field_definition->setBundle($bundle);
Dries Buytaert
committed
}
}
// Invoke 'per bundle' alter hook.
$this->moduleHandler->alter('entity_bundle_field_info', $bundle_field_definitions, $entity_type, $bundle);
return $bundle_field_definitions;
Dries Buytaert
committed
}
Alex Pott
committed
/**
* {@inheritdoc}
*/
public function getFieldStorageDefinitions($entity_type_id) {
if (!isset($this->fieldStorageDefinitions[$entity_type_id])) {
$this->fieldStorageDefinitions[$entity_type_id] = array();
// Add all non-computed base fields.
foreach ($this->getBaseFieldDefinitions($entity_type_id) as $field_name => $definition) {
if (!$definition->isComputed()) {
$this->fieldStorageDefinitions[$entity_type_id][$field_name] = $definition;
}
}
// Not prepared, try to load from cache.
$cid = 'entity_field_storage_definitions:' . $entity_type_id . ':' . $this->languageManager->getCurrentLanguage()->id;
if ($cache = $this->cacheBackend->get($cid)) {
Alex Pott
committed
$field_storage_definitions = $cache->data;
}
else {
// Rebuild the definitions and put it into the cache.
$field_storage_definitions = $this->buildFieldStorageDefinitions($entity_type_id);
$this->cacheBackend->set($cid, $field_storage_definitions, Cache::PERMANENT, array('entity_types' => TRUE, 'entity_field_info' => TRUE));
Alex Pott
committed
}
$this->fieldStorageDefinitions[$entity_type_id] += $field_storage_definitions;
}
return $this->fieldStorageDefinitions[$entity_type_id];
}
Alex Pott
committed
/**
* {@inheritdoc}
*/
public function getFieldMap() {
if (!$this->fieldMap) {
// Not prepared, try to load from cache.
$cid = 'entity_field_map';
if ($cache = $this->cacheBackend->get($cid)) {
Alex Pott
committed
$this->fieldMap = $cache->data;
}
else {
// Rebuild the definitions and put it into the cache.
foreach ($this->getDefinitions() as $entity_type_id => $entity_type) {
if ($entity_type->isSubclassOf('\Drupal\Core\Entity\ContentEntityInterface')) {
foreach ($this->getBundleInfo($entity_type_id) as $bundle => $bundle_info) {
foreach ($this->getFieldDefinitions($entity_type_id, $bundle) as $field_name => $field_definition) {
$this->fieldMap[$entity_type_id][$field_name]['type'] = $field_definition->getType();
$this->fieldMap[$entity_type_id][$field_name]['bundles'][] = $bundle;
}
}
}
}
$this->cacheBackend->set($cid, $this->fieldMap, Cache::PERMANENT, array('entity_types' => TRUE, 'entity_field_info' => TRUE));
Alex Pott
committed
}
}
return $this->fieldMap;
}
/**
* {@inheritdoc}
*/
public function getFieldMapByFieldType($field_type) {
if (!isset($this->fieldMapByFieldType[$field_type])) {
$filtered_map = array();
$map = $this->getFieldMap();
foreach ($map as $entity_type => $fields) {
foreach ($fields as $field_name => $field_info) {
if ($field_info['type'] == $field_type) {
$filtered_map[$entity_type][$field_name] = $field_info;
}
}
}
$this->fieldMapByFieldType[$field_type] = $filtered_map;
}
return $this->fieldMapByFieldType[$field_type];
}
Alex Pott
committed
/**
* Builds field storage definitions for an entity type.
*
* @param string $entity_type_id
* The entity type ID. Only entity types that implement
* \Drupal\Core\Entity\ContentEntityInterface are supported
*
* @return \Drupal\Core\Field\FieldStorageDefinitionInterface[]
* An array of field storage definitions, keyed by field name.
*/
protected function buildFieldStorageDefinitions($entity_type_id) {
$entity_type = $this->getDefinition($entity_type_id);
$field_definitions = array();
// Retrieve base field definitions from modules.
foreach ($this->moduleHandler->getImplementations('entity_field_storage_info') as $module) {
$module_definitions = $this->moduleHandler->invoke($module, 'entity_field_storage_info', array($entity_type));
if (!empty($module_definitions)) {
// Ensure the provider key actually matches the name of the provider
// defining the field.
foreach ($module_definitions as $field_name => $definition) {
// @todo Remove this check once FieldDefinitionInterface exposes a
// proper provider setter. See https://drupal.org/node/2225961.
Alex Pott
committed
if ($definition instanceof BaseFieldDefinition) {
Alex Pott
committed
$definition->setProvider($module);
}
$field_definitions[$field_name] = $definition;
}
}
}
// Invoke alter hook.
$this->moduleHandler->alter('entity_field_storage_info', $field_definitions, $entity_type);
return $field_definitions;
}
Dries Buytaert
committed
/**
Angie Byron
committed
* {@inheritdoc}
Dries Buytaert
committed
*/
public function clearCachedFieldDefinitions() {
$this->baseFieldDefinitions = array();
$this->fieldDefinitions = array();
Alex Pott
committed
$this->fieldStorageDefinitions = array();
Alex Pott
committed
$this->fieldMap = array();
$this->fieldMapByFieldType = array();
$this->displayModeInfo = array();
$this->extraFields = array();
Cache::deleteTags(array('entity_field_info' => TRUE));
// The typed data manager statically caches prototype objects with injected
// definitions, clear those as well.
$this->typedDataManager->clearCachedDefinitions();
}
/**
* {@inheritdoc}
*/
public function clearCachedBundles() {
$this->bundleInfo = array();
Cache::deleteTags(array('entity_bundles' => TRUE));
// Entity bundles are exposed as data types, clear that cache too.
$this->typedDataManager->clearCachedDefinitions();
Dries Buytaert
committed
}
/**
Angie Byron
committed
* {@inheritdoc}
*/
public function getBundleInfo($entity_type) {
$bundle_info = $this->getAllBundleInfo();
return isset($bundle_info[$entity_type]) ? $bundle_info[$entity_type] : array();
}
/**
Angie Byron
committed
* {@inheritdoc}
*/
public function getAllBundleInfo() {
if (empty($this->bundleInfo)) {
$langcode = $this->languageManager->getCurrentLanguage()->id;
if ($cache = $this->cacheBackend->get("entity_bundle_info:$langcode")) {
$this->bundleInfo = $cache->data;
}
else {
$this->bundleInfo = $this->moduleHandler->invokeAll('entity_bundle_info');
Alex Pott
committed
// First look for entity types that act as bundles for others, load them
// and add them as bundles.
Alex Pott
committed
foreach ($this->getDefinitions() as $type => $entity_type) {
Alex Pott
committed
if ($entity_type->getBundleOf()) {
foreach ($this->getStorage($type)->loadMultiple() as $entity) {
$this->bundleInfo[$entity_type->getBundleOf()][$entity->id()]['label'] = $entity->label();
}
}
}
foreach ($this->getDefinitions() as $type => $entity_type) {
// If no bundles are provided, use the entity type name and label.
if (!isset($this->bundleInfo[$type])) {
Alex Pott
committed
$this->bundleInfo[$type][$type]['label'] = $entity_type->getLabel();
}
}
$this->moduleHandler->alter('entity_bundle_info', $this->bundleInfo);
$this->cacheBackend->set("entity_bundle_info:$langcode", $this->bundleInfo, Cache::PERMANENT, array('entity_types' => TRUE, 'entity_bundles' => TRUE));
}
}
return $this->bundleInfo;
}
/**
* {@inheritdoc}
*/
public function getExtraFields($entity_type_id, $bundle) {
// Read from the "static" cache.
if (isset($this->extraFields[$entity_type_id][$bundle])) {
return $this->extraFields[$entity_type_id][$bundle];
}
// Read from the persistent cache. Since hook_entity_extra_field_info() and
// hook_entity_extra_field_info_alter() might contain t() calls, we cache
// per language.
$cache_id = 'entity_bundle_extra_fields:' . $entity_type_id . ':' . $bundle . ':' . $this->languageManager->getCurrentLanguage()->id;
$cached = $this->cacheBackend->get($cache_id);
if ($cached) {
$this->extraFields[$entity_type_id][$bundle] = $cached->data;
return $this->extraFields[$entity_type_id][$bundle];
}
$extra = $this->moduleHandler->invokeAll('entity_extra_field_info');
$this->moduleHandler->alter('entity_extra_field_info', $extra);
$info = isset($extra[$entity_type_id][$bundle]) ? $extra[$entity_type_id][$bundle] : array();
$info += array(
'form' => array(),
'display' => array(),
);
// Store in the 'static' and persistent caches.
$this->extraFields[$entity_type_id][$bundle] = $info;
$this->cacheBackend->set($cache_id, $info, Cache::PERMANENT, array(
'entity_field_info' => TRUE,
));
return $this->extraFields[$entity_type_id][$bundle];
}
catch
committed
/**
Angie Byron
committed
* {@inheritdoc}
catch
committed
*/
Angie Byron
committed
public function getEntityTypeLabels($group = FALSE) {
catch
committed
$options = array();
Angie Byron
committed
$definitions = $this->getDefinitions();
foreach ($definitions as $entity_type_id => $definition) {
if ($group) {
$options[$definition->getGroupLabel()][$entity_type_id] = $definition->getLabel();
}
else {
$options[$entity_type_id] = $definition->getLabel();
}
}
if ($group) {
foreach ($options as &$group_options) {
// Sort the list alphabetically by group label.
array_multisort($group_options, SORT_ASC, SORT_NATURAL);
}
// Make sure that the 'Content' group is situated at the top.
$content = $this->t('Content', array(), array('context' => 'Entity type group'));
$options = array($content => $options[$content]) + $options;
catch
committed
}
return $options;
}
/**
* {@inheritdoc}
*/
public function getTranslationFromContext(EntityInterface $entity, $langcode = NULL, $context = array()) {
$translation = $entity;
if ($entity instanceof TranslatableInterface) {
if (empty($langcode)) {
$langcode = $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->id;
}
// Retrieve language fallback candidates to perform the entity language
// negotiation.
$context['data'] = $entity;
$context += array('operation' => 'entity_view', 'langcode' => $langcode);
$candidates = $this->languageManager->getFallbackCandidates($context);
// Ensure the default language has the proper language code.
$default_language = $entity->getUntranslated()->language();
$candidates[$default_language->id] = LanguageInterface::LANGCODE_DEFAULT;
// Return the most fitting entity translation.
foreach ($candidates as $candidate) {
if ($entity->hasTranslation($candidate)) {
$translation = $entity->getTranslation($candidate);
break;
}
}
}
return $translation;
}
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
/**
* {@inheritdoc}
*/
public function getAllViewModes() {
return $this->getAllDisplayModesByEntityType('view_mode');
}
/**
* {@inheritdoc}
*/
public function getViewModes($entity_type_id) {
return $this->getDisplayModesByEntityType('view_mode', $entity_type_id);
}
/**
* {@inheritdoc}
*/
public function getAllFormModes() {
return $this->getAllDisplayModesByEntityType('form_mode');
}
/**
* {@inheritdoc}
*/
public function getFormModes($entity_type_id) {
return $this->getDisplayModesByEntityType('form_mode', $entity_type_id);
}
/**
* Returns the entity display mode info for all entity types.
*
* @param string $display_type
* The display type to be retrieved. It can be "view_mode" or "form_mode".
*
* @return array
* The display mode info for all entity types.
*/
protected function getAllDisplayModesByEntityType($display_type) {
if (!isset($this->displayModeInfo[$display_type])) {
$key = 'entity_' . $display_type . '_info';
$entity_type_id = 'entity_' . $display_type;
$langcode = $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_INTERFACE)->id;
if ($cache = $this->cacheBackend->get("$key:$langcode")) {
$this->displayModeInfo[$display_type] = $cache->data;
}
else {
$this->displayModeInfo[$display_type] = array();
foreach ($this->getStorage($entity_type_id)->loadMultiple() as $display_mode) {
list($display_mode_entity_type, $display_mode_name) = explode('.', $display_mode->id(), 2);
$this->displayModeInfo[$display_type][$display_mode_entity_type][$display_mode_name] = $display_mode->toArray();
}
$this->moduleHandler->alter($key, $this->displayModeInfo[$display_type]);
$this->cacheBackend->set("$key:$langcode", $this->displayModeInfo[$display_type], CacheBackendInterface::CACHE_PERMANENT, array('entity_types' => TRUE, 'entity_field_info' => TRUE));
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
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
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
}
}
return $this->displayModeInfo[$display_type];
}
/**
* Returns the entity display mode info for a specific entity type.
*
* @param string $display_type
* The display type to be retrieved. It can be "view_mode" or "form_mode".
* @param string $entity_type_id
* The entity type whose display mode info should be returned.
*
* @return array
* The display mode info for a specific entity type.
*/
protected function getDisplayModesByEntityType($display_type, $entity_type_id) {
if (isset($this->displayModeInfo[$display_type][$entity_type_id])) {
return $this->displayModeInfo[$display_type][$entity_type_id];
}
else {
$display_modes = $this->getAllDisplayModesByEntityType($display_type);
if (isset($display_modes[$entity_type_id])) {
return $display_modes[$entity_type_id];
}
}
return array();
}
/**
* {@inheritdoc}
*/
public function getViewModeOptions($entity_type, $include_disabled = FALSE) {
return $this->getDisplayModeOptions('view_mode', $entity_type, $include_disabled);
}
/**
* {@inheritdoc}
*/
public function getFormModeOptions($entity_type, $include_disabled = FALSE) {
return $this->getDisplayModeOptions('form_mode', $entity_type, $include_disabled);
}
/**
* Returns an array of display mode options.
*
* @param string $display_type
* The display type to be retrieved. It can be "view_mode" or "form_mode".
* @param string $entity_type_id
* The entity type whose display mode options should be returned.
* @param bool $include_disabled
* Force to include disabled display modes. Defaults to FALSE.
*
* @return array
* An array of display mode labels, keyed by the display mode ID.
*/
protected function getDisplayModeOptions($display_type, $entity_type_id, $include_disabled = FALSE) {
$options = array('default' => t('Default'));
foreach ($this->getDisplayModesByEntityType($display_type, $entity_type_id) as $mode => $settings) {
if (!empty($settings['status']) || $include_disabled) {
$options[$mode] = $settings['label'];
}
}
return $options;
}
Alex Pott
committed
/**
* {@inheritdoc}
*/
public function loadEntityByUuid($entity_type_id, $uuid) {
$entity_type = $this->getDefinition($entity_type_id);
if (!$uuid_key = $entity_type->getKey('uuid')) {
throw new EntityStorageException("Entity type $entity_type_id does not support UUIDs.");
}
$entities = $this->getStorage($entity_type_id)->loadByProperties(array($uuid_key => $uuid));
return reset($entities);
}
Alex Pott
committed
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
/**
* {@inheritdoc}
*/
public function getEntityTypeFromClass($class_name) {
// Check the already calculated classes first.
if (isset($this->classNameEntityTypeMap[$class_name])) {
return $this->classNameEntityTypeMap[$class_name];
}
$same_class = 0;
$entity_type_id = NULL;
foreach ($this->getDefinitions() as $entity_type) {
if ($entity_type->getOriginalClass() == $class_name || $entity_type->getClass() == $class_name) {
$entity_type_id = $entity_type->id();
if ($same_class++) {
throw new AmbiguousEntityClassException($class_name);
}
}
}
// Return the matching entity type ID if there is one.
if ($entity_type_id) {
$this->classNameEntityTypeMap[$class_name] = $entity_type_id;
return $entity_type_id;
}
throw new NoCorrespondingEntityClassException($class_name);
}
/**
* {@inheritdoc}
*/
public function onEntityTypeCreate(EntityTypeInterface $entity_type) {
Alex Pott
committed
$entity_type_id = $entity_type->id();
// @todo Forward this to all interested handlers, not only storage, once
// iterating handlers is possible: https://www.drupal.org/node/2332857.
Alex Pott
committed
$storage = $this->getStorage($entity_type_id);
if ($storage instanceof EntityTypeListenerInterface) {
$storage->onEntityTypeCreate($entity_type);
}
Alex Pott
committed
$this->setLastInstalledDefinition($entity_type);
if ($entity_type->isFieldable()) {
$this->setLastInstalledFieldStorageDefinitions($entity_type_id, $this->getFieldStorageDefinitions($entity_type_id));
}
}
/**
* {@inheritdoc}
*/
public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
Alex Pott
committed
$entity_type_id = $entity_type->id();