Commit d63c8fae authored by webchick's avatar webchick

Issue #2168333 by tim.plunkett, EclipseGc, yched, fago: Ensure custom...

Issue #2168333 by tim.plunkett, EclipseGc, yched, fago: Ensure custom EntityType controllers can be provided by modules.
parent 8b0cb059
......@@ -2,17 +2,15 @@
/**
* @file
* Contains \Drupal\config_translation\Exception\InvalidMapperDefinitionException.
* Contains \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException.
*/
namespace Drupal\config_translation\Exception;
use Drupal\Component\Plugin\Exception\PluginException;
namespace Drupal\Component\Plugin\Exception;
/**
* Defines a class for invalid configuration mapper definition exceptions.
* Defines a class for invalid plugin definition exceptions.
*/
class InvalidMapperDefinitionException extends PluginException {
class InvalidPluginDefinitionException extends PluginException {
/**
* The plugin ID of the mapper.
......@@ -22,7 +20,7 @@ class InvalidMapperDefinitionException extends PluginException {
protected $pluginId;
/**
* Constructs a InvalidMapperDefinitionException.
* Constructs a InvalidPluginDefinitionException.
*
* @param string $plugin_id
* The plugin ID of the mapper.
......
......@@ -10,7 +10,6 @@
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityStorageControllerInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormInterface;
/**
......@@ -49,8 +48,8 @@ abstract class DraggableListController extends ConfigEntityListController implem
/**
* {@inheritdoc}
*/
public function __construct(EntityTypeInterface $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler) {
parent::__construct($entity_info, $storage, $module_handler);
public function __construct(EntityTypeInterface $entity_info, EntityStorageControllerInterface $storage) {
parent::__construct($entity_info, $storage);
// Check if the entity type supports weighting.
if ($this->entityInfo->hasKey('weight')) {
......
......@@ -17,7 +17,7 @@
/**
* Defines a default implementation for entity access controllers.
*/
class EntityAccessController implements EntityAccessControllerInterface {
class EntityAccessController extends EntityControllerBase implements EntityAccessControllerInterface {
/**
* Stores calculcated access check results.
......@@ -40,13 +40,6 @@ class EntityAccessController implements EntityAccessControllerInterface {
*/
protected $entityInfo;
/**
* The module handler service.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* Constructs an access controller instance.
*
......@@ -79,8 +72,8 @@ public function access(EntityInterface $entity, $operation, $langcode = Language
// - No modules say to deny access.
// - At least one module says to grant access.
$access = array_merge(
$this->moduleHandler->invokeAll('entity_access', array($entity, $operation, $account, $langcode)),
$this->moduleHandler->invokeAll($entity->entityType() . '_access', array($entity, $operation, $account, $langcode))
$this->moduleHandler()->invokeAll('entity_access', array($entity, $operation, $account, $langcode)),
$this->moduleHandler()->invokeAll($entity->entityType() . '_access', array($entity, $operation, $account, $langcode))
);
if (($return = $this->processAccessHookResults($access)) === NULL) {
......@@ -226,8 +219,8 @@ public function createAccess($entity_bundle = NULL, AccountInterface $account =
// - No modules say to deny access.
// - At least one module says to grant access.
$access = array_merge(
$this->moduleHandler->invokeAll('entity_create_access', array($account, $context['langcode'])),
$this->moduleHandler->invokeAll($this->entityType . '_create_access', array($account, $context['langcode']))
$this->moduleHandler()->invokeAll('entity_create_access', array($account, $context['langcode'])),
$this->moduleHandler()->invokeAll($this->entityType . '_create_access', array($account, $context['langcode']))
);
if (($return = $this->processAccessHookResults($access)) === NULL) {
......@@ -281,14 +274,6 @@ protected function prepareUser(AccountInterface $account = NULL) {
return $account;
}
/**
* {@inheritdoc}
*/
public function setModuleHandler(ModuleHandlerInterface $module_handler) {
$this->moduleHandler = $module_handler;
return $this;
}
/**
* {@inheritdoc}
*/
......@@ -301,9 +286,9 @@ public function fieldAccess($operation, FieldDefinitionInterface $field_definiti
// Invoke hook and collect grants/denies for field access from other
// modules. Our default access flag is masked under the ':default' key.
$grants = array(':default' => $default);
$hook_implementations = $this->moduleHandler->getImplementations('entity_field_access');
$hook_implementations = $this->moduleHandler()->getImplementations('entity_field_access');
foreach ($hook_implementations as $module) {
$grants = array_merge($grants, array($module => $this->moduleHandler->invoke($module, 'entity_field_access', array($operation, $field_definition, $account, $items))));
$grants = array_merge($grants, array($module => $this->moduleHandler()->invoke($module, 'entity_field_access', array($operation, $field_definition, $account, $items))));
}
// Also allow modules to alter the returned grants/denies.
......@@ -313,7 +298,7 @@ public function fieldAccess($operation, FieldDefinitionInterface $field_definiti
'items' => $items,
'account' => $account,
);
$this->moduleHandler->alter('entity_field_access', $grants, $context);
$this->moduleHandler()->alter('entity_field_access', $grants, $context);
// One grant being FALSE is enough to deny access immediately.
if (in_array(FALSE, $grants, TRUE)) {
......
......@@ -62,13 +62,12 @@ public function createAccess($entity_bundle = NULL, AccountInterface $account =
public function resetCache();
/**
* Sets the module handler for this form.
* Sets the module handler for this access controller.
*
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
*
* @return self
* The entity access controller.
* @return $this
*/
public function setModuleHandler(ModuleHandlerInterface $module_handler);
......
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityControllerBase.
*/
namespace Drupal\Core\Entity;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\StringTranslation\TranslationInterface;
/**
* Provides a base class for entity controllers.
*
* @todo Convert this to a trait.
*/
abstract class EntityControllerBase {
/**
* The translation manager service.
*
* @var \Drupal\Core\StringTranslation\TranslationInterface
*/
protected $translationManager;
/**
* The module handler to invoke hooks on.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* Gets the translation manager.
*
* @return \Drupal\Core\StringTranslation\TranslationInterface
* The translation manager.
*/
protected function translationManager() {
if (!$this->translationManager) {
$this->translationManager = \Drupal::translation();
}
return $this->translationManager;
}
/**
* Sets the translation manager for this controller.
*
* @param \Drupal\Core\StringTranslation\TranslationInterface $translation_manager
* The translation manager.
*
* @return $this
*/
public function setTranslationManager(TranslationInterface $translation_manager) {
$this->translationManager = $translation_manager;
return $this;
}
/**
* Returns the module handler.
*
* @return \Drupal\Core\Extension\ModuleHandlerInterface
* The module handler.
*/
protected function moduleHandler() {
if (!$this->moduleHandler) {
$this->moduleHandler = \Drupal::moduleHandler();
}
return $this->moduleHandler;
}
/**
* Sets the module handler for this controller.
*
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
*
* @return $this
*/
public function setModuleHandler(ModuleHandlerInterface $module_handler) {
$this->moduleHandler = $module_handler;
return $this;
}
}
......@@ -45,8 +45,7 @@ public function isDefaultFormLangcode(array $form_state);
* @param string $operation
* The name of the current operation.
*
* @return self
* The entity form.
* @return $this
*/
public function setOperation($operation);
......@@ -106,6 +105,8 @@ public function getEntity();
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity the current form should operate upon.
*
* @return $this
*/
public function setEntity(EntityInterface $entity);
......@@ -158,8 +159,7 @@ public function submit(array $form, array &$form_state);
* @param \Drupal\Core\StringTranslation\TranslationInterface $translation_manager
* The translation manager.
*
* @return self
* The entity form.
* @return $this
*/
public function setTranslationManager(TranslationInterface $translation_manager);
......@@ -169,8 +169,7 @@ public function setTranslationManager(TranslationInterface $translation_manager)
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
*
* @return self
* The entity form.
* @return $this
*/
public function setModuleHandler(ModuleHandlerInterface $module_handler);
......
......@@ -7,7 +7,6 @@
namespace Drupal\Core\Entity;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\StringTranslation\TranslationInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
......@@ -16,7 +15,7 @@
/**
* Provides a generic implementation of an entity list controller.
*/
class EntityListController implements EntityListControllerInterface, EntityControllerInterface {
class EntityListController extends EntityControllerBase implements EntityListControllerInterface, EntityControllerInterface {
/**
* The entity storage controller class.
......@@ -25,13 +24,6 @@ class EntityListController implements EntityListControllerInterface, EntityContr
*/
protected $storage;
/**
* The module handler to invoke hooks on.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The entity type name.
*
......@@ -46,21 +38,13 @@ class EntityListController implements EntityListControllerInterface, EntityContr
*/
protected $entityInfo;
/**
* The translation manager service.
*
* @var \Drupal\Core\StringTranslation\TranslationInterface
*/
protected $translationManager;
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_info) {
return new static(
$entity_info,
$container->get('entity.manager')->getStorageController($entity_info->id()),
$container->get('module_handler')
$container->get('entity.manager')->getStorageController($entity_info->id())
);
}
......@@ -71,14 +55,11 @@ public static function createInstance(ContainerInterface $container, EntityTypeI
* The entity info for the entity type.
* @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage
* The entity storage controller class.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler to invoke hooks on.
*/
public function __construct(EntityTypeInterface $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler) {
public function __construct(EntityTypeInterface $entity_info, EntityStorageControllerInterface $storage) {
$this->entityType = $entity_info->id();
$this->storage = $storage;
$this->entityInfo = $entity_info;
$this->moduleHandler = $module_handler;
}
/**
......@@ -178,7 +159,7 @@ public function buildRow(EntityInterface $entity) {
public function buildOperations(EntityInterface $entity) {
// Retrieve and sort operations.
$operations = $this->getOperations($entity);
$this->moduleHandler->alter('entity_operation', $operations, $entity);
$this->moduleHandler()->alter('entity_operation', $operations, $entity);
uasort($operations, 'drupal_sort_weight');
$build = array(
'#type' => 'operations',
......@@ -219,33 +200,6 @@ protected function t($string, array $args = array(), array $options = array()) {
return $this->translationManager()->translate($string, $args, $options);
}
/**
* Gets the translation manager.
*
* @return \Drupal\Core\StringTranslation\TranslationInterface
* The translation manager.
*/
protected function translationManager() {
if (!$this->translationManager) {
$this->translationManager = \Drupal::translation();
}
return $this->translationManager;
}
/**
* Sets the translation manager for this form.
*
* @param \Drupal\Core\StringTranslation\TranslationInterface $translation_manager
* The translation manager.
*
* @return self
* The entity form.
*/
public function setTranslationManager(TranslationInterface $translation_manager) {
$this->translationManager = $translation_manager;
return $this;
}
/**
* Returns the title of the page.
*
......
......@@ -7,9 +7,11 @@
namespace Drupal\Core\Entity;
use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
use Drupal\Component\Plugin\Exception\UnknownPluginException;
use Drupal\Component\Plugin\PluginManagerBase;
use Drupal\Component\Plugin\Factory\DefaultFactory;
use Drupal\Component\Utility\NestedArray;
use Drupal\Component\Utility\String;
use Drupal\Core\Field\FieldDefinition;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
......@@ -143,7 +145,6 @@ public function __construct(\Traversable $namespaces, ContainerInterface $contai
$this->discovery = new InfoHookDecorator($this->discovery, 'entity_info');
$this->discovery = new AlterDecorator($this->discovery, 'entity_info');
$this->discovery = new CacheDecorator($this->discovery, 'entity_info:' . $this->languageManager->getCurrentLanguage()->id, 'cache', CacheBackendInterface::CACHE_PERMANENT, array('entity_info' => TRUE));
$this->factory = new DefaultFactory($this->discovery);
$this->container = $container;
}
......@@ -159,65 +160,39 @@ public function clearCachedDefinitions() {
/**
* {@inheritdoc}
*/
public function hasController($entity_type, $controller_type) {
if ($definition = $this->getDefinition($entity_type)) {
return $definition->hasController($controller_type);
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;
}
return FALSE;
elseif (!$exception_on_invalid) {
return NULL;
}
throw new UnknownPluginException($entity_type_id, sprintf('The "%s" entity type does not exist.', $entity_type_id));
}
/**
* {@inheritdoc}
*/
public function getControllerClass($entity_type, $controller_type, $nested = NULL) {
$info = $this->getDefinition($entity_type);
if (!$info) {
throw new \InvalidArgumentException(sprintf('The %s entity type does not exist.', $entity_type));
}
if (!$info->hasController($controller_type)) {
throw new \InvalidArgumentException(sprintf('The entity type (%s) did not specify a %s controller.', $entity_type, $controller_type));
}
$class = $info->getController($controller_type);
// Some class definitions can be nested.
if (isset($nested)) {
if (empty($class[$nested])) {
throw new \InvalidArgumentException(sprintf("The entity type (%s) did not specify a %s controller: %s.", $entity_type, $controller_type, $nested));
}
$class = $class[$nested];
}
if (!class_exists($class)) {
throw new \InvalidArgumentException(sprintf('The entity type (%s) %s controller "%s" does not exist.', $entity_type, $controller_type, $class));
public function hasController($entity_type, $controller_type) {
if ($definition = $this->getDefinition($entity_type)) {
return $definition->hasControllerClass($controller_type);
}
return $class;
return FALSE;
}
/**
* {@inheritdoc}
*/
public function getStorageController($entity_type) {
return $this->getController($entity_type, 'storage');
return $this->getController($entity_type, 'storage', 'getStorageClass');
}
/**
* {@inheritdoc}
*/
public function getListController($entity_type) {
if (!isset($this->controllers['listing'][$entity_type])) {
$class = $this->getControllerClass($entity_type, 'list');
if (in_array('Drupal\Core\Entity\EntityControllerInterface', class_implements($class))) {
$this->controllers['listing'][$entity_type] = $class::createInstance($this->container, $this->getDefinition($entity_type));
}
else {
$this->controllers['listing'][$entity_type] = new $class($entity_type, $this->getStorageController($entity_type));
}
}
return $this->controllers['listing'][$entity_type];
return $this->getController($entity_type, 'list', 'getListClass');
}
/**
......@@ -225,7 +200,9 @@ public function getListController($entity_type) {
*/
public function getFormController($entity_type, $operation) {
if (!isset($this->controllers['form'][$operation][$entity_type])) {
$class = $this->getControllerClass($entity_type, 'form', $operation);
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));
}
if (in_array('Drupal\Core\DependencyInjection\ContainerInjectionInterface', class_implements($class))) {
$controller = $class::create($this->container);
}
......@@ -246,40 +223,56 @@ public function getFormController($entity_type, $operation) {
* {@inheritdoc}
*/
public function getViewBuilder($entity_type) {
return $this->getController($entity_type, 'view_builder');
return $this->getController($entity_type, 'view_builder', 'getViewBuilderClass');
}
/**
* {@inheritdoc}
*/
public function getAccessController($entity_type) {
if (!isset($this->controllers['access'][$entity_type])) {
$controller = $this->getController($entity_type, 'access');
$controller->setModuleHandler($this->moduleHandler);
}
return $this->controllers['access'][$entity_type];
return $this->getController($entity_type, 'access', 'getAccessClass');
}
/**
* Creates a new controller instance.
*
* @param string $entity_type
* The entity type for this access controller.
* The entity type for this controller.
* @param string $controller_type
* The controller type to create an instance for.
* @param string $controller_class_getter
* (optional) The method to call on the entity type object to get the controller class.
*
* @return mixed.
* @return mixed
* A controller instance.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
*/
protected function getController($entity_type, $controller_type) {
public function getController($entity_type, $controller_type, $controller_class_getter = NULL) {
if (!isset($this->controllers[$controller_type][$entity_type])) {
$class = $this->getControllerClass($entity_type, $controller_type);
if (in_array('Drupal\Core\Entity\EntityControllerInterface', class_implements($class))) {
$this->controllers[$controller_type][$entity_type] = $class::createInstance($this->container, $this->getDefinition($entity_type));
$definition = $this->getDefinition($entity_type, TRUE);
if ($controller_class_getter) {
$class = $definition->{$controller_class_getter}();
}
else {
$this->controllers[$controller_type][$entity_type] = new $class($this->getDefinition($entity_type));
$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);
}
$this->controllers[$controller_type][$entity_type] = $controller;
}
return $this->controllers[$controller_type][$entity_type];
}
......@@ -304,8 +297,7 @@ public function getForm(EntityInterface $entity, $operation = 'default', array $
* {@inheritdoc}
*/
public function getAdminRouteInfo($entity_type, $bundle) {
$entity_info = $this->getDefinition($entity_type);
if ($admin_form = $entity_info->getLinkTemplate('admin-form')) {
if (($entity_info = $this->getDefinition($entity_type)) && $admin_form = $entity_info->getLinkTemplate('admin-form')) {
return array(
'route_name' => $admin_form,
'route_parameters' => array(
......@@ -329,8 +321,7 @@ public function getFieldDefinitions($entity_type, $bundle = NULL) {
// @todo: Refactor to allow for per-bundle overrides.
// See https://drupal.org/node/2114707.
$entity_info = $this->getDefinition($entity_type);
$definition = array('class' => $entity_info->getClass());
$class = $this->factory->getPluginClass($entity_type, $definition);
$class = $entity_info->getClass();
$this->entityFieldInfo[$entity_type] = array(
'definitions' => $class::baseFieldDefinitions($entity_type),
......@@ -366,7 +357,7 @@ public function getFieldDefinitions($entity_type, $bundle = NULL) {
foreach (array('definitions', 'optional') as $key) {
foreach ($this->entityFieldInfo[$entity_type][$key] as $field_name => &$definition) {
if (isset($untranslatable_fields[$field_name]) && $definition->isTranslatable()) {
throw new \LogicException(format_string('The @field field cannot be translatable.', array('@field' => $definition->getLabel())));
throw new \LogicException(String::format('The @field field cannot be translatable.', array('@field' => $definition->getLabel())));
}
}
}
......
......@@ -104,22 +104,6 @@ public function getFieldDefinitionsByConstraints($entity_type, array $constraint
*/
public function getStorageController($entity_type);
/**
* Returns an entity controller class.
*
* @param string $entity_type
* The name of the entity type
* @param string $controller_type
* The name of the controller.
* @param string|null $nested
* (optional) If this controller definition is nested, the name of the key.
* Defaults to NULL.
*
* @return string
* The class name for this controller instance.
*/
public function getControllerClass($entity_type, $controller_type, $nested = NULL);
/**
* Get the bundle info of all entity types.
*
......@@ -186,6 +170,21 @@ public function clearCachedFieldDefinitions();
*/
public function hasController($entity_type, $controller_type);
/**
* Creates a new controller instance.
*
* @param string $entity_type
* The entity type for this controller.
* @param string $controller_type
* The controller type to create an instance for.
*
* @return mixed
* A controller instance.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
*/
public function getController($entity_type, $controller_type);
/**
* Returns the built and processed entity form for the given entity.
*
......@@ -247,13 +246,20 @@ public function getTranslationFromContext(EntityInterface $entity, $langcode = N
/**
* Returns the entity type info for a specific entity type.
*
* @param string $entity_type_name
* The name of the entity type.
* @param string $entity_type_id
* The ID of the entity type.
* @param bool $exception_on_invalid
* (optional) If TRUE, an invalid entity type ID will throw an exception.
* Defaults to FALSE.
*
* @return \Drupal\Core\Entity\EntityTypeInterface|null
* Either the entity type object, or NULL if the name does not exist.
* Returns the entity type object, or NULL if the entity type ID is invalid
* and $exception_on_invalid is TRUE.
*
* @throws \InvalidArgumentException
* Thrown if $entity_type_id is invalid and $exception_on_invalid is TRUE.
*/
public function getDefinition($entity_type_name);
public function getDefinition($entity_type_id, $exception_on_invalid = FALSE);
/**
* Returns an array of entity type info, keyed by entity type name.
......
......@@ -7,11 +7,12 @@
namespace Drupal\Core\Entity;
use Drupal\Core\Entity\Query\QueryInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
/**
* A base entity storage controller class.
*/
abstract class EntityStorageControllerBase implements EntityStorageControllerInterface, EntityControllerInterface {
abstract class EntityStorageControllerBase extends EntityControllerBase implements EntityStorageControllerInterface, EntityControllerInterface {
/**
* Static cache of entities.
......@@ -149,9 +150,9 @@ protected function cacheSet($entities) {
*/
protected function invokeHook($hook, EntityInterface $entity) {
// Invoke the hook.
module_invoke_all($this->entityType . '_' . $hook, $entity);
$this->moduleHandler()->invokeAll($this->entityType . '_' . $hook, array($entity));
// Invoke the respective entity-level hook.
module_invoke_all('entity_' . $hook, $entity, $this->entityType);
$this->moduleHandler()->invokeAll('entity_' . $hook, array($entity, $this->entityType));
}
/**
......@@ -164,12 +165,12 @@ protected function postLoad(array &$queried_entities) {
$entity_class = $this->entityInfo->getClass();