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