diff --git a/core/core.services.yml b/core/core.services.yml index 9a36139..d8b221c 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'] + arguments: ['@container.namespaces', '@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 148f11d..5295174 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 fbf4fdc..28d332c 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php @@ -473,9 +473,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 6bb91e1..aa3eec6 100644 --- a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php +++ b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php @@ -48,22 +48,6 @@ class DatabaseStorageController implements EntityStorageControllerInterface { protected $entityInfo; /** - * 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; - - /** * Additional arguments to pass to hook_TYPE_load(). * * Set before calling Drupal\Core\Entity\DatabaseStorageController::attachLoad(). @@ -675,64 +659,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 9b7020a..8d25eb3 100644 --- a/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php +++ b/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php @@ -237,7 +237,7 @@ protected function attachPropertyData(array &$entities, $load_revision = FALSE) $data = $query->execute(); // Fetch the field definitions to check which field is translatable. - $field_definition = $this->getFieldDefinitions(array()); + $field_definition = \Drupal::entityManager()->getFieldDefinitions($this->entityType, array()); $data_fields = array_flip(drupal_schema_fields_sql($this->entityInfo['data_table'])); foreach ($data as $values) { diff --git a/core/lib/Drupal/Core/Entity/EntityManager.php b/core/lib/Drupal/Core/Entity/EntityManager.php index 9e1d12f..44526f5 100644 --- a/core/lib/Drupal/Core/Entity/EntityManager.php +++ b/core/lib/Drupal/Core/Entity/EntityManager.php @@ -9,11 +9,14 @@ use Drupal\Component\Plugin\PluginManagerBase; use Drupal\Component\Plugin\Factory\DefaultFactory; +use Drupal\Component\Utility\NestedArray; +use Drupal\Core\Language\LanguageManager; use Drupal\Core\Plugin\Discovery\AlterDecorator; use Drupal\Core\Plugin\Discovery\CacheDecorator; use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery; use Drupal\Core\Plugin\Discovery\InfoHookDecorator; use Drupal\Core\Cache\CacheBackendInterface; +use Drupal\Core\Extension\ModuleHandler; /** * Manages entity type plugin definitions. @@ -39,21 +42,70 @@ class EntityManager extends PluginManagerBase { protected $controllers = array(); /** + * The module handler. + * + * @var \Drupal\Core\Extension\ModuleHandler + */ + 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 * An object that implements \Traversable which contains the root paths * keyed by the corresponding namespace to look for plugin implementations, + * @param \Drupal\Core\Extension\ModuleHandler $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) { + public function __construct(\Traversable $namespaces, ModuleHandler $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); } @@ -227,4 +279,87 @@ public function getAdminPath($entity_type, $bundle) { return $admin_path; } + /** + * Gets an array of entity field definitions. + * + * If a 'Bundle' constraint is present, 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 array $constraints + * An array of entity constraints as used for entities in typed data + * definitions, i.e. an array having an 'EntityType' and optionally a + * 'Bundle' key. For example: + * @code + * array( + * 'EntityType' => 'node', + * 'Bundle' => 'article', + * ) + * @endcode + * + * @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 + * typed_data()->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. + * + * @see Drupal\Core\TypedData\TypedDataManager::create() + * @see typed_data() + */ + public function getFieldDefinitions($entity_type, array $constraints) { + 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)); + } + } + + $bundle = !empty($constraints['Bundle']) ? $constraints['Bundle'] : FALSE; + + // Add in per-bundle fields. + if (!isset($this->fieldDefinitions[$entity_type][$bundle])) { + $this->fieldDefinitions[$entity_type][$bundle] = $this->entityFieldInfo[$entity_type]['definitions']; + + if ($bundle && 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]; + } + } diff --git a/core/lib/Drupal/Core/Entity/EntityNG.php b/core/lib/Drupal/Core/Entity/EntityNG.php index bbc3b84..aff41b4 100644 --- a/core/lib/Drupal/Core/Entity/EntityNG.php +++ b/core/lib/Drupal/Core/Entity/EntityNG.php @@ -223,7 +223,7 @@ public function getPropertyDefinition($name) { */ public function getPropertyDefinitions() { if (!isset($this->fieldDefinitions)) { - $this->fieldDefinitions = \Drupal::entityManager()->getStorageController($this->entityType)->getFieldDefinitions(array( + $this->fieldDefinitions = \Drupal::entityManager()->getFieldDefinitions($this->entityType, array( 'EntityType' => $this->entityType, 'Bundle' => $this->bundle, )); diff --git a/core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php b/core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php index 377a002..aec1543 100644 --- a/core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php @@ -126,38 +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 - * typed_data()->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. - * - * @see Drupal\Core\TypedData\TypedDataManager::create() - * @see typed_data() + * 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 eb6ae7d..d62ffb3 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()->getFieldDefinitions($this->entityType, $this->definition['constraints']); } /**