EntityManager.php 13.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13
<?php

/**
 * @file
 * Contains \Drupal\Core\Entity\EntityManager.
 */

namespace Drupal\Core\Entity;

use Drupal\Component\Plugin\PluginManagerBase;
use Drupal\Component\Plugin\Factory\DefaultFactory;
use Drupal\Core\Plugin\Discovery\AlterDecorator;
use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
14
use Drupal\Core\Plugin\Discovery\InfoHookDecorator;
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
use Drupal\Core\Cache\CacheBackendInterface;

/**
 * Manages entity type plugin definitions.
 *
 * Each entity type definition array is set in the entity type plugin's
 * annotation and altered by hook_entity_info_alter(). The definition includes
 * the following keys:
 * - module: The name of the module providing the type.
 * - class: The name of the entity type class. Defaults to
 *   Drupal\Core\Entity\Entity.
 * - base_table: The name of the entity type's base table. Used by
 *   Drupal\Core\Entity\DatabaseStorageController.
 * - controller_class: The name of the class that is used to load the objects.
 *   The class must implement
 *   Drupal\Core\Entity\EntityStorageControllerInterface. Defaults to
 *   Drupal\Core\Entity\DatabaseStorageController.
 * - fieldable: (optional) Boolean indicating whether fields can be attached
 *   to entities of this type. Defaults to FALSE.
 * - field_cache: (optional) Boolean indicating whether the Field API's
 *   Field API's persistent cache of field data should be used. The persistent
 *   cache should usually only be disabled if a higher level persistent cache
 *   is available for the entity type. Defaults to TRUE.
 * - form_controller_class: (optional) An associative array where the keys
 *   are the names of the different form operations (such as 'create',
 *   'edit', or 'delete') and the values are the names of the controller
 *   classes for those operations. The name of the operation is passed also
 *   to the form controller's constructor, so that one class can be used for
 *   multiple entity forms when the forms are similar. Defaults to
 *   Drupal\Core\Entity\EntityFormController.
 * - label: The human-readable name of the type.
 * - 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.
 * - list_controller_class: (optional) The name of the class that provides
58
 *   listings of the entities. The class must implement
59 60 61 62
 *   Drupal\Core\Entity\EntityListControllerInterface. Defaults to
 *   Drupal\Core\Entity\EntityListController.
 * - render_controller_class: The name of the class that is used to render the
 *   entities. Defaults to Drupal\Core\Entity\EntityRenderController.
63 64 65 66
 * - access_controller_class: The name of the class that is used for access
 *   checks. The class must implement
 *   Drupal\Core\Entity\EntityAccessControllerInterface. Defaults to
 *   Drupal\Core\Entity\EntityAccessController.
67 68 69 70
 * - translation_controller_class: (optional) The name of the translation
 *   controller class that should be used to handle the translation process.
 *   See Drupal\translation_entity\EntityTranslationControllerInterface for more
 *   information.
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
 * - static_cache: (optional) Boolean indicating whether entities should be
 *   statically cached during a page request. Used by
 *   Drupal\Core\Entity\DatabaseStorageController. Defaults to TRUE.
 * - translation: (optional) An associative array of modules registered as
 *   field translation handlers. Array keys are the module names, and array
 *   values can be any data structure the module uses to provide field
 *   translation. If the value is empty, the module will not be used as a
 *   translation handler.
 * - entity_keys: An array describing how the Field API can extract certain
 *   information from objects of this entity 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: (optional) 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: (optional) 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 (such
 *     that 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
 *   bundle machine names, as found in the objects' 'bundle' property
 *   (defined in the 'entity_keys' entry for the entity type in the
 *   EntityManager). Elements:
 *   - label: The human-readable name of the bundle.
 *   - uri_callback: The same as the 'uri_callback' key defined for the entity
 *     type in the EntityManager, 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.
152 153 154 155 156 157 158 159 160 161
 * - menu_base_path: (optional) The base menu router path to which the entity
 *   administration user interface responds. It can be used to generate UI
 *   links and to attach additional router items to the entity UI in a generic
 *   fashion.
 * - menu_view_path: (optional) The menu router path to be used to view the
 *   entity.
 * - menu_edit_path: (optional) The menu router path to be used to edit the
 *   entity.
 * - menu_path_wildcard: (optional) A string identifying the menu loader in the
 *   router path.
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
 *
 * The defaults for the plugin definition are provided in
 * \Drupal\Core\Entity\EntityManager::defaults.
 *
 * @see \Drupal\Core\Entity\Entity
 * @see entity_get_info()
 * @see hook_entity_info_alter()
 */
class EntityManager extends PluginManagerBase {

  /**
   * The cache bin used for entity plugin definitions.
   *
   * @var string
   */
  protected $cacheBin = 'cache';

  /**
   * The cache key used for entity plugin definitions.
   *
   * @var string
   */
  protected $cacheKey = 'entity_info';

  /**
   * The cache expiration for entity plugin definitions.
   *
   * @var int
   */
  protected $cacheExpire = CacheBackendInterface::CACHE_PERMANENT;

  /**
   * The cache tags used for entity plugin definitions.
   *
   * @var array
   */
  protected $cacheTags = array('entity_info' => TRUE);

  /**
   * The default values for optional keys of the entity plugin definition.
   *
   * @var array
   */
  protected $defaults = array(
    'class' => 'Drupal\Core\Entity\Entity',
    'controller_class' => 'Drupal\Core\Entity\DatabaseStorageController',
    'entity_keys' => array(
      'revision' => '',
      'bundle' => '',
    ),
    'fieldable' => FALSE,
    'field_cache' => TRUE,
    'form_controller_class' => array(
      'default' => 'Drupal\Core\Entity\EntityFormController',
    ),
    'list_controller_class' => 'Drupal\Core\Entity\EntityListController',
    'render_controller_class' => 'Drupal\Core\Entity\EntityRenderController',
219
    'access_controller_class' => 'Drupal\Core\Entity\EntityAccessController',
220 221 222 223 224 225 226 227 228 229 230
    'static_cache' => TRUE,
    'translation' => array(),
    'bundles' => array(),
    'view_modes' => array(),
  );

  /**
   * Constructs a new Entity plugin manager.
   */
  public function __construct() {
    // Allow the plugin definition to be altered by hook_entity_info_alter().
231
    $this->discovery = new AlterDecorator(new InfoHookDecorator(new AnnotatedClassDiscovery('Core', 'Entity'), 'entity_info'), 'entity_info');
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
    $this->factory = new DefaultFactory($this);

    // Entity type plugins includes translated strings, so each language is
    // cached separately.
    $this->cacheKey .= ':' . language(LANGUAGE_TYPE_INTERFACE)->langcode;
  }

  /**
   * Overrides Drupal\Component\Plugin\PluginManagerBase::getDefinition().
   */
  public function getDefinition($plugin_id) {
    $definitions = $this->getDefinitions();
    return isset($definitions[$plugin_id]) ? $definitions[$plugin_id] : NULL;
  }

  /**
   * Overrides Drupal\Component\Plugin\PluginManagerBase::getDefinitions().
   */
  public function getDefinitions() {
    // Because \Drupal\Core\Plugin\Discovery\CacheDecorator runs before
    // definitions are processed and does not support cache tags, we perform our
    // own caching.
    if ($cache = cache($this->cacheBin)->get($this->cacheKey)) {
      return $cache->data;
    }
    else {
      // @todo Remove array_filter() once http://drupal.org/node/1780396 is
      //   resolved.
      $definitions = array_filter(parent::getDefinitions());
      cache($this->cacheBin)->set($this->cacheKey, $definitions, $this->cacheExpire, $this->cacheTags);
      return $definitions;
    }
  }

  /**
   * Overrides Drupal\Component\Plugin\PluginManagerBase::processDefinition().
   */
  protected function processDefinition(&$definition, $plugin_id) {
    parent::processDefinition($definition, $plugin_id);

    // @todo Remove this check once http://drupal.org/node/1780396 is resolved.
    if (!module_exists($definition['module'])) {
      $definition = NULL;
      return;
    }

    foreach ($definition['view_modes'] as $view_mode => $view_mode_info) {
      $definition['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($definition['entity_keys']['bundle']) && empty($definition['bundles'])) {
      $definition['bundles'] = array($plugin_id => array('label' => $definition['label']));
    }
    // Prepare entity schema fields SQL info for
    // Drupal\Core\Entity\DatabaseStorageControllerInterface::buildQuery().
    if (isset($definition['base_table'])) {
      $definition['schema_fields_sql']['base_table'] = drupal_schema_fields_sql($definition['base_table']);
      if (isset($definition['revision_table'])) {
        $definition['schema_fields_sql']['revision_table'] = drupal_schema_fields_sql($definition['revision_table']);
      }
    }
  }

}