PageRenderTime 57ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/core/lib/Drupal/Core/Entity/EntityResolverManager.php

http://github.com/drupal/drupal
PHP | 242 lines | 124 code | 24 blank | 94 comment | 31 complexity | 03d730abda8ec549e745ea4107b60a63 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. <?php
  2. namespace Drupal\Core\Entity;
  3. use Drupal\Core\DependencyInjection\ClassResolverInterface;
  4. use Drupal\Core\DependencyInjection\DeprecatedServicePropertyTrait;
  5. use Symfony\Component\Routing\Route;
  6. /**
  7. * Sets the entity route parameter converter options automatically.
  8. *
  9. * If controllers of routes with route parameters, type-hint the parameters with
  10. * an entity interface, upcasting is done automatically.
  11. */
  12. class EntityResolverManager {
  13. use DeprecatedServicePropertyTrait;
  14. /**
  15. * {@inheritdoc}
  16. */
  17. protected $deprecatedProperties = ['entityManager' => 'entity.manager'];
  18. /**
  19. * The entity type manager service.
  20. *
  21. * @var \Drupal\Core\Entity\EntityTypeManagerInterface
  22. */
  23. protected $entityTypeManager;
  24. /**
  25. * The class resolver.
  26. *
  27. * @var \Drupal\Core\DependencyInjection\ClassResolverInterface
  28. */
  29. protected $classResolver;
  30. /**
  31. * Constructs a new EntityRouteAlterSubscriber.
  32. *
  33. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
  34. * The entity type manager service.
  35. * @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver
  36. * The class resolver.
  37. */
  38. public function __construct(EntityTypeManagerInterface $entity_type_manager, ClassResolverInterface $class_resolver) {
  39. if ($entity_type_manager instanceof EntityManagerInterface) {
  40. @trigger_error('Passing the entity.manager service to EntityResolverManager::__construct() is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. Pass the new dependencies instead. See https://www.drupal.org/node/2549139.', E_USER_DEPRECATED);
  41. $this->entityTypeManager = \Drupal::entityTypeManager();
  42. }
  43. else {
  44. $this->entityTypeManager = $entity_type_manager;
  45. }
  46. $this->classResolver = $class_resolver;
  47. }
  48. /**
  49. * Gets the controller class using route defaults.
  50. *
  51. * By design we cannot support all possible routes, but just the ones which
  52. * use the defaults provided by core, which are _controller and _form.
  53. *
  54. * Rather than creating an instance of every controller determine the class
  55. * and method that would be used. This is not possible for the service:method
  56. * notation as the runtime container does not allow static introspection.
  57. *
  58. * @see \Drupal\Core\Controller\ControllerResolver::getControllerFromDefinition()
  59. * @see \Drupal\Core\Controller\ClassResolver::getInstanceFromDefinition()
  60. *
  61. * @param array $defaults
  62. * The default values provided by the route.
  63. *
  64. * @return string|null
  65. * Returns the controller class, otherwise NULL.
  66. */
  67. protected function getControllerClass(array $defaults) {
  68. $controller = NULL;
  69. if (isset($defaults['_controller'])) {
  70. $controller = $defaults['_controller'];
  71. }
  72. if (isset($defaults['_form'])) {
  73. $controller = $defaults['_form'];
  74. // Check if the class exists and if so use the buildForm() method from the
  75. // interface.
  76. if (class_exists($controller)) {
  77. return [$controller, 'buildForm'];
  78. }
  79. }
  80. if (strpos($controller, ':') === FALSE) {
  81. if (method_exists($controller, '__invoke')) {
  82. return [$controller, '__invoke'];
  83. }
  84. if (function_exists($controller)) {
  85. return $controller;
  86. }
  87. return NULL;
  88. }
  89. $count = substr_count($controller, ':');
  90. if ($count == 1) {
  91. // Controller in the service:method notation. Get the information from the
  92. // service. This is dangerous as the controller could depend on services
  93. // that could not exist at this point. There is however no other way to
  94. // do it, as the container does not allow static introspection.
  95. list($class_or_service, $method) = explode(':', $controller, 2);
  96. return [$this->classResolver->getInstanceFromDefinition($class_or_service), $method];
  97. }
  98. elseif (strpos($controller, '::') !== FALSE) {
  99. // Controller in the class::method notation.
  100. return explode('::', $controller, 2);
  101. }
  102. return NULL;
  103. }
  104. /**
  105. * Sets the upcasting information using reflection.
  106. *
  107. * @param string|array $controller
  108. * A PHP callable representing the controller.
  109. * @param \Symfony\Component\Routing\Route $route
  110. * The route object to populate without upcasting information.
  111. *
  112. * @return bool
  113. * Returns TRUE if the upcasting parameters could be set, FALSE otherwise.
  114. */
  115. protected function setParametersFromReflection($controller, Route $route) {
  116. $entity_types = $this->getEntityTypes();
  117. $parameter_definitions = $route->getOption('parameters') ?: [];
  118. $result = FALSE;
  119. if (is_array($controller)) {
  120. list($instance, $method) = $controller;
  121. $reflection = new \ReflectionMethod($instance, $method);
  122. }
  123. else {
  124. $reflection = new \ReflectionFunction($controller);
  125. }
  126. $parameters = $reflection->getParameters();
  127. foreach ($parameters as $parameter) {
  128. $parameter_name = $parameter->getName();
  129. // If the parameter name matches with an entity type try to set the
  130. // upcasting information automatically. Therefore take into account that
  131. // the user has specified some interface, so the upcasting is intended.
  132. if (isset($entity_types[$parameter_name])) {
  133. $entity_type = $entity_types[$parameter_name];
  134. $entity_class = $entity_type->getClass();
  135. if (($reflection_class = $parameter->getClass()) && (is_subclass_of($entity_class, $reflection_class->name) || $entity_class == $reflection_class->name)) {
  136. $parameter_definitions += [$parameter_name => []];
  137. $parameter_definitions[$parameter_name] += [
  138. 'type' => 'entity:' . $parameter_name,
  139. ];
  140. $result = TRUE;
  141. }
  142. }
  143. }
  144. if (!empty($parameter_definitions)) {
  145. $route->setOption('parameters', $parameter_definitions);
  146. }
  147. return $result;
  148. }
  149. /**
  150. * Sets the upcasting information using the _entity_* route defaults.
  151. *
  152. * Supports the '_entity_view' and '_entity_form' route defaults.
  153. *
  154. * @param \Symfony\Component\Routing\Route $route
  155. * The route object.
  156. */
  157. protected function setParametersFromEntityInformation(Route $route) {
  158. if ($entity_view = $route->getDefault('_entity_view')) {
  159. list($entity_type) = explode('.', $entity_view, 2);
  160. }
  161. elseif ($entity_form = $route->getDefault('_entity_form')) {
  162. list($entity_type) = explode('.', $entity_form, 2);
  163. }
  164. // Do not add parameter information if the route does not declare a
  165. // parameter in the first place. This is the case for add forms, for
  166. // example.
  167. if (isset($entity_type) && isset($this->getEntityTypes()[$entity_type]) && (strpos($route->getPath(), '{' . $entity_type . '}') !== FALSE)) {
  168. $parameter_definitions = $route->getOption('parameters') ?: [];
  169. // First try to figure out whether there is already a parameter upcasting
  170. // the same entity type already.
  171. foreach ($parameter_definitions as $info) {
  172. if (isset($info['type']) && (strpos($info['type'], 'entity:') === 0)) {
  173. // The parameter types are in the form 'entity:$entity_type'.
  174. list(, $parameter_entity_type) = explode(':', $info['type'], 2);
  175. if ($parameter_entity_type == $entity_type) {
  176. return;
  177. }
  178. }
  179. }
  180. if (!isset($parameter_definitions[$entity_type])) {
  181. $parameter_definitions[$entity_type] = [];
  182. }
  183. $parameter_definitions[$entity_type] += [
  184. 'type' => 'entity:' . $entity_type,
  185. ];
  186. if (!empty($parameter_definitions)) {
  187. $route->setOption('parameters', $parameter_definitions);
  188. }
  189. }
  190. }
  191. /**
  192. * Set the upcasting route objects.
  193. *
  194. * @param \Symfony\Component\Routing\Route $route
  195. * The route object to add the upcasting information onto.
  196. */
  197. public function setRouteOptions(Route $route) {
  198. if ($controller = $this->getControllerClass($route->getDefaults())) {
  199. // Try to use reflection.
  200. if ($this->setParametersFromReflection($controller, $route)) {
  201. return;
  202. }
  203. }
  204. // Try to use _entity_* information on the route.
  205. $this->setParametersFromEntityInformation($route);
  206. }
  207. /**
  208. * Gets the list of all entity types.
  209. *
  210. * @return \Drupal\Core\Entity\EntityTypeInterface[]
  211. */
  212. protected function getEntityTypes() {
  213. if (!isset($this->entityTypes)) {
  214. $this->entityTypes = $this->entityTypeManager->getDefinitions();
  215. }
  216. return $this->entityTypes;
  217. }
  218. }