diff --git a/core/lib/Drupal/Core/Entity/ContentEntityBase.php b/core/lib/Drupal/Core/Entity/ContentEntityBase.php index 1572129..fe6d5cf 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityBase.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php @@ -489,8 +489,7 @@ public function getPropertyDefinition($name) { */ public function getPropertyDefinitions() { if (!isset($this->fieldDefinitions)) { - $bundle = $this->bundle != $this->entityTypeId ? $this->bundle : NULL; - $this->fieldDefinitions = \Drupal::entityManager()->getFieldDefinitions($this->entityTypeId, $bundle); + $this->fieldDefinitions = \Drupal::entityManager()->getFieldDefinitions($this->entityTypeId, $this->bundle()); } return $this->fieldDefinitions; } @@ -991,4 +990,11 @@ public function referencedEntities() { return $referenced_entities; } + /** + * {@inheritdoc} + */ + public static function baseFieldDefinitionsByBundle($entity_type, $bundle, array $field_definitions) { + return array(); + } + } diff --git a/core/lib/Drupal/Core/Entity/ContentEntityInterface.php b/core/lib/Drupal/Core/Entity/ContentEntityInterface.php index 862b23f..627dce9 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityInterface.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityInterface.php @@ -50,6 +50,9 @@ public function initTranslation($langcode); * ->setLabel(t('Name')); * @endcode * + * If a defined field varies by bundle, see + * \Drupal\Core\Entity\ContentEntityInterface::baseFieldDefinitionsByBundle(). + * * @param string $entity_type * The entity type to return properties for. Useful when a single class is * used for multiple, possibly dynamic entity types. @@ -62,6 +65,30 @@ public function initTranslation($langcode); public static function baseFieldDefinitions($entity_type); /** + * Defines or alters base field definitions for the given bundle. + * + * Any field definitions returned take precedence over the base fields defined + * for the entity type via + * \Drupal\Core\Entity\ContentEntityInterface::baseFieldDefinitions(). + * + * @todo Provide a better DX for field overrides. + * + * @param string $entity_type + * The entity type to return properties for. Useful when a single class is + * used for multiple, possibly dynamic entity types. + * @param string $bundle + * Name of the bundle for which the overrides should be applied. + * @param \Drupal\Core\Field\FieldDefinitionInterface[] $field_definitions + * List of base fields for the given entity type. + * + * @return \Drupal\Core\Field\FieldDefinitionInterface[] + * An array of field definitions, keyed by field name. + * + * @see \Drupal\Core\Entity\EntityManagerInterface::getFieldDefinitions() + */ + public static function baseFieldDefinitionsByBundle($entity_type, $bundle, array $field_definitions); + + /** * Returns whether the entity has a field with the given name. * * @param string $field_name diff --git a/core/lib/Drupal/Core/Entity/EntityManager.php b/core/lib/Drupal/Core/Entity/EntityManager.php index 6a2385f..15a508b 100644 --- a/core/lib/Drupal/Core/Entity/EntityManager.php +++ b/core/lib/Drupal/Core/Entity/EntityManager.php @@ -78,20 +78,25 @@ class EntityManager extends PluginManagerBase implements EntityManagerInterface protected $languageManager; /** - * An array of field information per entity type, i.e. containing definitions. + * Static cache of base field definitions. * * @var array + */ + protected $baseFieldDefinitions; + + /** + * Static cache of field definitions excluding per-bundle fields. * - * @see hook_entity_field_info() + * @var array */ - protected $entityFieldInfo; + protected $fieldDefinitions; /** * Static cache of field definitions per bundle and entity type. * * @var array */ - protected $fieldDefinitions; + protected $fieldDefinitionsByBundle; /** * The root paths. @@ -297,94 +302,124 @@ public function getAdminRouteInfo($entity_type_id, $bundle) { * {@inheritdoc} */ public function getFieldDefinitions($entity_type_id, $bundle = NULL) { - if (!isset($this->entityFieldInfo[$entity_type_id])) { - // First, try to load from cache. - $cid = 'entity_field_definitions:' . $entity_type_id . ':' . $this->languageManager->getCurrentLanguage()->id; - if ($cache = $this->cache->get($cid)) { - $this->entityFieldInfo[$entity_type_id] = $cache->data; - } - else { - // @todo: Refactor to allow for per-bundle overrides. - // See https://drupal.org/node/2114707. - $entity_type = $this->getDefinition($entity_type_id); - $class = $entity_type->getClass(); - - $base_definitions = $class::baseFieldDefinitions($entity_type_id); - foreach ($base_definitions as &$base_definition) { - $base_definition->setTargetEntityTypeId($entity_type_id); - } - $this->entityFieldInfo[$entity_type_id] = array( - 'definitions' => $base_definitions, - // 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_id . '_field_info'); - $this->entityFieldInfo[$entity_type_id] = NestedArray::mergeDeep($this->entityFieldInfo[$entity_type_id], $result); - $result = $this->moduleHandler->invokeAll('entity_field_info', array($entity_type_id)); - $this->entityFieldInfo[$entity_type_id] = NestedArray::mergeDeep($this->entityFieldInfo[$entity_type_id], $result); - - // Automatically set the field name for non-configurable fields. - foreach (array('definitions', 'optional') as $key) { - foreach ($this->entityFieldInfo[$entity_type_id][$key] as $field_name => &$definition) { - if ($definition instanceof FieldDefinition) { - $definition->setName($field_name); - } - } - } - - // Invoke alter hooks. - $hooks = array('entity_field_info', $entity_type_id . '_field_info'); - $this->moduleHandler->alter($hooks, $this->entityFieldInfo[$entity_type_id], $entity_type_id); - - // Ensure all basic fields are not defined as translatable. - $keys = array_intersect_key(array_filter($entity_type->getKeys()), array_flip(array('id', 'revision', 'uuid', 'bundle'))); - $untranslatable_fields = array_flip(array('langcode') + $keys); - foreach (array('definitions', 'optional') as $key) { - foreach ($this->entityFieldInfo[$entity_type_id][$key] as $field_name => &$definition) { - if (isset($untranslatable_fields[$field_name]) && $definition->isTranslatable()) { - throw new \LogicException(String::format('The @field field cannot be translatable.', array('@field' => $definition->getLabel()))); - } - } + // If there's no bundle specified, only return fields that are not bundle + // specific. + if (!$bundle) { + // Check the static cache. + if (!isset($this->fieldDefinitions[$entity_type_id])) { + // Not prepared, try to load from cache. + $cid = 'entity_field_definitions:' . $entity_type_id . ':' . $this->languageManager->getCurrentLanguage()->id; + if ($cache = $this->cache->get($cid)) { + $this->fieldDefinitions[$entity_type_id] = $cache->data; } - - $this->cache->set($cid, $this->entityFieldInfo[$entity_type_id], Cache::PERMANENT, array('entity_info' => TRUE, 'entity_field_info' => TRUE)); - } + else { + // Rebuild the definitions and put it into the cache. + $this->fieldDefinitions[$entity_type_id] = $this->buildFieldDefinitions($entity_type_id, NULL); + $this->cache->set($cid, $this->fieldDefinitions[$entity_type_id], Cache::PERMANENT, array('entity_info' => TRUE, 'entity_field_info' => TRUE)); + } + } + return $this->fieldDefinitions[$entity_type_id]; } - if (!$bundle) { - return $this->entityFieldInfo[$entity_type_id]['definitions']; - } - else { - // Add in per-bundle fields. - if (!isset($this->fieldDefinitions[$entity_type_id][$bundle])) { - $this->fieldDefinitions[$entity_type_id][$bundle] = $this->entityFieldInfo[$entity_type_id]['definitions']; - if (isset($this->entityFieldInfo[$entity_type_id]['bundle map'][$bundle])) { - $this->fieldDefinitions[$entity_type_id][$bundle] += array_intersect_key($this->entityFieldInfo[$entity_type_id]['optional'], array_flip($this->entityFieldInfo[$entity_type_id]['bundle map'][$bundle])); - } + // We have a bundle, so get the field definitions for that specific bundle. + if (!isset($this->fieldDefinitionsByBundle[$entity_type_id][$bundle])) { + // Not prepared, try to load from cache. + $cid = 'entity_field_definitions_bundle:' . $entity_type_id . ':' . $bundle . ':' . $this->languageManager->getCurrentLanguage()->id; + if ($cache = $this->cache->get($cid)) { + $this->fieldDefinitionsByBundle[$entity_type_id][$bundle] = $cache->data; + } + else { + // Rebuild the definitions and put it into the cache. + $this->fieldDefinitionsByBundle[$entity_type_id][$bundle] = $this->buildFieldDefinitions($entity_type_id, $bundle); + $this->cache->set($cid, $this->fieldDefinitionsByBundle[$entity_type_id][$bundle], Cache::PERMANENT, array('entity_info' => TRUE, 'entity_field_info' => TRUE)); } - return $this->fieldDefinitions[$entity_type_id][$bundle]; } + return $this->fieldDefinitionsByBundle[$entity_type_id][$bundle]; } /** * {@inheritdoc} */ 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); } /** + * Builds entity field definitions. + * + * @param string $entity_type_id + * The entity type ID to get field definitions for. Only entity types that + * implement \Drupal\Core\Entity\ContentEntityInterface are supported. + * @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 \Drupal\Core\Field\FieldDefinitionInterface[] + * An array of entity field definitions, keyed by field name. + */ + protected function buildFieldDefinitions($entity_type_id, $bundle = NULL) { + $entity_type = $this->getDefinition($entity_type_id); + $class = $entity_type->getClass(); + + // Build the base fields only once for all bundles. + if (empty($this->baseFieldDefinitions[$entity_type_id])) { + $base_definitions = $class::baseFieldDefinitions($entity_type_id); + + // Invoke hooks. + $result = $this->moduleHandler->invokeAll($entity_type_id . '_field_info'); + $base_definitions = NestedArray::mergeDeep($base_definitions, $result); + $result = $this->moduleHandler->invokeAll('entity_field_info', array($entity_type_id)); + $base_definitions = NestedArray::mergeDeep($base_definitions, $result); + + // Automatically set the field name for non-configurable fields. + foreach ($base_definitions as $field_name => &$base_definition) { + $base_definition->setName($field_name); + $base_definition->setTargetEntityTypeId($entity_type_id); + } + + // Invoke alter hooks. + $hooks = array('entity_field_info', $entity_type_id . '_field_info'); + $this->moduleHandler->alter($hooks, $base_definitions, $entity_type_id); + + $this->baseFieldDefinitions[$entity_type_id] = $base_definitions; + } + $field_definitions = $this->baseFieldDefinitions[$entity_type_id]; + + // When we build fields for a specific bundle, allow the entity class to + // override the base fields. + if ($bundle) { + $field_definitions = $class::baseFieldDefinitionsByBundle($entity_type_id, $bundle, $field_definitions) + $field_definitions; + + // Invoke 'per bundle' hooks. + $result = $this->moduleHandler->invokeAll($entity_type_id . '_field_info_by_bundle', array($bundle)); + $field_definitions = NestedArray::mergeDeep($field_definitions, $result); + $result = $this->moduleHandler->invokeAll('entity_field_info_by_bundle', array($entity_type_id, $bundle)); + $field_definitions = NestedArray::mergeDeep($field_definitions, $result); + + // Invoke 'per bundle' alter hooks. + $hooks = array('entity_field_info_by_bundle', $entity_type_id . '_field_info_by_bundle'); + $this->moduleHandler->alter($hooks, $field_definitions, $entity_type_id, $bundle); + } + + // Ensure all basic fields are not defined as translatable. + $keys = array_intersect_key(array_filter($entity_type->getKeys()), array_flip(array('id', 'revision', 'uuid', 'bundle'))); + $untranslatable_fields = array_flip(array('langcode') + $keys); + foreach ($field_definitions as $field_name => $definition) { + if (isset($untranslatable_fields[$field_name]) && $definition->isTranslatable()) { + throw new \LogicException(String::format('The @field field cannot be translatable.', array('@field' => $definition->getLabel()))); + } + } + + return $field_definitions; + } + + /** * {@inheritdoc} */ public function clearCachedFieldDefinitions() { - unset($this->entityFieldInfo); - unset($this->fieldDefinitions); + $this->baseFieldDefinitions = array(); + $this->fieldDefinitions = array(); + $this->fieldDefinitionsByBundle = array(); Cache::deleteTags(array('entity_field_info' => TRUE)); } diff --git a/core/lib/Drupal/Core/Field/ConfigFieldItemList.php b/core/lib/Drupal/Core/Field/ConfigFieldItemList.php index 3c76be2..a5f61a8 100644 --- a/core/lib/Drupal/Core/Field/ConfigFieldItemList.php +++ b/core/lib/Drupal/Core/Field/ConfigFieldItemList.php @@ -17,50 +17,10 @@ class ConfigFieldItemList extends FieldItemList implements ConfigFieldItemListInterface { /** - * The Field instance definition. - * - * @var \Drupal\field\FieldInstanceInterface - */ - protected $instance; - - /** - * {@inheritdoc} - */ - public function __construct($definition, $name = NULL, TypedDataInterface $parent = NULL) { - parent::__construct($definition, $name, $parent); - // Definition can be the field config or field instance. - if ($definition instanceof FieldInstanceInterface) { - $this->instance = $definition; - } - } - - /** - * {@inheritdoc} - */ - public function getFieldDefinition() { - // Configurable fields have the field_config entity injected as definition, - // but we want to return the more specific field instance here. - // @todo: Overhaul this once we have per-bundle field definitions injected, - // see https://drupal.org/node/2114707. - if (!isset($this->instance)) { - $entity = $this->getEntity(); - $instances = Field::fieldInfo()->getBundleInstances($entity->getEntityTypeId(), $entity->bundle()); - if (isset($instances[$this->getName()])) { - $this->instance = $instances[$this->getName()]; - } - else { - // For base fields, fall back to return the general definition. - return parent::getFieldDefinition(); - } - } - return $this->instance; - } - - /** * {@inheritdoc} */ public function getConstraints() { - $constraints = array(); + $constraints = parent::getConstraints(); // Check that the number of values doesn't exceed the field cardinality. For // form submitted values, this can only happen with 'multiple value' // widgets. diff --git a/core/lib/Drupal/Core/TypedData/TypedDataManager.php b/core/lib/Drupal/Core/TypedData/TypedDataManager.php index e060cd3..0948b40 100644 --- a/core/lib/Drupal/Core/TypedData/TypedDataManager.php +++ b/core/lib/Drupal/Core/TypedData/TypedDataManager.php @@ -368,4 +368,13 @@ public function getConstraints(DataDefinitionInterface $definition) { return $constraints; } + + /** + * {@inheritdoc} + */ + public function clearCachedDefinitions() { + parent::clearCachedDefinitions(); + $this->prototypes = array(); + } + } diff --git a/core/modules/content_translation/content_translation.module b/core/modules/content_translation/content_translation.module index aea9349..7c69ab0 100644 --- a/core/modules/content_translation/content_translation.module +++ b/core/modules/content_translation/content_translation.module @@ -9,6 +9,7 @@ use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\EntityFormControllerInterface; use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Field\FieldDefinition; use Drupal\Core\Language\Language; use Drupal\Core\Session\AccountInterface; use Drupal\Core\TypedData\TranslatableInterface; @@ -145,25 +146,22 @@ function content_translation_entity_bundle_info_alter(&$bundles) { /** * Implements hook_entity_field_info_alter(). */ -function content_translation_entity_field_info_alter(&$info, $entity_type) { - $translation_settings = config('content_translation.settings')->get($entity_type); +function content_translation_entity_field_info_alter(&$fields, $entity_type_id) { + $translation_settings = config('content_translation.settings')->get($entity_type_id); if ($translation_settings) { // Currently field translatability is defined per-field but we may want to - // make it per-instance instead, so leaving the possibility open for further - // easier refactoring. - $fields = array(); + // make it per-instance instead. In that case, we will need to implement + // hook_entity_field_info_by_bundle_alter() instead. + $field_settings = array(); foreach ($translation_settings as $bundle => $settings) { - $fields += !empty($settings['content_translation']['fields']) ? $settings['content_translation']['fields'] : array(); + $field_settings += !empty($settings['content_translation']['fields']) ? $settings['content_translation']['fields'] : array(); } - $keys = array('definitions', 'optional'); - foreach ($fields as $name => $translatable) { - foreach ($keys as $key) { - if (isset($info[$key][$name])) { - $info[$key][$name]->setTranslatable((bool) $translatable); - break; - } + foreach ($field_settings as $name => $translatable) { + if (isset($fields[$name]) && $fields[$name] instanceof FieldDefinition) { + $fields[$name]->setTranslatable((bool) $translatable); + break; } } } diff --git a/core/modules/entity/lib/Drupal/entity/EntityDisplayBase.php b/core/modules/entity/lib/Drupal/entity/EntityDisplayBase.php index f1d84ab..3400107 100644 --- a/core/modules/entity/lib/Drupal/entity/EntityDisplayBase.php +++ b/core/modules/entity/lib/Drupal/entity/EntityDisplayBase.php @@ -325,20 +325,14 @@ protected function getFieldDefinition($field_name) { * Returns the definitions of the fields that are candidate for display. */ protected function getFieldDefinitions() { + // Entity displays are sometimes created for non-content entities. + // @todo Prevent this in https://drupal.org/node/2095195. + if (!\Drupal::entityManager()->getDefinition($this->targetEntityType)->isSubclassOf('\Drupal\Core\Entity\ContentEntityInterface')) { + return array(); + } + if (!isset($this->fieldDefinitions)) { - // @todo Replace this with \Drupal::entityManager()->getFieldDefinition() - // when it can hand the $instance objects (and then reconsider the - // $this->fieldDefinitions static cache ?) - // https://drupal.org/node/2114707 - $entity_manager = \Drupal::entityManager(); - $entity_type = $entity_manager->getDefinition($this->targetEntityType); - $definitions = array(); - if ($entity_type->isSubclassOf('\Drupal\Core\Entity\ContentEntityInterface')) { - $entity = _field_create_entity_from_ids((object) array('entity_type' => $this->targetEntityType, 'bundle' => $this->bundle, 'entity_id' => NULL)); - foreach ($entity as $field_name => $items) { - $definitions[$field_name] = $items->getFieldDefinition(); - } - } + $definitions = \Drupal::entityManager()->getFieldDefinitions($this->targetEntityType, $this->bundle); // The display only cares about fields that specify display options. // Discard base fields that are not rendered through formatters / widgets. diff --git a/core/modules/field/field.attach.inc b/core/modules/field/field.attach.inc index 59c6deb..6fd0b92 100644 --- a/core/modules/field/field.attach.inc +++ b/core/modules/field/field.attach.inc @@ -124,31 +124,18 @@ function field_invoke_method($method, $target_function, EntityInterface $entity, * @param $options * An associative array of options, as provided to field_invoke_method(). Only * the following keys are considered: - * - deleted * - field_name - * - field_id * See field_invoke_method() for details. * * @return * The array of selected field definitions. */ function _field_invoke_get_field_definitions($entity_type, $bundle, $options) { - // @todo Replace with \Drupal::entityManager()->getFieldDefinition() after - // [#2047229] lands. - $entity = _field_create_entity_from_ids((object) array('entity_type' => $entity_type, 'bundle' => $bundle, 'entity_id' => NULL)); - $field_definitions = array(); + $definitions = \Drupal::entityManager()->getFieldDefinitions($entity_type, $bundle); if (isset($options['field_name'])) { - if ($entity->hasField($options['field_name'])) { - $field_definitions[] = $entity->get($options['field_name'])->getFieldDefinition(); - } - } - else { - foreach ($entity as $items) { - $field_definitions[] = $items->getFieldDefinition(); - } + $definitions = array_intersect_key($definitions, array($options['field_name'] => TRUE)); } - - return $field_definitions; + return $definitions; } /** diff --git a/core/modules/field/field.module b/core/modules/field/field.module index 6a8b941..7c16212 100644 --- a/core/modules/field/field.module +++ b/core/modules/field/field.module @@ -7,6 +7,7 @@ use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Template\Attribute; use Drupal\entity\Entity\EntityViewDisplay; +use Drupal\field\Field; /* * Load all public Field API functions. Drupal currently has no @@ -183,29 +184,11 @@ function field_system_info_alter(&$info, $file, $type) { } /** - * Implements hook_entity_field_info() to define all configured fields. + * Implements hook_entity_field_info_by_bundle(). */ -function field_entity_field_info($entity_type) { - $property_info = array(); - - foreach (field_info_instances($entity_type) as $bundle_name => $instances) { - $optional = $bundle_name != $entity_type; - // @todo: Improve hook_entity_field_info() to allow per-bundle field - // definitions, such that we can pass on field instances as field - // definitions here. See https://drupal.org/node/2114707. - - foreach ($instances as $field_name => $instance) { - if ($optional) { - $property_info['optional'][$field_name] = $instance->getField(); - $property_info['bundle map'][$bundle_name][] = $field_name; - } - else { - $property_info['definitions'][$field_name] = $instance->getField(); - } - } - } - - return $property_info; +function field_entity_field_info_by_bundle($entity_type_id, $bundle) { + // Define all configurable fields, which are always set for a specific bundle. + return Field::fieldInfo()->getBundleInstances($entity_type_id, $bundle); } /** diff --git a/core/modules/field/lib/Drupal/field/Tests/FieldAttachOtherTest.php b/core/modules/field/lib/Drupal/field/Tests/FieldAttachOtherTest.php index b4ce690..53f5d32 100644 --- a/core/modules/field/lib/Drupal/field/Tests/FieldAttachOtherTest.php +++ b/core/modules/field/lib/Drupal/field/Tests/FieldAttachOtherTest.php @@ -162,7 +162,7 @@ function testEntityDisplayViewMultiple() { // Create two entities. $entity1 = entity_create('entity_test', array('id' => 1, 'type' => 'entity_test')); $entity1->{$this->field_name}->setValue($this->_generateTestFieldValues(1)); - $entity2 = entity_create('entity_test', array('id' => 2, 'type' => 'test_bundle')); + $entity2 = entity_create('entity_test', array('id' => 2, 'type' => 'entity_test')); $entity2->{$this->field_name}->setValue($this->_generateTestFieldValues(1)); // Run buildMultiple(), and check that the entities come out as expected. diff --git a/core/modules/node/lib/Drupal/node/Entity/Node.php b/core/modules/node/lib/Drupal/node/Entity/Node.php index 017e89c..1d957b8 100644 --- a/core/modules/node/lib/Drupal/node/Entity/Node.php +++ b/core/modules/node/lib/Drupal/node/Entity/Node.php @@ -384,7 +384,6 @@ public static function baseFieldDefinitions($entity_type) { // possible - https://drupal.org/node/2114707. ->setLabel(t('Title')) ->setDescription(t('The title of this node, always treated as non-markup plain text.')) - ->setClass('\Drupal\node\NodeTitleItemList') ->setRequired(TRUE) ->setTranslatable(TRUE) ->setSettings(array( @@ -453,4 +452,17 @@ public static function baseFieldDefinitions($entity_type) { return $fields; } + /** + * {@inheritdoc} + */ + public static function baseFieldDefinitionsByBundle($entity_type, $bundle, array $field_definitions) { + $node_type = node_type_load($bundle); + $fields = array(); + if (isset($node_type->title_label)) { + $fields['title'] = clone $field_definitions['title']; + $fields['title']->setLabel($node_type->title_label); + } + return $fields; + } + } diff --git a/core/modules/node/lib/Drupal/node/NodeTitleItemList.php b/core/modules/node/lib/Drupal/node/NodeTitleItemList.php deleted file mode 100644 index 2b81193..0000000 --- a/core/modules/node/lib/Drupal/node/NodeTitleItemList.php +++ /dev/null @@ -1,34 +0,0 @@ -getType()); - if (isset($node_type->title_label)) { - $definition->setLabel($node_type->title_label); - } - parent::__construct($definition, $name, $node); - } - -} diff --git a/core/modules/node/tests/modules/node_access_test/node_access_test.module b/core/modules/node/tests/modules/node_access_test/node_access_test.module index 128b31a..47a2822 100644 --- a/core/modules/node/tests/modules/node_access_test/node_access_test.module +++ b/core/modules/node/tests/modules/node_access_test/node_access_test.module @@ -80,13 +80,13 @@ function node_access_test_permission() { /** * Implements hook_entity_field_info(). */ -function node_access_test_entity_field_info($entity_type) { - if ($entity_type === 'node') { - $info['definitions']['private'] = FieldDefinition::create('boolean') - ->setLabel(t('Private')) - ->setComputed(TRUE); +function node_access_test_entity_field_info($entity_type_id) { + if ($entity_type_id === 'node') { + $fields['private'] = FieldDefinition::create('boolean') + ->setLabel(t('Private')) + ->setComputed(TRUE); - return $info; + return $fields; } } diff --git a/core/modules/path/path.module b/core/modules/path/path.module index bd8969f..5e04d77 100644 --- a/core/modules/path/path.module +++ b/core/modules/path/path.module @@ -221,13 +221,13 @@ function path_form_taxonomy_term_form_alter(&$form, $form_state) { /** * Implements hook_entity_field_info(). */ -function path_entity_field_info($entity_type) { - if ($entity_type === 'taxonomy_term' || $entity_type === 'node') { - $info['definitions']['path'] = FieldDefinition::create('path') +function path_entity_field_info($entity_type_id) { + if ($entity_type_id === 'taxonomy_term' || $entity_type_id === 'node') { + $fields['path'] = FieldDefinition::create('path') ->setLabel(t('The path alias')) ->setComputed(TRUE); - return $info; + return $fields; } } diff --git a/core/modules/system/entity.api.php b/core/modules/system/entity.api.php index 1a3973d..dfab372 100644 --- a/core/modules/system/entity.api.php +++ b/core/modules/system/entity.api.php @@ -664,57 +664,98 @@ function hook_entity_form_display_alter(\Drupal\Core\Entity\Display\EntityFormDi * @param string $entity_type_id * The entity type for which to define entity fields. * - * @return array - * An array of entity field information having the following optional entries: - * - definitions: An array of field definitions to add to all entities of this - * type, keyed by field name. - * - 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. + * @return \Drupal\Core\Field\FieldDefinitionInterface[] + * An array of field definitions. * * @see hook_entity_field_info_alter() + * @see hook_entity_field_info_by_bundle() + * @see hook_entity_field_info_by_bundle_alter() * @see \Drupal\Core\Field\FieldDefinitionInterface * @see \Drupal\Core\Entity\EntityManagerInterface::getFieldDefinitions() * @see \Drupal\Core\TypedData\TypedDataManager::create() */ function hook_entity_field_info($entity_type_id) { if (mymodule_uses_entity_type($entity_type_id)) { - $info = array(); - $info['definitions']['mymodule_text'] = FieldDefinition::create('string') + $fields = array(); + $fields['mymodule_text'] = FieldDefinition::create('string') ->setLabel(t('The text')) ->setDescription(t('A text property added by mymodule.')) ->setComputed(TRUE) ->setClass('\Drupal\mymodule\EntityComputedText'); - if ($entity_type_id == 'node') { - // Add a property only to entities of the 'article' bundle. - $info['optional']['mymodule_text_more'] = FieldDefinition::create('string') + return $fields; + } +} + +/** + * Alter defined entity fields. + * + * @param \Drupal\Core\Field\FieldDefinitionInterface[] $fields + * An array of field definitions. + * @param string $entity_type_id + * The entity type for which entity fields are defined. + * + * @see hook_entity_field_info() + * @see hook_entity_field_info_by_bundle() + * @see hook_entity_field_info_by_bundle_alter() + */ +function hook_entity_field_info_alter(&$fields, $entity_type_id) { + if (mymodule_uses_entity_type($entity_type_id) && !empty($fields['mymodule_text'])) { + // Alter the mymodule_text field to use a custom class. + $fields['mymodule_text']->setClass('\Drupal\anothermodule\EntityComputedText'); + } +} + +/** + * Define custom entity fields which are specific to a bundle. + * + * @param string $entity_type_id + * The entity type for which to define entity fields. + * @param string $bundle + * The name of the bundle to return field definitions for. + * + * @return \Drupal\Core\Field\FieldDefinitionInterface[] + * An array of field definitions. + * + * @see hook_entity_field_info() + * @see hook_entity_field_info_alter() + * @see hook_entity_field_info_by_bundle_alter() + * @see \Drupal\Core\Field\FieldDefinitionInterface + * @see \Drupal\Core\Entity\EntityManagerInterface::getFieldDefinitions() + * @see \Drupal\Core\TypedData\TypedDataManager::create() + */ +function hook_entity_field_info_by_bundle($entity_type_id, $bundle) { + if (mymodule_uses_entity_type_and_bundle($entity_type_id, $bundle)) { + $fields = array(); + // Add a property only to nodes of the 'article' bundle. + if ($entity_type_id == 'node' && $bundle == 'article') { + $fields['mymodule_text_more'] = FieldDefinition::create('string') ->setLabel(t('More text')) ->setComputed(TRUE) ->setClass('\Drupal\mymodule\EntityComputedMoreText'); - - $info['bundle map']['article'][0] = 'mymodule_text_more'; } - return $info; + return $fields; } } /** - * Alter defined entity fields. + * Alter bundle-specific entity fields. * - * @param array $info - * The entity field info array as returned by hook_entity_field_info(). + * @param \Drupal\Core\Field\FieldDefinitionInterface[] $fields + * An array of field definitions. * @param string $entity_type_id * The entity type for which entity fields are defined. + * @param string $bundle + * The name of the bundle to return field definitions for. * * @see hook_entity_field_info() + * @see hook_entity_field_info_alter() + * @see hook_entity_field_info_by_bundle() */ -function hook_entity_field_info_alter(&$info, $entity_type_id) { - if (!empty($info['definitions']['mymodule_text'])) { +function hook_entity_field_info_by_bundle_alter(&$fields, $entity_type_id, $bundle) { + if (mymodule_uses_entity_type_and_bundle($entity_type_id, $bundle) && !empty($fields['mymodule_text'])) { // Alter the mymodule_text field to use a custom class. - $info['definitions']['mymodule_text']->setClass('\Drupal\anothermodule\EntityComputedText'); + $fields['mymodule_text']->setClass('\Drupal\anothermodule\EntityComputedText'); } } diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityFieldTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityFieldTest.php index 2ea38e9..ca7993f 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityFieldTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityFieldTest.php @@ -355,7 +355,7 @@ public function testIntrospection() { */ protected function checkIntrospection($entity_type) { // Test getting metadata upfront. - $definitions = \Drupal::entityManager()->getFieldDefinitions($entity_type); + $definitions = \Drupal::entityManager()->getFieldDefinitions($entity_type, $entity_type); $this->assertEqual($definitions['name']->getType(), 'string', $entity_type .': Name field found.'); $this->assertEqual($definitions['user_id']->getType(), 'entity_reference', $entity_type .': User field found.'); $this->assertEqual($definitions['field_test_text']->getType(), 'text', $entity_type .': Test-text-field field found.'); diff --git a/core/modules/system/tests/modules/entity_test/entity_test.module b/core/modules/system/tests/modules/entity_test/entity_test.module index a46a1e7..99153d4 100644 --- a/core/modules/system/tests/modules/entity_test/entity_test.module +++ b/core/modules/system/tests/modules/entity_test/entity_test.module @@ -75,10 +75,10 @@ function entity_test_entity_info_alter(&$entity_info) { /** * Implements hook_entity_field_info_alter(). */ -function entity_test_entity_field_info_alter(&$info, $entity_type) { - if ($entity_type == 'entity_test_mulrev' && ($names = \Drupal::state()->get('entity_test.field_definitions.translatable'))) { +function entity_test_entity_field_info_alter(&$fields, $entity_type_id) { + if ($entity_type_id == 'entity_test_mulrev' && ($names = \Drupal::state()->get('entity_test.field_definitions.translatable'))) { foreach ($names as $name => $value) { - $info['definitions'][$name]->setTranslatable($value); + $fields[$name]->setTranslatable($value); } } } diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestRev.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestRev.php index a31dcdc..514864e 100644 --- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestRev.php +++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestRev.php @@ -31,7 +31,8 @@ * "id" = "id", * "uuid" = "uuid", * "revision" = "revision_id", - * "bundle" = "type" + * "bundle" = "type", + * "label" = "name", * }, * links = { * "canonical" = "entity_test.edit_entity_test_rev", diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php index 96d5970..5cd7322 100644 --- a/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php @@ -474,18 +474,16 @@ public function testGetFieldDefinitionsWithCaching() { $expected = array('id' => $field_definition); - // @todo Investigate why this is 0 and 2, not 0/1 or 1/2. $this->cache->expects($this->at(0)) ->method('get') ->with('entity_field_definitions:test_entity_type:en', FALSE) ->will($this->returnValue(FALSE)); + $this->cache->expects($this->once()) + ->method('set'); $this->cache->expects($this->at(2)) ->method('get') ->with('entity_field_definitions:test_entity_type:en', FALSE) - ->will($this->returnValue((object) array('data' => array('definitions' => $expected)))); - - $this->cache->expects($this->once()) - ->method('set'); + ->will($this->returnValue((object) array('data' => $expected))); $this->assertSame($expected, $this->entityManager->getFieldDefinitions('test_entity_type')); $this->entityManager->testClearEntityFieldInfo(); @@ -493,38 +491,6 @@ public function testGetFieldDefinitionsWithCaching() { } /** - * Tests the getFieldDefinitions() method with bundle map. - * - * @covers ::getFieldDefinitions() - */ - public function testGetFieldDefinitionsWithBundleMap() { - $field_definition = $this->setUpEntityWithFieldDefinition(TRUE); - - $this->moduleHandler->expects($this->at(0)) - ->method('invokeAll') - ->will($this->returnValue(array())); - $this->moduleHandler->expects($this->at(1)) - ->method('invokeAll') - ->will($this->returnValue(array( - 'bundle map' => array( - 'test_entity_bundle' => array( - 'custom_field', - ), - ), - 'optional' => array( - 'custom_field' => $field_definition, - ), - ))); - - $expected = array('id' => $field_definition); - $this->assertSame($expected, $this->entityManager->getFieldDefinitions('test_entity_type')); - $this->assertSame($expected, $this->entityManager->getFieldDefinitions('test_entity_type', 'test_entity_type')); - - $expected['custom_field'] = $field_definition; - $this->assertSame($expected, $this->entityManager->getFieldDefinitions('test_entity_type', 'test_entity_bundle')); - } - - /** * Tests the getFieldDefinitions() method with an invalid definition. * * @covers ::getFieldDefinitions() @@ -541,23 +507,6 @@ public function testGetFieldDefinitionsInvalidDefinition() { } /** - * Tests the getFieldDefinitionsByConstraints() method. - * - * @covers ::getFieldDefinitionsByConstraints() - */ - public function testGetFieldDefinitionsByConstraints() { - $field_definition = $this->setUpEntityWithFieldDefinition(); - - $this->moduleHandler->expects($this->exactly(2)) - ->method('invokeAll') - ->will($this->returnValue(array())); - - $expected = array('id' => $field_definition); - $this->assertSame($expected, $this->entityManager->getFieldDefinitionsByConstraints('test_entity_type', array())); - $this->assertSame($expected, $this->entityManager->getFieldDefinitionsByConstraints('test_entity_type', array('Bundle' => 'test_entity_bundle'))); - } - - /** * Prepares an entity that defines a field definition. * * @param bool $custom_invoke_all @@ -574,10 +523,10 @@ protected function setUpEntityWithFieldDefinition($custom_invoke_all = FALSE, $f $entity = $this->getMock('Drupal\Tests\Core\Entity\TestContentEntityInterface'); $entity_class = get_class($entity); - $entity_type->expects($this->exactly(2)) + $entity_type->expects($this->any()) ->method('getClass') ->will($this->returnValue($entity_class)); - $entity_type->expects($this->once()) + $entity_type->expects($this->any()) ->method('getKeys') ->will($this->returnValue(array())); $field_definition = $this->getMockBuilder('Drupal\Core\Field\FieldDefinition') @@ -588,11 +537,14 @@ protected function setUpEntityWithFieldDefinition($custom_invoke_all = FALSE, $f ->will($this->returnValue(array( $field_definition_id => $field_definition, ))); + $entity_class::staticExpects($this->any()) + ->method('baseFieldDefinitionsByBundle') + ->will($this->returnValue(array())); - $this->moduleHandler->expects($this->once()) + $this->moduleHandler->expects($this->any()) ->method('alter'); if (!$custom_invoke_all) { - $this->moduleHandler->expects($this->exactly(2)) + $this->moduleHandler->expects($this->any()) ->method('invokeAll') ->will($this->returnValue(array())); } @@ -821,7 +773,9 @@ public function setDiscovery(DiscoveryInterface $discovery) { * Allows the $entityFieldInfo property to be cleared. */ public function testClearEntityFieldInfo() { - $this->entityFieldInfo = NULL; + $this->baseFieldDefinitions = array(); + $this->fieldDefinitions = array(); + $this->fieldDefinitionsByBundle = array(); } }