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

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

namespace Drupal\Core\Entity;

10
use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
11
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
12
use Drupal\Component\Plugin\PluginManagerBase;
13
use Drupal\Component\Utility\NestedArray;
14
use Drupal\Component\Utility\String;
15
use Drupal\Core\Field\FieldDefinition;
16
use Drupal\Core\Cache\Cache;
17
use Drupal\Core\Cache\CacheBackendInterface;
18 19
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Language\LanguageManager;
20
use Drupal\Core\Language\Language;
21
use Drupal\Core\Plugin\Discovery\AlterDecorator;
22
use Drupal\Core\Plugin\Discovery\CacheDecorator;
23
use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
24
use Drupal\Core\Plugin\Discovery\InfoHookDecorator;
25
use Drupal\Core\StringTranslation\TranslationInterface;
26
use Drupal\Core\TypedData\TranslatableInterface;
27
use Symfony\Component\DependencyInjection\ContainerInterface;
28 29 30 31

/**
 * Manages entity type plugin definitions.
 *
32
 * Each entity type definition array is set in the entity type's
33
 * annotation and altered by hook_entity_type_alter().
34 35
 *
 * The defaults for the plugin definition are provided in
36
 * \Drupal\Core\Entity\EntityManagerInterface::defaults.
37
 *
38 39
 * @see \Drupal\Core\Entity\Annotation\EntityType
 * @see \Drupal\Core\Entity\EntityInterface
40
 * @see \Drupal\Core\Entity\EntityTypeInterface
41
 * @see hook_entity_type_alter()
42
 */
43
class EntityManager extends PluginManagerBase implements EntityManagerInterface {
44

45 46 47 48 49 50 51
  /**
   * The injection container that should be passed into the controller factory.
   *
   * @var \Symfony\Component\DependencyInjection\ContainerInterface
   */
  protected $container;

52 53 54 55 56 57 58
  /**
   * Contains instantiated controllers keyed by controller type and entity type.
   *
   * @var array
   */
  protected $controllers = array();

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 91 92 93 94 95
  /**
   * 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;

96 97 98
  /**
   * The root paths.
   *
99
   * @see self::__construct().
100 101 102 103 104
   *
   * @var \Traversable
   */
  protected $namespaces;

105 106 107 108 109 110 111
  /**
   * The string translationManager.
   *
   * @var \Drupal\Core\StringTranslation\TranslationInterface
   */
  protected $translationManager;

112
  /**
113 114 115 116 117 118
   * Static cache of bundle information.
   *
   * @var array
   */
  protected $bundleInfo;

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

    $this->moduleHandler = $module_handler;
    $this->cache = $cache;
    $this->languageManager = $language_manager;
142
    $this->namespaces = $namespaces;
143
    $this->translationManager = $translation_manager;
144

145
    $this->discovery = new AnnotatedClassDiscovery('Entity', $namespaces, 'Drupal\Core\Entity\Annotation\EntityType');
146 147 148
    $this->discovery = new InfoHookDecorator($this->discovery, 'entity_type_build');
    $this->discovery = new AlterDecorator($this->discovery, 'entity_type');
    $this->discovery = new CacheDecorator($this->discovery, 'entity_type:' . $this->languageManager->getCurrentLanguage()->id, 'cache', Cache::PERMANENT, array('entity_types' => TRUE));
149

150
    $this->container = $container;
151 152
  }

153 154 155 156 157 158 159 160 161
  /**
   * {@inheritdoc}
   */
  public function clearCachedDefinitions() {
    parent::clearCachedDefinitions();

    $this->bundleInfo = NULL;
  }

162
  /**
163
   * {@inheritdoc}
164
   */
165 166 167
  public function getDefinition($entity_type_id, $exception_on_invalid = FALSE) {
    if (($entity_type = parent::getDefinition($entity_type_id)) && class_exists($entity_type->getClass())) {
      return $entity_type;
168
    }
169 170 171 172
    elseif (!$exception_on_invalid) {
      return NULL;
    }

173
    throw new PluginNotFoundException($entity_type_id, sprintf('The "%s" entity type does not exist.', $entity_type_id));
174 175
  }

176
  /**
177
   * {@inheritdoc}
178
   */
179 180 181
  public function hasController($entity_type, $controller_type) {
    if ($definition = $this->getDefinition($entity_type)) {
      return $definition->hasControllerClass($controller_type);
182
    }
183
    return FALSE;
184 185 186
  }

  /**
187
   * {@inheritdoc}
188 189
   */
  public function getStorageController($entity_type) {
190
    return $this->getController($entity_type, 'storage', 'getStorageClass');
191 192 193
  }

  /**
194
   * {@inheritdoc}
195 196
   */
  public function getListController($entity_type) {
197
    return $this->getController($entity_type, 'list', 'getListClass');
198 199 200
  }

  /**
201
   * {@inheritdoc}
202 203 204
   */
  public function getFormController($entity_type, $operation) {
    if (!isset($this->controllers['form'][$operation][$entity_type])) {
205 206 207
      if (!$class = $this->getDefinition($entity_type, TRUE)->getFormClass($operation)) {
        throw new InvalidPluginDefinitionException($entity_type, sprintf('The "%s" entity type did not specify a "%s" form class.', $entity_type, $operation));
      }
208
      if (in_array('Drupal\Core\DependencyInjection\ContainerInjectionInterface', class_implements($class))) {
209
        $controller = $class::create($this->container);
210 211
      }
      else {
212
        $controller = new $class();
213
      }
214 215 216 217 218 219

      $controller
        ->setTranslationManager($this->translationManager)
        ->setModuleHandler($this->moduleHandler)
        ->setOperation($operation);
      $this->controllers['form'][$operation][$entity_type] = $controller;
220 221 222 223 224
    }
    return $this->controllers['form'][$operation][$entity_type];
  }

  /**
225
   * {@inheritdoc}
226
   */
227
  public function getViewBuilder($entity_type) {
228
    return $this->getController($entity_type, 'view_builder', 'getViewBuilderClass');
229 230 231
  }

  /**
232
   * {@inheritdoc}
233 234
   */
  public function getAccessController($entity_type) {
235
    return $this->getController($entity_type, 'access', 'getAccessClass');
236 237 238 239 240 241
  }

  /**
   * Creates a new controller instance.
   *
   * @param string $entity_type
242
   *   The entity type for this controller.
243 244
   * @param string $controller_type
   *   The controller type to create an instance for.
245 246
   * @param string $controller_class_getter
   *   (optional) The method to call on the entity type object to get the controller class.
247
   *
248
   * @return mixed
249
   *   A controller instance.
250 251
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
252
   */
253
  public function getController($entity_type, $controller_type, $controller_class_getter = NULL) {
254
    if (!isset($this->controllers[$controller_type][$entity_type])) {
255 256 257
      $definition = $this->getDefinition($entity_type, TRUE);
      if ($controller_class_getter) {
        $class = $definition->{$controller_class_getter}();
258 259
      }
      else {
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
        $class = $definition->getControllerClass($controller_type);
      }
      if (!$class) {
        throw new InvalidPluginDefinitionException($entity_type, sprintf('The "%s" entity type did not specify a %s class.', $entity_type, $controller_type));
      }
      if (is_subclass_of($class, 'Drupal\Core\Entity\EntityControllerInterface')) {
        $controller = $class::createInstance($this->container, $definition);
      }
      else {
        $controller = new $class($definition);
      }
      if (method_exists($controller, 'setModuleHandler')) {
        $controller->setModuleHandler($this->moduleHandler);
      }
      if (method_exists($controller, 'setTranslationManager')) {
        $controller->setTranslationManager($this->translationManager);
276
      }
277
      $this->controllers[$controller_type][$entity_type] = $controller;
278
    }
279
    return $this->controllers[$controller_type][$entity_type];
280 281
  }

282
  /**
283
   * {@inheritdoc}
284
   */
285 286
  public function getAdminRouteInfo($entity_type_id, $bundle) {
    if (($entity_type = $this->getDefinition($entity_type_id)) && $admin_form = $entity_type->getLinkTemplate('admin-form')) {
287 288 289
      return array(
        'route_name' => $admin_form,
        'route_parameters' => array(
290
          $entity_type->getBundleEntityType() => $bundle,
291 292
        ),
      );
293
    }
294 295
  }

296
  /**
297
   * {@inheritdoc}
298
   */
299 300
  public function getFieldDefinitions($entity_type_id, $bundle = NULL) {
    if (!isset($this->entityFieldInfo[$entity_type_id])) {
301
      // First, try to load from cache.
302
      $cid = 'entity_field_definitions:' . $entity_type_id . ':' . $this->languageManager->getCurrentLanguage()->id;
303
      if ($cache = $this->cache->get($cid)) {
304
        $this->entityFieldInfo[$entity_type_id] = $cache->data;
305 306
      }
      else {
307 308
        // @todo: Refactor to allow for per-bundle overrides.
        // See https://drupal.org/node/2114707.
309 310
        $entity_type = $this->getDefinition($entity_type_id);
        $class = $entity_type->getClass();
311

312 313 314 315
        $base_definitions = $class::baseFieldDefinitions($entity_type_id);
        foreach ($base_definitions as &$base_definition) {
          $base_definition->setTargetEntityTypeId($entity_type_id);
        }
316
        $this->entityFieldInfo[$entity_type_id] = array(
317
          'definitions' => $base_definitions,
318 319 320 321 322 323 324 325
          // 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.
326 327 328 329
        $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);
330

331
        // Automatically set the field name for non-configurable fields.
332
        foreach (array('definitions', 'optional') as $key) {
333
          foreach ($this->entityFieldInfo[$entity_type_id][$key] as $field_name => &$definition) {
334
            if ($definition instanceof FieldDefinition) {
335
              $definition->setName($field_name);
336 337 338 339 340
            }
          }
        }

        // Invoke alter hooks.
341 342
        $hooks = array('entity_field_info', $entity_type_id . '_field_info');
        $this->moduleHandler->alter($hooks, $this->entityFieldInfo[$entity_type_id], $entity_type_id);
343

344
        // Ensure all basic fields are not defined as translatable.
345
        $keys = array_intersect_key(array_filter($entity_type->getKeys()), array_flip(array('id', 'revision', 'uuid', 'bundle')));
346 347
        $untranslatable_fields = array_flip(array('langcode') + $keys);
        foreach (array('definitions', 'optional') as $key) {
348
          foreach ($this->entityFieldInfo[$entity_type_id][$key] as $field_name => &$definition) {
349
            if (isset($untranslatable_fields[$field_name]) && $definition->isTranslatable()) {
350
              throw new \LogicException(String::format('The @field field cannot be translatable.', array('@field' => $definition->getLabel())));
351 352
            }
          }
353
        }
354

355
        $this->cache->set($cid, $this->entityFieldInfo[$entity_type_id], Cache::PERMANENT, array('entity_types' => TRUE, 'entity_field_info' => TRUE));
356 357 358 359
      }
    }

    if (!$bundle) {
360
      return $this->entityFieldInfo[$entity_type_id]['definitions'];
361 362 363
    }
    else {
      // Add in per-bundle fields.
364 365 366 367
      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]));
368 369
        }
      }
370
      return $this->fieldDefinitions[$entity_type_id][$bundle];
371 372 373 374
    }
  }

  /**
375
   * {@inheritdoc}
376 377 378 379
   */
  public function clearCachedFieldDefinitions() {
    unset($this->entityFieldInfo);
    unset($this->fieldDefinitions);
380
    Cache::deleteTags(array('entity_field_info' => TRUE));
381 382
  }

383
  /**
384
   * {@inheritdoc}
385 386 387 388 389 390 391
   */
  public function getBundleInfo($entity_type) {
    $bundle_info = $this->getAllBundleInfo();
    return isset($bundle_info[$entity_type]) ? $bundle_info[$entity_type] : array();
  }

  /**
392
   * {@inheritdoc}
393 394 395
   */
  public function getAllBundleInfo() {
    if (!isset($this->bundleInfo)) {
396
      $langcode = $this->languageManager->getCurrentLanguage()->id;
397 398 399 400 401 402
      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.
403
        foreach ($this->getDefinitions() as $type => $entity_type) {
404
          if (!isset($this->bundleInfo[$type])) {
405
            $this->bundleInfo[$type][$type]['label'] = $entity_type->getLabel();
406 407 408
          }
        }
        $this->moduleHandler->alter('entity_bundle_info', $this->bundleInfo);
409
        $this->cache->set("entity_bundle_info:$langcode", $this->bundleInfo, Cache::PERMANENT, array('entity_types' => TRUE));
410 411 412 413 414 415
      }
    }

    return $this->bundleInfo;
  }

416
  /**
417
   * {@inheritdoc}
418 419 420 421
   */
  public function getEntityTypeLabels() {
    $options = array();
    foreach ($this->getDefinitions() as $entity_type => $definition) {
422
      $options[$entity_type] = $definition->getLabel();
423 424 425 426 427
    }

    return $options;
  }

428 429 430 431 432 433 434 435
  /**
   * {@inheritdoc}
   */
  public function getTranslationFromContext(EntityInterface $entity, $langcode = NULL, $context = array()) {
    $translation = $entity;

    if ($entity instanceof TranslatableInterface) {
      if (empty($langcode)) {
436
        $langcode = $this->languageManager->getCurrentLanguage(Language::TYPE_CONTENT)->id;
437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460
      }

      // Retrieve language fallback candidates to perform the entity language
      // negotiation.
      $context['data'] = $entity;
      $context += array('operation' => 'entity_view');
      $candidates = $this->languageManager->getFallbackCandidates($langcode, $context);

      // Ensure the default language has the proper language code.
      $default_language = $entity->getUntranslated()->language();
      $candidates[$default_language->id] = Language::LANGCODE_DEFAULT;

      // Return the most fitting entity translation.
      foreach ($candidates as $candidate) {
        if ($entity->hasTranslation($candidate)) {
          $translation = $entity->getTranslation($candidate);
          break;
        }
      }
    }

    return $translation;
  }

461
}