Commit 5f67fd1d authored by Dries's avatar Dries

Issue #1893820 by fago, das-peter, Berdir: Manage entity field definitions in the entity manager.

parent 98b6331e
......@@ -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', '@cache.cache', '@language_manager', '@module_handler']
......
......@@ -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()
*/
......
......@@ -401,9 +401,10 @@ public function save(EntityInterface $entity) {
}
/**
* 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();
}
......
......@@ -26,22 +26,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.
*
......@@ -555,64 +539,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();
}
......
......@@ -309,7 +309,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'])));
}
......
......@@ -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;
......@@ -47,6 +50,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.
*
......@@ -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;
......@@ -289,4 +340,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));
}
}
......@@ -281,10 +281,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;
}
......
......@@ -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.
......
......@@ -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']);
}
/**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment