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

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

namespace Drupal\Core\Entity;

use Drupal\Component\Plugin\PluginManagerBase;
use Drupal\Component\Plugin\Factory\DefaultFactory;
12 13 14
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Language\LanguageManager;
15
use Drupal\Core\Language\Language;
16
use Drupal\Core\Plugin\Discovery\AlterDecorator;
17
use Drupal\Core\Plugin\Discovery\CacheDecorator;
18
use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
19
use Drupal\Core\Plugin\Discovery\InfoHookDecorator;
20
use Drupal\Core\Cache\CacheBackendInterface;
21
use Drupal\Core\StringTranslation\TranslationInterface;
22
use Symfony\Component\DependencyInjection\ContainerInterface;
23 24 25 26

/**
 * Manages entity type plugin definitions.
 *
27 28
 * Each entity type definition array is set in the entity type's
 * annotation and altered by hook_entity_info_alter().
29 30
 *
 * The defaults for the plugin definition are provided in
31
 * \Drupal\Core\Entity\EntityManagerInterface::defaults.
32
 *
33 34
 * @see \Drupal\Core\Entity\Annotation\EntityType
 * @see \Drupal\Core\Entity\EntityInterface
35 36 37
 * @see entity_get_info()
 * @see hook_entity_info_alter()
 */
38
class EntityManager extends PluginManagerBase implements EntityManagerInterface {
39

40 41 42 43 44 45 46
  /**
   * The injection container that should be passed into the controller factory.
   *
   * @var \Symfony\Component\DependencyInjection\ContainerInterface
   */
  protected $container;

47 48 49 50 51 52 53
  /**
   * Contains instantiated controllers keyed by controller type and entity type.
   *
   * @var array
   */
  protected $controllers = array();

54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
  /**
   * 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;

91 92 93
  /**
   * The root paths.
   *
94
   * @see self::__construct().
95 96 97 98 99
   *
   * @var \Traversable
   */
  protected $namespaces;

100 101 102 103 104 105 106
  /**
   * The string translationManager.
   *
   * @var \Drupal\Core\StringTranslation\TranslationInterface
   */
  protected $translationManager;

107
  /**
108 109 110 111 112 113
   * Static cache of bundle information.
   *
   * @var array
   */
  protected $bundleInfo;

114 115
  /**
   * Constructs a new Entity plugin manager.
116
   *
117 118 119
   * @param \Traversable $namespaces
   *   An object that implements \Traversable which contains the root paths
   *   keyed by the corresponding namespace to look for plugin implementations,
120 121
   * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
   *   The service container this object should use.
122 123 124 125 126 127
   * @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.
128 129
   * @param \Drupal\Core\StringTranslation\TranslationInterface $translation_manager
   *   The string translationManager.
130
   */
131
  public function __construct(\Traversable $namespaces, ContainerInterface $container, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache, LanguageManager $language_manager, TranslationInterface $translation_manager) {
132
    // Allow the plugin definition to be altered by hook_entity_info_alter().
133 134 135 136

    $this->moduleHandler = $module_handler;
    $this->cache = $cache;
    $this->languageManager = $language_manager;
137
    $this->namespaces = $namespaces;
138
    $this->translationManager = $translation_manager;
139

140 141 142 143 144 145
    $this->doDiscovery($namespaces);
    $this->factory = new DefaultFactory($this->discovery);
    $this->container = $container;
  }

  protected function doDiscovery($namespaces) {
146
    $this->discovery = new AnnotatedClassDiscovery('Entity', $namespaces, 'Drupal\Core\Entity\Annotation\EntityType');
147
    $this->discovery = new InfoHookDecorator($this->discovery, 'entity_info');
148
    $this->discovery = new AlterDecorator($this->discovery, 'entity_info');
149
    $this->discovery = new CacheDecorator($this->discovery, 'entity_info:' . $this->languageManager->getLanguage(Language::TYPE_INTERFACE)->id, 'cache', CacheBackendInterface::CACHE_PERMANENT, array('entity_info' => TRUE));
150
  }
151

152
  /**
153
   * {@inheritdoc}
154 155 156 157 158 159 160
   */
  public function addNamespaces(\Traversable $namespaces) {
    reset($this->namespaces);
    $iterator = new \AppendIterator;
    $iterator->append(new \IteratorIterator($this->namespaces));
    $iterator->append($namespaces);
    $this->doDiscovery($iterator);
161 162
  }

163 164 165 166 167 168 169 170 171
  /**
   * {@inheritdoc}
   */
  public function clearCachedDefinitions() {
    parent::clearCachedDefinitions();

    $this->bundleInfo = NULL;
  }

172
  /**
173
   * {@inheritdoc}
174 175 176
   */
  public function hasController($entity_type, $controller_type) {
    $definition = $this->getDefinition($entity_type);
177
    return !empty($definition['controllers'][$controller_type]);
178 179
  }

180
  /**
181
   * {@inheritdoc}
182 183 184
   */
  public function getControllerClass($entity_type, $controller_type, $nested = NULL) {
    $definition = $this->getDefinition($entity_type);
185 186 187
    if (!$definition) {
      throw new \InvalidArgumentException(sprintf('The %s entity type does not exist.', $entity_type));
    }
188
    $definition = $definition['controllers'];
189 190 191 192
    if (!$definition) {
      throw new \InvalidArgumentException(sprintf('The entity type (%s) does not exist.', $entity_type));
    }

193
    if (empty($definition[$controller_type])) {
194
      throw new \InvalidArgumentException(sprintf('The entity type (%s) did not specify a %s controller.', $entity_type, $controller_type));
195 196 197 198 199 200 201
    }

    $class = $definition[$controller_type];

    // Some class definitions can be nested.
    if (isset($nested)) {
      if (empty($class[$nested])) {
202
        throw new \InvalidArgumentException(sprintf("The entity type (%s) did not specify a %s controller: %s.", $entity_type, $controller_type, $nested));
203 204 205 206 207 208
      }

      $class = $class[$nested];
    }

    if (!class_exists($class)) {
209
      throw new \InvalidArgumentException(sprintf('The entity type (%s) %s controller "%s" does not exist.', $entity_type, $controller_type, $class));
210 211 212 213 214 215
    }

    return $class;
  }

  /**
216
   * {@inheritdoc}
217 218
   */
  public function getStorageController($entity_type) {
219
    return $this->getController($entity_type, 'storage');
220 221 222
  }

  /**
223
   * {@inheritdoc}
224 225 226
   */
  public function getListController($entity_type) {
    if (!isset($this->controllers['listing'][$entity_type])) {
227
      $class = $this->getControllerClass($entity_type, 'list');
228 229 230 231 232 233
      if (in_array('Drupal\Core\Entity\EntityControllerInterface', class_implements($class))) {
        $this->controllers['listing'][$entity_type] = $class::createInstance($this->container, $entity_type, $this->getDefinition($entity_type));
      }
      else {
        $this->controllers['listing'][$entity_type] = new $class($entity_type, $this->getStorageController($entity_type));
      }
234 235 236 237 238
    }
    return $this->controllers['listing'][$entity_type];
  }

  /**
239
   * {@inheritdoc}
240 241 242
   */
  public function getFormController($entity_type, $operation) {
    if (!isset($this->controllers['form'][$operation][$entity_type])) {
243
      $class = $this->getControllerClass($entity_type, 'form', $operation);
244
      if (in_array('Drupal\Core\DependencyInjection\ContainerInjectionInterface', class_implements($class))) {
245
        $controller = $class::create($this->container);
246 247
      }
      else {
248
        $controller = new $class();
249
      }
250 251 252 253 254 255

      $controller
        ->setTranslationManager($this->translationManager)
        ->setModuleHandler($this->moduleHandler)
        ->setOperation($operation);
      $this->controllers['form'][$operation][$entity_type] = $controller;
256 257 258 259 260
    }
    return $this->controllers['form'][$operation][$entity_type];
  }

  /**
261
   * {@inheritdoc}
262
   */
263 264
  public function getViewBuilder($entity_type) {
    return $this->getController($entity_type, 'view_builder');
265 266 267
  }

  /**
268
   * {@inheritdoc}
269 270
   */
  public function getAccessController($entity_type) {
271 272 273 274 275
    if (!isset($this->controllers['access'][$entity_type])) {
      $controller = $this->getController($entity_type, 'access');
      $controller->setModuleHandler($this->moduleHandler);
    }
    return $this->controllers['access'][$entity_type];
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291
  }

  /**
   * Creates a new controller instance.
   *
   * @param string $entity_type
   *   The entity type for this access controller.
   * @param string $controller_type
   *   The controller type to create an instance for.
   *
   * @return mixed.
   *   A controller instance.
   */
  protected function getController($entity_type, $controller_type) {
    if (!isset($this->controllers[$controller_type][$entity_type])) {
      $class = $this->getControllerClass($entity_type, $controller_type);
292
      if (in_array('Drupal\Core\Entity\EntityControllerInterface', class_implements($class))) {
293
        $this->controllers[$controller_type][$entity_type] = $class::createInstance($this->container, $entity_type, $this->getDefinition($entity_type));
294 295
      }
      else {
296
        $this->controllers[$controller_type][$entity_type] = new $class($entity_type, $this->getDefinition($entity_type));
297
      }
298
    }
299
    return $this->controllers[$controller_type][$entity_type];
300 301
  }

302
  /**
303
   * {@inheritdoc}
304 305 306
   */
  public function getForm(EntityInterface $entity, $operation = 'default', array $form_state = array()) {
    $form_state += entity_form_state_defaults($entity, $operation);
307
    $form_id = $form_state['build_info']['callback_object']->getFormId();
308 309 310
    return drupal_build_form($form_id, $form_state);
  }

311
  /**
312
   * {@inheritdoc}
313 314 315 316 317 318 319 320 321 322 323 324 325
   */
  public function getAdminPath($entity_type, $bundle) {
    $admin_path = '';
    $entity_info = $this->getDefinition($entity_type);
    // Check for an entity type's admin base path.
    if (isset($entity_info['route_base_path'])) {
      // Replace any dynamic 'bundle' portion of the path with the actual bundle.
      $admin_path = str_replace('{bundle}', $bundle, $entity_info['route_base_path']);
    }

    return $admin_path;
  }

326
  /**
327
   * {@inheritdoc}
328 329 330
   */
  public function getAdminRouteInfo($entity_type, $bundle) {
    return array(
331
      'route_name' => "field_ui.overview_$entity_type",
332 333 334 335 336 337
      'route_parameters' => array(
        'bundle' => $bundle,
      )
    );
  }

338
  /**
339
   * {@inheritdoc}
340 341 342 343
   */
  public function getFieldDefinitions($entity_type, $bundle = NULL) {
    if (!isset($this->entityFieldInfo[$entity_type])) {
      // First, try to load from cache.
344
      $cid = 'entity_field_definitions:' . $entity_type . ':' . $this->languageManager->getLanguage(Language::TYPE_INTERFACE)->id;
345 346 347 348
      if ($cache = $this->cache->get($cid)) {
        $this->entityFieldInfo[$entity_type] = $cache->data;
      }
      else {
349
        $class = $this->factory->getPluginClass($entity_type, $this->getDefinition($entity_type));
350 351 352 353 354 355 356 357

        $base_definitions = $class::baseFieldDefinitions($entity_type);
        foreach ($base_definitions as &$base_definition) {
          // Support old-style field types to avoid that all base field
          // definitions need to be changed.
          // @todo: Remove after https://drupal.org/node/2047229.
          $base_definition['type'] = preg_replace('/(.+)_field/', 'field_item:$1', $base_definition['type']);
        }
358
        $this->entityFieldInfo[$entity_type] = array(
359
          'definitions' => $base_definitions,
360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375
          // 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);

376 377 378 379 380 381 382 383 384 385 386 387 388 389 390
        // Enforce fields to be multiple and untranslatable by default.
        $entity_info = $this->getDefinition($entity_type);
        $keys = array_intersect_key(array_filter($entity_info['entity_keys']), 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][$key] as $name => &$definition) {
            $definition['list'] = TRUE;
            // Ensure ids and langcode fields are never made translatable.
            if (isset($untranslatable_fields[$name]) && !empty($definition['translatable'])) {
              throw new \LogicException(format_string('The @field field cannot be translatable.', array('@field' => $definition['label'])));
            }
            if (!isset($definition['translatable'])) {
              $definition['translatable'] = FALSE;
            }
          }
391
        }
392

393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412
        $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];
    }
  }

  /**
413
   * {@inheritdoc}
414 415 416 417 418 419 420
   */
  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);
  }

  /**
421
   * {@inheritdoc}
422 423 424 425 426 427 428
   */
  public function clearCachedFieldDefinitions() {
    unset($this->entityFieldInfo);
    unset($this->fieldDefinitions);
    $this->cache->deleteTags(array('entity_field_info' => TRUE));
  }

429
  /**
430
   * {@inheritdoc}
431 432 433 434 435 436 437
   */
  public function getBundleInfo($entity_type) {
    $bundle_info = $this->getAllBundleInfo();
    return isset($bundle_info[$entity_type]) ? $bundle_info[$entity_type] : array();
  }

  /**
438
   * {@inheritdoc}
439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461
   */
  public function getAllBundleInfo() {
    if (!isset($this->bundleInfo)) {
      $langcode = $this->languageManager->getLanguage(Language::TYPE_INTERFACE)->id;
      if ($cache = $this->cache->get("entity_bundle_info:$langcode")) {
        $this->bundleInfo = $cache->data;
      }
      else {
        $this->bundleInfo = $this->moduleHandler->invokeAll('entity_bundle_info');
        // If no bundles are provided, use the entity type name and label.
        foreach ($this->getDefinitions() as $type => $entity_info) {
          if (!isset($this->bundleInfo[$type])) {
            $this->bundleInfo[$type][$type]['label'] = $entity_info['label'];
          }
        }
        $this->moduleHandler->alter('entity_bundle_info', $this->bundleInfo);
        $this->cache->set("entity_bundle_info:$langcode", $this->bundleInfo, CacheBackendInterface::CACHE_PERMANENT, array('entity_info' => TRUE));
      }
    }

    return $this->bundleInfo;
  }

462
  /**
463
   * {@inheritdoc}
464 465 466 467 468 469 470 471 472 473
   */
  public function getEntityTypeLabels() {
    $options = array();
    foreach ($this->getDefinitions() as $entity_type => $definition) {
      $options[$entity_type] = $definition['label'];
    }

    return $options;
  }

474
}