diff --git a/core/core.services.yml b/core/core.services.yml index 2cd6077..7930625 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -150,7 +150,7 @@ services: - { name: persist } plugin.manager.entity: class: Drupal\Core\Entity\EntityManager - arguments: ['@container.namespaces', '@service_container'] + arguments: ['@container.namespaces', '@service_container', '@module_handler', '@cache.cache', '@language_manager'] plugin.manager.archiver: class: Drupal\Core\Archiver\ArchiverManager arguments: ['@container.namespaces'] diff --git a/core/includes/entity.api.php b/core/includes/entity.api.php index 986e344..726d1b1 100644 --- a/core/includes/entity.api.php +++ b/core/includes/entity.api.php @@ -473,26 +473,26 @@ function hook_entity_form_display_alter(\Drupal\entity\Plugin\Core\Entity\Entity } /** - * Define custom entity properties. + * Define custom entity fields. * * @param string $entity_type - * The entity type for which to define entity properties. + * The entity type for which to define entity fields. * * @return array - * An array of property information having the following optional entries: - * - definitions: An array of property definitions to add all entities of this - * type, keyed by property name. See - * Drupal\Core\TypedData\TypedDataManager::create() for a list of supported - * keys in property definitions. - * - optional: An array of property definitions for optional properties keyed - * by property name. Optional properties are properties that only exist for - * certain bundles of the entity type. - * - bundle map: An array keyed by bundle name containing the names of - * optional properties that entities of this bundle have. - * - * @see Drupal\Core\TypedData\TypedDataManager::create() + * An array of entity field information having the following optional entries: + * - definitions: An array of field definitions to add all entities of this + * type, keyed by field name. See + * \Drupal\Core\Entity\EntityManager::getFieldDefinitions() for a list of + * supported keys in field definitions. + * - optional: An array of field definitions for optional entity fields, keyed + * by field name. Optional fields are fields that only exist for certain + * bundles of the entity type. + * - bundle map: An array keyed by bundle name, containing the names of + * optional fields that entities of this bundle have. + * * @see hook_entity_field_info_alter() - * @see Drupal\Core\Entity\StorageControllerInterface::getPropertyDefinitions() + * @see \Drupal\Core\Entity\EntityManager::getFieldDefinitions() + * @see \Drupal\Core\TypedData\TypedDataManager::create() */ function hook_entity_field_info($entity_type) { if (mymodule_uses_entity_type($entity_type)) { @@ -521,12 +521,12 @@ function hook_entity_field_info($entity_type) { } /** - * Alter defined entity properties. + * Alter defined entity fields. * * @param array $info - * The property info array as returned by hook_entity_field_info(). + * The entity field info array as returned by hook_entity_field_info(). * @param string $entity_type - * The entity type for which entity properties are defined. + * The entity type for which entity fields are defined. * * @see hook_entity_field_info() */ diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php index a25f72f..722ffc0 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php @@ -437,9 +437,10 @@ protected function postDelete($entities) { } /** - * Implements Drupal\Core\Entity\EntityStorageControllerInterface::getFieldDefinitions(). + * {@inheritdoc} */ - public function getFieldDefinitions(array $constraints) { + public function baseFieldDefinitions() { + // @todo: Define abstract once all entity types have been converted. return array(); } diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php index fe746f4..c793a2e 100644 --- a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php +++ b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php @@ -27,22 +27,6 @@ class DatabaseStorageController extends EntityStorageControllerBase { /** - * An array of field information, i.e. containing definitions. - * - * @var array - * - * @see hook_entity_field_info() - */ - protected $entityFieldInfo; - - /** - * Static cache of field definitions per bundle. - * - * @var array - */ - protected $fieldDefinitions; - - /** * Name of entity's revision database table field, if it supports revisions. * * Has the value FALSE if this entity does not use revisions. @@ -595,64 +579,10 @@ protected function invokeHook($hook, EntityInterface $entity) { } /** - * Implements \Drupal\Core\Entity\EntityStorageControllerInterface::getFieldDefinitions(). - */ - public function getFieldDefinitions(array $constraints) { - if (!isset($this->entityFieldInfo)) { - // First, try to load from cache. - $cid = 'entity_field_definitions:' . $this->entityType . ':' . language(Language::TYPE_INTERFACE)->langcode; - if ($cache = cache()->get($cid)) { - $this->entityFieldInfo = $cache->data; - } - else { - $this->entityFieldInfo = array( - 'definitions' => $this->baseFieldDefinitions(), - // Contains definitions of optional (per-bundle) fields. - 'optional' => array(), - // An array keyed by bundle name containing the optional fields added - // by the bundle. - 'bundle map' => array(), - ); - - // Invoke hooks. - $result = module_invoke_all($this->entityType . '_property_info'); - $this->entityFieldInfo = NestedArray::mergeDeep($this->entityFieldInfo, $result); - $result = module_invoke_all('entity_field_info', $this->entityType); - $this->entityFieldInfo = NestedArray::mergeDeep($this->entityFieldInfo, $result); - - $hooks = array('entity_field_info', $this->entityType . '_property_info'); - drupal_alter($hooks, $this->entityFieldInfo, $this->entityType); - - // Enforce fields to be multiple by default. - foreach ($this->entityFieldInfo['definitions'] as &$definition) { - $definition['list'] = TRUE; - } - foreach ($this->entityFieldInfo['optional'] as &$definition) { - $definition['list'] = TRUE; - } - cache()->set($cid, $this->entityFieldInfo, CacheBackendInterface::CACHE_PERMANENT, array('entity_info' => TRUE)); - } - } - - $bundle = !empty($constraints['Bundle']) ? $constraints['Bundle'] : FALSE; - - // Add in per-bundle fields. - if (!isset($this->fieldDefinitions[$bundle])) { - $this->fieldDefinitions[$bundle] = $this->entityFieldInfo['definitions']; - - if ($bundle && isset($this->entityFieldInfo['bundle map'][$bundle])) { - $this->fieldDefinitions[$bundle] += array_intersect_key($this->entityFieldInfo['optional'], array_flip($this->entityFieldInfo['bundle map'][$bundle])); - } - } - return $this->fieldDefinitions[$bundle]; - } - - /** - * Defines the base properties of the entity type. - * - * @todo: Define abstract once all entity types have been converted. + * {@inheritdoc} */ public function baseFieldDefinitions() { + // @todo: Define abstract once all entity types have been converted. return array(); } diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php b/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php index 91ec62d..234cccf 100644 --- a/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php +++ b/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php @@ -301,7 +301,7 @@ protected function attachPropertyData(array &$entities, $revision_id = FALSE) { } $data = $query->execute(); - $field_definition = $this->getFieldDefinitions(array()); + $field_definition = \Drupal::entityManager()->getFieldDefinitions($this->entityType); if ($this->revisionTable) { $data_fields = array_flip(array_diff(drupal_schema_fields_sql($this->entityInfo['revision_table']), drupal_schema_fields_sql($this->entityInfo['base_table']))); } diff --git a/core/lib/Drupal/Core/Entity/EntityManager.php b/core/lib/Drupal/Core/Entity/EntityManager.php index ae931f6..ff17294 100644 --- a/core/lib/Drupal/Core/Entity/EntityManager.php +++ b/core/lib/Drupal/Core/Entity/EntityManager.php @@ -9,6 +9,9 @@ use Drupal\Component\Plugin\PluginManagerBase; use Drupal\Component\Plugin\Factory\DefaultFactory; +use Drupal\Component\Utility\NestedArray; +use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\Language\LanguageManager; use Drupal\Core\Language\Language; use Drupal\Core\Plugin\Discovery\AlterDecorator; use Drupal\Core\Plugin\Discovery\CacheDecorator; @@ -48,6 +51,43 @@ class EntityManager extends PluginManagerBase { protected $controllers = array(); /** + * The module handler. + * + * @var \Drupal\Core\Extension\ModuleHandlerInterface + */ + protected $moduleHandler; + + /** + * The cache backend to use. + * + * @var \Drupal\Core\Cache\CacheBackendInterface + */ + protected $cache; + + /** + * The language manager. + * + * @var \Drupal\Core\Language\LanguageManager + */ + protected $languageManager; + + /** + * An array of field information per entity type, i.e. containing definitions. + * + * @var array + * + * @see hook_entity_field_info() + */ + protected $entityFieldInfo; + + /** + * Static cache of field definitions per bundle and entity type. + * + * @var array + */ + protected $fieldDefinitions; + + /** * Constructs a new Entity plugin manager. * * @param \Traversable $namespaces @@ -55,16 +95,27 @@ class EntityManager extends PluginManagerBase { * keyed by the corresponding namespace to look for plugin implementations, * @param \Symfony\Component\DependencyInjection\ContainerInterface $container * The service container this object should use. + * @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\LanguageManager $language_manager + * The language manager. */ - public function __construct(\Traversable $namespaces, ContainerInterface $container) { + public function __construct(\Traversable $namespaces, ContainerInterface $container, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache, LanguageManager $language_manager) { // Allow the plugin definition to be altered by hook_entity_info_alter(). $annotation_namespaces = array( 'Drupal\Core\Entity\Annotation' => DRUPAL_ROOT . '/core/lib', ); + + $this->moduleHandler = $module_handler; + $this->cache = $cache; + $this->languageManager = $language_manager; + $this->discovery = new AnnotatedClassDiscovery('Core/Entity', $namespaces, $annotation_namespaces, 'Drupal\Core\Entity\Annotation\EntityType'); $this->discovery = new InfoHookDecorator($this->discovery, 'entity_info'); $this->discovery = new AlterDecorator($this->discovery, 'entity_info'); - $this->discovery = new CacheDecorator($this->discovery, 'entity_info:' . language(Language::TYPE_INTERFACE)->langcode, 'cache', CacheBackendInterface::CACHE_PERMANENT, array('entity_info' => TRUE)); + $this->discovery = new CacheDecorator($this->discovery, 'entity_info:' . $this->languageManager->getLanguage(Language::TYPE_INTERFACE)->langcode, 'cache', CacheBackendInterface::CACHE_PERMANENT, array('entity_info' => TRUE)); $this->factory = new DefaultFactory($this->discovery); $this->container = $container; @@ -265,4 +316,118 @@ public function getAdminPath($entity_type, $bundle) { return $admin_path; } + /** + * Gets an array of entity field definitions. + * + * If a bundle is passed, fields specific to this bundle are included. Entity + * fields are always multi-valued, so 'list' is TRUE for each returned field + * definition. + * + * @param string $entity_type + * The entity type to get field definitions for. + * @param string $bundle + * (optional) The entity bundle for which to get field definitions. If NULL + * is passed, no bundle-specific fields are included. Defaults to NULL. + * + * @return array + * An array of field definitions of entity fields, keyed by field + * name. In addition to the typed data definition keys as described at + * \Drupal\Core\TypedData\TypedDataManager::create() the following keys are + * supported: + * - queryable: Whether the field is queryable via QueryInterface. + * Defaults to TRUE if 'computed' is FALSE or not set, to FALSE otherwise. + * - translatable: Whether the field is translatable. Defaults to FALSE. + * - configurable: A boolean indicating whether the field is configurable + * via field.module. Defaults to FALSE. + * + * @see \Drupal\Core\TypedData\TypedDataManager::create() + * @see \Drupal\Core\Entity\EntityManager::getFieldDefinitionsByConstraints() + */ + public function getFieldDefinitions($entity_type, $bundle = NULL) { + if (!isset($this->entityFieldInfo[$entity_type])) { + // First, try to load from cache. + $cid = 'entity_field_definitions:' . $entity_type . ':' . $this->languageManager->getLanguage(Language::TYPE_INTERFACE)->langcode; + if ($cache = $this->cache->get($cid)) { + $this->entityFieldInfo[$entity_type] = $cache->data; + } + else { + $this->entityFieldInfo[$entity_type] = array( + 'definitions' => $this->getStorageController($entity_type)->baseFieldDefinitions(), + // Contains definitions of optional (per-bundle) fields. + 'optional' => array(), + // An array keyed by bundle name containing the optional fields added + // by the bundle. + 'bundle map' => array(), + ); + + // Invoke hooks. + $result = $this->moduleHandler->invokeAll($entity_type . '_field_info'); + $this->entityFieldInfo[$entity_type] = NestedArray::mergeDeep($this->entityFieldInfo[$entity_type], $result); + $result = $this->moduleHandler->invokeAll('entity_field_info', array($entity_type)); + $this->entityFieldInfo[$entity_type] = NestedArray::mergeDeep($this->entityFieldInfo[$entity_type], $result); + + $hooks = array('entity_field_info', $entity_type . '_field_info'); + $this->moduleHandler->alter($hooks, $this->entityFieldInfo[$entity_type], $entity_type); + + // Enforce fields to be multiple by default. + foreach ($this->entityFieldInfo[$entity_type]['definitions'] as &$definition) { + $definition['list'] = TRUE; + } + foreach ($this->entityFieldInfo[$entity_type]['optional'] as &$definition) { + $definition['list'] = TRUE; + } + $this->cache->set($cid, $this->entityFieldInfo[$entity_type], CacheBackendInterface::CACHE_PERMANENT, array('entity_info' => TRUE, 'entity_field_info' => TRUE)); + } + } + + if (!$bundle) { + return $this->entityFieldInfo[$entity_type]['definitions']; + } + else { + // Add in per-bundle fields. + if (!isset($this->fieldDefinitions[$entity_type][$bundle])) { + $this->fieldDefinitions[$entity_type][$bundle] = $this->entityFieldInfo[$entity_type]['definitions']; + if (isset($this->entityFieldInfo[$entity_type]['bundle map'][$bundle])) { + $this->fieldDefinitions[$entity_type][$bundle] += array_intersect_key($this->entityFieldInfo[$entity_type]['optional'], array_flip($this->entityFieldInfo[$entity_type]['bundle map'][$bundle])); + } + } + return $this->fieldDefinitions[$entity_type][$bundle]; + } + } + + /** + * Gets an array of entity field definitions based on validation constraints. + * + * @param string $entity_type + * The entity type to get field definitions for. + * @param array $constraints + * An array of entity constraints as used for entities in typed data + * definitions, i.e. an array optionally including a 'Bundle' key. + * For example the constraints used by an entity reference could be: + * @code + * array( + * 'Bundle' => 'article', + * ) + * @endcode + * + * @return array + * An array of field definitions of entity fields, keyed by field + * name. + * + * @see \Drupal\Core\Entity\EntityManager::getFieldDefinitions() + */ + public function getFieldDefinitionsByConstraints($entity_type, array $constraints) { + // @todo: Add support for specifying multiple bundles. + return $this->getFieldDefinitions($entity_type, isset($constraints['Bundle']) ? $constraints['Bundle'] : NULL); + } + + /** + * Clears static and persistent field definition caches. + */ + public function clearCachedFieldDefinitions() { + unset($this->entityFieldInfo); + unset($this->fieldDefinitions); + $this->cache->deleteTags(array('entity_field_info' => TRUE)); + } + } diff --git a/core/lib/Drupal/Core/Entity/EntityNG.php b/core/lib/Drupal/Core/Entity/EntityNG.php index de0fb46..5951e02 100644 --- a/core/lib/Drupal/Core/Entity/EntityNG.php +++ b/core/lib/Drupal/Core/Entity/EntityNG.php @@ -285,10 +285,8 @@ public function getPropertyDefinition($name) { */ public function getPropertyDefinitions() { if (!isset($this->fieldDefinitions)) { - $this->fieldDefinitions = \Drupal::entityManager()->getStorageController($this->entityType)->getFieldDefinitions(array( - 'EntityType' => $this->entityType, - 'Bundle' => $this->bundle, - )); + $bundle = $this->bundle != $this->entityType ? $this->bundle : NULL; + $this->fieldDefinitions = \Drupal::entityManager()->getFieldDefinitions($this->entityType, $bundle); } return $this->fieldDefinitions; } diff --git a/core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php b/core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php index 610cb4d..aec1543 100644 --- a/core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php @@ -126,46 +126,16 @@ public function delete(array $entities); public function save(EntityInterface $entity); /** - * Gets an array of entity field definitions. - * - * If a 'bundle' key is present in the given entity definition, fields - * specific to this bundle are included. - * Entity fields are always multi-valued, so 'list' is TRUE for each - * returned field definition. - * - * @param array $constraints - * An array of entity constraints as used for entities in typed data - * definitions, i.e. an array having an 'entity type' and optionally a - * 'bundle' key. For example: - * @code - * array( - * 'EntityType' => 'node', - * 'Bundle' => 'article', - * ) - * @endcode + * Defines the base fields of the entity type. * * @return array - * An array of field definitions of entity fields, keyed by field - * name. In addition to the typed data definition keys as described at - * \Drupal::typedData()->create() the follow keys are supported: - * - queryable: Whether the field is queryable via QueryInterface. - * Defaults to TRUE if 'computed' is FALSE or not set, to FALSE otherwise. - * - translatable: Whether the field is translatable. Defaults to FALSE. - * - configurable: A boolean indicating whether the field is configurable - * via field.module. Defaults to FALSE. - * - property_constraints: An array of constraint arrays applying to the - * field item properties, keyed by property name. E.g. the following - * validates the value property to have a maximum length of 128: - * @code - * array( - * 'value' => array('Length' => array('max' => 128)), - * ) - * @endcode - * - * @see Drupal\Core\TypedData\TypedDataManager::create() - * @see \Drupal::typedData() + * An array of entity field definitions as specified by + * \Drupal\Core\Entity\EntityManager::getFieldDefinitions(), keyed by field + * name. + * + * @see \Drupal\Core\Entity\EntityManager::getFieldDefinitions() */ - public function getFieldDefinitions(array $constraints); + public function baseFieldDefinitions(); /** * Gets the name of the service for the query for this entity storage. diff --git a/core/lib/Drupal/Core/Entity/Field/Type/EntityWrapper.php b/core/lib/Drupal/Core/Entity/Field/Type/EntityWrapper.php index f6f224e..2b2b54a 100644 --- a/core/lib/Drupal/Core/Entity/Field/Type/EntityWrapper.php +++ b/core/lib/Drupal/Core/Entity/Field/Type/EntityWrapper.php @@ -178,7 +178,7 @@ public function getPropertyDefinition($name) { */ public function getPropertyDefinitions() { // @todo: Support getting definitions if multiple bundles are specified. - return \Drupal::entityManager()->getStorageController($this->entityType)->getFieldDefinitions($this->definition['constraints']); + return \Drupal::entityManager()->getFieldDefinitionsByConstraints($this->entityType, $this->definition['constraints']); } /**