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