Commit 76a157fe authored by webchick's avatar webchick

Issue #1763974 by tim.plunkett, EclipseGC, berdir, xjm, neclimdul, FabianX:...

Issue #1763974 by tim.plunkett, EclipseGC, berdir, xjm, neclimdul, FabianX: Convert entity type info into plugins.
parent a61f62b4
......@@ -11,232 +11,27 @@
*/
/**
* Inform the base system and the Field API about one or more entity types.
*
* Inform the system about one or more entity types (i.e., object types that
* can be loaded via entity_load() and, optionally, to which fields can be
* attached).
*
* @return
* An array whose keys are entity type names and whose values identify
* properties of those types that the system needs to know about:
* - label: The human-readable name of the type.
* - entity class: The name of the entity class, defaults to
* Drupal\Core\Entity\Entity. The entity class must implement EntityInterface.
* - controller class: The name of the class that is used to load the objects.
* The class has to implement the
* Drupal\Core\Entity\EntityStorageControllerInterface interface. Leave blank
* to use the Drupal\Core\Entity\DatabaseStorageController implementation.
* - render controller class: The name of the class that is used to render
* the entities. Deafaults to Drupal\Core\Entity\EntityRenderController.
* - form controller class: An associative array where the keys are the names
* of the different form operations (such as creation, editing or deletion)
* and the values are the names of the controller classes. To facilitate
* supporting the case where an entity form varies only slightly between
* different operations, the name of the operation is passed also to the
* constructor of the form controller class. This way, one class can be used
* for multiple entity forms.
* - list controller class: The name of the class that is used to provide
* listings of the entity. The class must implement
* Drupal\Core\Entity\EntityListControllerInterface. Defaults to
* Drupal\Core\Entity\EntityListController.
* - base table: (used by Drupal\Core\Entity\DatabaseStorageController) The
* name of the entity type's base table.
* - static cache: (used by Drupal\Core\Entity\DatabaseStorageController)
* FALSE to disable static caching of entities during a page request.
* Defaults to TRUE.
* - field cache: (used by Field API loading and saving of field data) FALSE
* to disable Field API's persistent cache of field data. Only recommended
* if a higher level persistent cache is available for the entity type.
* Defaults to TRUE.
* - uri callback: A function taking an entity as argument and returning the
* URI elements of the entity, e.g. 'path' and 'options'. The actual entity
* URI can be constructed by passing these elements to url().
* - label callback: (optional) A function taking an entity and optional langcode
* argument, and returning the label of the entity. If langcode is omitted, the
* entity's default language is used.
*
* The entity label is the main string associated with an entity; for
* example, the title of a node or the subject of a comment. If there is an
* entity object property that defines the label, use the 'label' element
* of the 'entity keys' return value component to provide this information
* (see below). If more complex logic is needed to determine the label of
* an entity, you can instead specify a callback function here, which will
* be called to determine the entity label. See also the
* Drupal\Core\Entity\Entity::label() method, which implements this logic.
* - fieldable: Set to TRUE if you want your entity type to be fieldable.
* - translation: An associative array of modules registered as field
* translation handlers. Array keys are the module names, array values
* can be any data structure the module uses to provide field translation.
* Any empty value disallows the module to appear as a translation handler.
* - entity keys: An array describing how the Field API can extract the
* information it needs from the objects of the type. Elements:
* - id: The name of the property that contains the primary id of the
* entity. Every entity object passed to the Field API must have this
* property and its value must be numeric.
* - revision: The name of the property that contains the revision id of
* the entity. The Field API assumes that all revision ids are unique
* across all entities of a type. This entry can be omitted if the
* entities of this type are not versionable.
* - bundle: The name of the property that contains the bundle name for the
* entity. The bundle name defines which set of fields are attached to
* the entity (e.g. what nodes call "content type"). This entry can be
* omitted if this entity type exposes a single bundle (all entities have
* the same collection of fields). The name of this single bundle will be
* the same as the entity type.
* - label: The name of the property that contains the entity label. For
* example, if the entity's label is located in $entity->subject, then
* 'subject' should be specified here. If complex logic is required to
* build the label, a 'label callback' should be defined instead (see
* the 'label callback' section above for details).
* - uuid (optional): The name of the property that contains the universally
* unique identifier of the entity, which is used to distinctly identify
* an entity across different systems.
* - bundle keys: An array describing how the Field API can extract the
* information it needs from the bundle objects for this type (e.g
* $vocabulary objects for terms; not applicable for nodes). This entry can
* be omitted if this type's bundles do not exist as standalone objects.
* Elements:
* - bundle: The name of the property that contains the name of the bundle
* object.
* - bundles: An array describing all bundles for this object type. Keys are
* bundles machine names, as found in the objects' 'bundle' property
* (defined in the 'entity keys' entry above). Elements:
* - label: The human-readable name of the bundle.
* - uri callback: Same as the 'uri callback' key documented above for the
* entity type, but for the bundle only. When determining the URI of an
* entity, if a 'uri callback' is defined for both the entity type and
* the bundle, the one for the bundle is used.
* - admin: An array of information that allows Field UI pages to attach
* themselves to the existing administration pages for the bundle.
* Elements:
* - path: the path of the bundle's main administration page, as defined
* in hook_menu(). If the path includes a placeholder for the bundle,
* the 'bundle argument', 'bundle helper' and 'real path' keys below
* are required.
* - bundle argument: The position of the placeholder in 'path', if any.
* - real path: The actual path (no placeholder) of the bundle's main
* administration page. This will be used to generate links.
* - access callback: As in hook_menu(). 'user_access' will be assumed if
* no value is provided.
* - access arguments: As in hook_menu().
* - view modes: An array describing the view modes for the entity type. View
* modes let entities be displayed differently depending on the context.
* For instance, a node can be displayed differently on its own page
* ('full' mode), on the home page or taxonomy listings ('teaser' mode), or
* in an RSS feed ('rss' mode). Modules taking part in the display of the
* entity (notably the Field API) can adjust their behavior depending on
* the requested view mode. An additional 'default' view mode is available
* for all entity types. This view mode is not intended for actual entity
* display, but holds default display settings. For each available view
* mode, administrators can configure whether it should use its own set of
* field display settings, or just replicate the settings of the 'default'
* view mode, thus reducing the amount of display configurations to keep
* track of. Keys of the array are view mode names. Each view mode is
* described by an array with the following key/value pairs:
* - label: The human-readable name of the view mode
* - custom settings: A boolean specifying whether the view mode should by
* default use its own custom field display settings. If FALSE, entities
* displayed in this view mode will reuse the 'default' display settings
* by default (e.g. right after the module exposing the view mode is
* enabled), but administrators can later use the Field UI to apply custom
* display settings specific to the view mode.
*
* @see entity_load()
* @see entity_load_multiple()
* @see hook_entity_info_alter()
*/
function hook_entity_info() {
$return = array(
'node' => array(
'label' => t('Node'),
'entity class' => 'Drupal\node\Node',
'controller class' => 'Drupal\node\NodeStorageController',
'form controller class' => array(
'default' => 'Drupal\node\NodeFormController',
),
'base table' => 'node',
'revision table' => 'node_revision',
'uri callback' => 'node_uri',
'fieldable' => TRUE,
'translation' => array(
'locale' => TRUE,
),
'entity keys' => array(
'id' => 'nid',
'revision' => 'vid',
'bundle' => 'type',
'uuid' => 'uuid',
),
'bundle keys' => array(
'bundle' => 'type',
),
'bundles' => array(),
'view modes' => array(
'full' => array(
'label' => t('Full content'),
'custom settings' => FALSE,
),
'teaser' => array(
'label' => t('Teaser'),
'custom settings' => TRUE,
),
'rss' => array(
'label' => t('RSS'),
'custom settings' => FALSE,
),
),
),
);
// Search integration is provided by node.module, so search-related
// view modes for nodes are defined here and not in search.module.
if (module_exists('search')) {
$return['node']['view modes'] += array(
'search_index' => array(
'label' => t('Search index'),
'custom settings' => FALSE,
),
'search_result' => array(
'label' => t('Search result'),
'custom settings' => FALSE,
),
);
}
// Bundles must provide a human readable name so we can create help and error
// messages, and the path to attach Field admin pages to.
foreach (node_type_get_names() as $type => $name) {
$return['node']['bundles'][$type] = array(
'label' => $name,
'admin' => array(
'path' => 'admin/structure/types/manage/%node_type',
'real path' => 'admin/structure/types/manage/' . $type,
'bundle argument' => 4,
'access arguments' => array('administer content types'),
),
);
}
return $return;
}
/**
* Alter the entity info.
* Alter the entity type definition.
*
* Modules may implement this hook to alter the information that defines an
* entity. All properties that are available in hook_entity_info() can be
* altered here.
* entity. All properties that are available in
* \Drupal\Core\Entity\EntityManager can be altered here.
*
* @param array $entity_info
* An associative array of all entity type definitions, keyed by the entity
* type name.
*
* @param $entity_info
* The entity info array, keyed by entity name.
* @see \Drupal\Core\Entity\Entity
* @see \Drupal\Core\Entity\EntityManager
* @see entity_get_info()
*
* @see hook_entity_info()
* @todo Allow a module to add its bundles and view modes before other modules
* alter the definition.
*/
function hook_entity_info_alter(&$entity_info) {
// Set the controller class for nodes to an alternate implementation of the
// Drupal\Core\Entity\EntityStorageControllerInterface interface.
$entity_info['node']['controller class'] = 'Drupal\mymodule\MyCustomNodeStorageController';
$entity_info['node']['controller_class'] = 'Drupal\mymodule\MyCustomNodeStorageController';
}
/**
......
......@@ -10,18 +10,21 @@
use Drupal\Core\Entity\EntityInterface;
/**
* Gets the entity info array of an entity type.
* Gets the entity definition for an entity type.
*
* @param $entity_type
* The entity type, e.g. node, for which the info shall be returned, or NULL
* to return an array with info about all types.
* @param string|null $entity_type
* (optional) The entity type (e.g. 'node'). Leave NULL to retrieve
* information for all entity types.
*
* @return array
* An array containing the entity type's definition, as retrieved with
* \Drupal\Core\Entity\EntityManager. If $entity_type is NULL, an associative
* array of all entity type definitions keyed by entity type is returned.
*
* @see hook_entity_info()
* @see \Drupal\Core\Entity\EntityManager
* @see hook_entity_info_alter()
*/
function entity_get_info($entity_type = NULL) {
$language_interface = language(LANGUAGE_TYPE_INTERFACE);
// Use the advanced drupal_static() pattern, since this is called very often.
static $drupal_static_fast;
if (!isset($drupal_static_fast)) {
......@@ -29,61 +32,8 @@ function entity_get_info($entity_type = NULL) {
}
$entity_info = &$drupal_static_fast['entity_info'];
// hook_entity_info() includes translated strings, so each language is cached
// separately.
$langcode = $language_interface->langcode;
if (empty($entity_info)) {
if ($cache = cache()->get("entity_info:$langcode")) {
$entity_info = $cache->data;
}
else {
$entity_info = module_invoke_all('entity_info');
// Merge in default values.
foreach ($entity_info as $name => $data) {
$entity_info[$name] += array(
'fieldable' => FALSE,
'entity class' => 'Drupal\Core\Entity\Entity',
'controller class' => 'Drupal\Core\Entity\DatabaseStorageController',
'list controller class' => 'Drupal\Core\Entity\EntityListController',
'render controller class' => 'Drupal\Core\Entity\EntityRenderController',
'form controller class' => array(
'default' => 'Drupal\Core\Entity\EntityFormController',
),
'static cache' => TRUE,
'field cache' => TRUE,
'bundles' => array(),
'view modes' => array(),
'entity keys' => array(),
'translation' => array(),
);
$entity_info[$name]['entity keys'] += array(
'revision' => '',
'bundle' => '',
);
foreach ($entity_info[$name]['view modes'] as $view_mode => $view_mode_info) {
$entity_info[$name]['view modes'][$view_mode] += array(
'custom settings' => FALSE,
);
}
// If no bundle key is provided, assume a single bundle, named after
// the entity type.
if (empty($entity_info[$name]['entity keys']['bundle']) && empty($entity_info[$name]['bundles'])) {
$entity_info[$name]['bundles'] = array($name => array('label' => $entity_info[$name]['label']));
}
// Prepare entity schema fields SQL info for
// Drupal\Core\Entity\DatabaseStorageControllerInterface::buildQuery().
if (isset($entity_info[$name]['base table'])) {
$entity_info[$name]['schema_fields_sql']['base table'] = drupal_schema_fields_sql($entity_info[$name]['base table']);
if (isset($entity_info[$name]['revision table'])) {
$entity_info[$name]['schema_fields_sql']['revision table'] = drupal_schema_fields_sql($entity_info[$name]['revision table']);
}
}
}
// Let other modules alter the entity info.
drupal_alter('entity_info', $entity_info);
cache()->set("entity_info:$langcode", $entity_info, CacheBackendInterface::CACHE_PERMANENT, array('entity_info' => TRUE));
}
$entity_info = drupal_container()->get('plugin.manager.entity')->getDefinitions();
}
if (empty($entity_type)) {
......@@ -116,7 +66,7 @@ function entity_info_cache_clear() {
* @return Drupal\Core\Entity\EntityInterface
* The entity object, or FALSE if there is no entity with the given id.
*
* @see hook_entity_info()
* @see \Drupal\Core\Entity\EntityManager
* @see entity_load_multiple()
* @see Drupal\Core\Entity\EntityStorageControllerInterface
* @see Drupal\Core\Entity\DatabaseStorageController
......@@ -139,7 +89,7 @@ function entity_load($entity_type, $id, $reset = FALSE) {
* The entity object, or FALSE if there is no entity with the given revision
* id.
*
* @see hook_entity_info()
* @see \Drupal\Core\Entity\EntityManager
* @see Drupal\Core\Entity\EntityStorageControllerInterface
* @see Drupal\Core\Entity\DatabaseStorageController
*/
......@@ -177,14 +127,14 @@ function entity_revision_delete($entity_type, $revision_id) {
* @throws Drupal\Core\Entity\EntityStorageException
* Thrown in case the requested entity type does not support UUIDs.
*
* @see hook_entity_info()
* @see \Drupal\Core\Entity\EntityManager
*/
function entity_load_by_uuid($entity_type, $uuid, $reset = FALSE) {
$entity_info = entity_get_info($entity_type);
if (empty($entity_info['entity keys']['uuid'])) {
if (empty($entity_info['entity_keys']['uuid'])) {
throw new EntityStorageException("Entity type $entity_type does not support UUIDs.");
}
$uuid_key = $entity_info['entity keys']['uuid'];
$uuid_key = $entity_info['entity_keys']['uuid'];
$controller = entity_get_controller($entity_type);
if ($reset) {
......@@ -205,11 +155,12 @@ function entity_load_by_uuid($entity_type, $uuid, $reset = FALSE) {
* Drupal\Core\Entity\EntityStorageControllerInterface interface. By default,
* Drupal\Core\Entity\DatabaseStorageController is used. Entity types can
* specify that a different class should be used by setting the
* 'controller class' key in hook_entity_info(). These classes can either
* implement the Drupal\Core\Entity\EntityStorageControllerInterface interface, or,
* most commonly, extend the Drupal\Core\Entity\DatabaseStorageController
* class. See node_entity_info() and the NodeStorageController in node.module as
* an example.
* 'controller_class' key in the entity plugin annotation. These classes can
* either implement the Drupal\Core\Entity\EntityStorageControllerInterface
* interface, or, most commonly, extend the
* Drupal\Core\Entity\DatabaseStorageController class.
* See Drupal\node\Plugin\Core\Entity\Node and Drupal\node\NodeStorageController
* for an example.
*
* @param string $entity_type
* The entity type to load, e.g. node or user.
......@@ -221,7 +172,7 @@ function entity_load_by_uuid($entity_type, $uuid, $reset = FALSE) {
* @return array
* An array of entity objects indexed by their ids.
*
* @see hook_entity_info()
* @see \Drupal\Core\Entity\EntityManager
* @see Drupal\Core\Entity\EntityStorageControllerInterface
* @see Drupal\Core\Entity\DatabaseStorageController
* @see Drupal\Core\Entity\Query\QueryInterface
......@@ -308,7 +259,7 @@ function entity_get_controller($entity_type) {
$controllers = &drupal_static(__FUNCTION__, array());
if (!isset($controllers[$entity_type])) {
$type_info = entity_get_info($entity_type);
$class = $type_info['controller class'];
$class = $type_info['controller_class'];
$controllers[$entity_type] = new $class($entity_type);
}
return $controllers[$entity_type];
......@@ -345,7 +296,7 @@ function entity_page_label(EntityInterface $entity, $langcode = NULL) {
* used. If a non-existing non-default operation is specified an exception will
* be thrown.
*
* @see hook_entity_info()
* @see \Drupal\Core\Entity\EntityManager
*
* @param $entity_type
* The type of the entity.
......@@ -361,11 +312,11 @@ function entity_form_controller($entity_type, $operation = 'default') {
$info = entity_get_info($entity_type);
// Check whether there is a form controller class for the specified operation.
if (!empty($info['form controller class'][$operation])) {
$class = $info['form controller class'][$operation];
if (!empty($info['form_controller_class'][$operation])) {
$class = $info['form_controller_class'][$operation];
}
// If no controller is specified default to the base implementation.
elseif (empty($info['form controller class']) && $operation == 'default') {
elseif (empty($info['form_controller_class']) && $operation == 'default') {
$class = 'Drupal\Core\Entity\EntityFormController';
}
// If a non-existing operation has been specified stop.
......@@ -511,12 +462,12 @@ function entity_form_submit_build_entity($entity_type, $entity, $form, &$form_st
* @return Drupal\Core\Entity\EntityListControllerInterface
* An entity list controller.
*
* @see hook_entity_info()
* @see \Drupal\Core\Entity\EntityManager
*/
function entity_list_controller($entity_type) {
$storage = entity_get_controller($entity_type);
$entity_info = entity_get_info($entity_type);
$class = $entity_info['list controller class'];
$class = $entity_info['list_controller_class'];
return new $class($entity_type, $storage);
}
......@@ -529,11 +480,11 @@ function entity_list_controller($entity_type) {
* @return Drupal\Core\Entity\EntityRenderControllerInterface
* An entity render controller.
*
* @see hook_entity_info()
* @see \Drupal\Core\Entity\EntityManager
*/
function entity_render_controller($entity_type) {
$info = entity_get_info($entity_type);
$class = $info['render controller class'];
$class = $info['render_controller_class'];
return new $class($entity_type);
}
......
......@@ -65,7 +65,7 @@ public function __construct($entityType) {
$this->entityType = $entityType;
$this->entityInfo = entity_get_info($entityType);
$this->hookLoadArguments = array();
$this->idKey = $this->entityInfo['entity keys']['id'];
$this->idKey = $this->entityInfo['entity_keys']['id'];
}
/**
......@@ -158,8 +158,8 @@ public function loadByProperties(array $values = array()) {
* A SelectQuery object for loading the entity.
*/
protected function buildQuery($ids, $revision_id = FALSE) {
$config_class = $this->entityInfo['entity class'];
$prefix = $this->entityInfo['config prefix'] . '.';
$config_class = $this->entityInfo['class'];
$prefix = $this->entityInfo['config_prefix'] . '.';
// Load all of the configuration entities.
if ($ids === NULL) {
......@@ -220,7 +220,7 @@ protected function attachLoad(&$queried_entities, $revision_id = FALSE) {
* Implements Drupal\Core\Entity\EntityStorageControllerInterface::create().
*/
public function create(array $values) {
$class = isset($this->entityInfo['entity class']) ? $this->entityInfo['entity class'] : 'Drupal\Core\Entity\Entity';
$class = $this->entityInfo['class'];
$entity = new $class($values, $this->entityType);
// Mark this entity as new, so isNew() returns TRUE. This does not check
......@@ -252,7 +252,7 @@ public function delete($ids) {
}
foreach ($entities as $id => $entity) {
$config = config($this->entityInfo['config prefix'] . '.' . $entity->id());
$config = config($this->entityInfo['config_prefix'] . '.' . $entity->id());
$config->delete();
}
......@@ -269,7 +269,7 @@ public function delete($ids) {
* When attempting to save a configuration entity that has no ID.
*/
public function save(EntityInterface $entity) {
$prefix = $this->entityInfo['config prefix'] . '.';
$prefix = $this->entityInfo['config_prefix'] . '.';
// Configuration entity IDs are strings, and '0' is a valid ID.
$id = $entity->id();
......
......@@ -70,6 +70,8 @@ public function build(ContainerBuilder $container) {
->addArgument(new Reference('router.dumper'))
->addArgument(new Reference('lock'));
$container->register('plugin.manager.entity', 'Drupal\Core\Entity\EntityManager');
$container->register('matcher', 'Drupal\Core\Routing\ChainMatcher');
$container->register('legacy_url_matcher', 'Drupal\Core\LegacyUrlMatcher')
->addTag('chained_matcher');
......
......@@ -117,27 +117,27 @@ public function __construct($entityType) {
$this->entityInfo = entity_get_info($entityType);
$this->entityCache = array();
$this->hookLoadArguments = array();
$this->idKey = $this->entityInfo['entity keys']['id'];
$this->idKey = $this->entityInfo['entity_keys']['id'];
// Check if the entity type supports UUIDs.
if (!empty($this->entityInfo['entity keys']['uuid'])) {
$this->uuidKey = $this->entityInfo['entity keys']['uuid'];
if (!empty($this->entityInfo['entity_keys']['uuid'])) {
$this->uuidKey = $this->entityInfo['entity_keys']['uuid'];
}
else {
$this->uuidKey = FALSE;
}
// Check if the entity type supports revisions.
if (!empty($this->entityInfo['entity keys']['revision'])) {
$this->revisionKey = $this->entityInfo['entity keys']['revision'];
$this->revisionTable = $this->entityInfo['revision table'];
if (!empty($this->entityInfo['entity_keys']['revision'])) {
$this->revisionKey = $this->entityInfo['entity_keys']['revision'];
$this->revisionTable = $this->entityInfo['revision_table'];
}
else {
$this->revisionKey = FALSE;
}
// Check if the entity type supports static caching of loaded entities.
$this->cache = !empty($this->entityInfo['static cache']);
$this->cache = !empty($this->entityInfo['static_cache']);
}
/**
......@@ -183,11 +183,11 @@ public function load(array $ids = NULL) {
// Build and execute the query.
$query_result = $this->buildQuery($ids)->execute();
if (!empty($this->entityInfo['entity class'])) {
if (!empty($this->entityInfo['class'])) {
// We provide the necessary arguments for PDO to create objects of the
// specified entity class.
// @see Drupal\Core\Entity\EntityInterface::__construct()
$query_result->setFetchMode(PDO::FETCH_CLASS, $this->entityInfo['entity class'], array(array(), $this->entityType));
$query_result->setFetchMode(PDO::FETCH_CLASS, $this->entityInfo['class'], array(array(), $this->entityType));
}
$queried_entities = $query_result->fetchAllAssoc($this->idKey);
}
......@@ -228,11 +228,11 @@ public function loadRevision($revision_id) {
// Build and execute the query.
$query_result = $this->buildQuery(array(), $revision_id)->execute();
if (!empty($this->entityInfo['entity class'])) {
if (!empty($this->entityInfo['class'])) {
// We provide the necessary arguments for PDO to create objects of the
// specified entity class.
// @see Drupal\Core\Entity\EntityInterface::__construct()
$query_result->setFetchMode(PDO::FETCH_CLASS, $this->entityInfo['entity class'], array(array(), $this->entityType));
$query_result->setFetchMode(PDO::FETCH_CLASS, $this->entityInfo['class'], array(array(), $this->entityType));
}
$queried_entities = $query_result->fetchAllAssoc($this->idKey);
......@@ -310,7 +310,7 @@ protected function buildPropertyQuery(QueryInterface $entity_query, array $value
* A SelectQuery object for loading the entity.
*/
protected function buildQuery($ids, $revision_id = FALSE) {
$query = db_select($this->entityInfo['base table'], 'base');
$query = db_select($this->entityInfo['base_table'], 'base');
$query->addTag($this->entityType . '_load_multiple');
......@@ -322,11 +322,11 @@ protected function buildQuery($ids, $revision_id = FALSE) {
}
// Add fields from the {entity} table.