Chris@0: 'entity.manager']; Chris@18: Chris@18: /** Chris@18: * The entity type manager service. Chris@0: * Chris@18: * @var \Drupal\Core\Entity\EntityTypeManagerInterface Chris@0: */ Chris@18: protected $entityTypeManager; Chris@0: Chris@0: /** Chris@0: * The class resolver. Chris@0: * Chris@0: * @var \Drupal\Core\DependencyInjection\ClassResolverInterface Chris@0: */ Chris@0: protected $classResolver; Chris@0: Chris@0: /** Chris@0: * Constructs a new EntityRouteAlterSubscriber. Chris@0: * Chris@18: * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager Chris@18: * The entity type manager service. Chris@0: * @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver Chris@0: * The class resolver. Chris@0: */ Chris@18: public function __construct(EntityTypeManagerInterface $entity_type_manager, ClassResolverInterface $class_resolver) { Chris@18: if ($entity_type_manager instanceof EntityManagerInterface) { Chris@18: @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: $this->entityTypeManager = \Drupal::entityTypeManager(); Chris@18: } Chris@18: else { Chris@18: $this->entityTypeManager = $entity_type_manager; Chris@18: } Chris@0: $this->classResolver = $class_resolver; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets the controller class using route defaults. Chris@0: * Chris@0: * By design we cannot support all possible routes, but just the ones which Chris@0: * use the defaults provided by core, which are _controller and _form. Chris@0: * Chris@0: * Rather than creating an instance of every controller determine the class Chris@0: * and method that would be used. This is not possible for the service:method Chris@0: * notation as the runtime container does not allow static introspection. Chris@0: * Chris@0: * @see \Drupal\Core\Controller\ControllerResolver::getControllerFromDefinition() Chris@0: * @see \Drupal\Core\Controller\ClassResolver::getInstanceFromDefinition() Chris@0: * Chris@0: * @param array $defaults Chris@0: * The default values provided by the route. Chris@0: * Chris@0: * @return string|null Chris@0: * Returns the controller class, otherwise NULL. Chris@0: */ Chris@0: protected function getControllerClass(array $defaults) { Chris@0: $controller = NULL; Chris@0: if (isset($defaults['_controller'])) { Chris@0: $controller = $defaults['_controller']; Chris@0: } Chris@0: Chris@0: if (isset($defaults['_form'])) { Chris@0: $controller = $defaults['_form']; Chris@0: // Check if the class exists and if so use the buildForm() method from the Chris@0: // interface. Chris@0: if (class_exists($controller)) { Chris@0: return [$controller, 'buildForm']; Chris@0: } Chris@0: } Chris@0: Chris@0: if (strpos($controller, ':') === FALSE) { Chris@0: if (method_exists($controller, '__invoke')) { Chris@0: return [$controller, '__invoke']; Chris@0: } Chris@0: if (function_exists($controller)) { Chris@0: return $controller; Chris@0: } Chris@0: return NULL; Chris@0: } Chris@0: Chris@0: $count = substr_count($controller, ':'); Chris@0: if ($count == 1) { Chris@0: // Controller in the service:method notation. Get the information from the Chris@0: // service. This is dangerous as the controller could depend on services Chris@0: // that could not exist at this point. There is however no other way to Chris@0: // do it, as the container does not allow static introspection. Chris@0: list($class_or_service, $method) = explode(':', $controller, 2); Chris@0: return [$this->classResolver->getInstanceFromDefinition($class_or_service), $method]; Chris@0: } Chris@0: elseif (strpos($controller, '::') !== FALSE) { Chris@0: // Controller in the class::method notation. Chris@0: return explode('::', $controller, 2); Chris@0: } Chris@0: Chris@0: return NULL; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets the upcasting information using reflection. Chris@0: * Chris@0: * @param string|array $controller Chris@0: * A PHP callable representing the controller. Chris@0: * @param \Symfony\Component\Routing\Route $route Chris@0: * The route object to populate without upcasting information. Chris@0: * Chris@0: * @return bool Chris@0: * Returns TRUE if the upcasting parameters could be set, FALSE otherwise. Chris@0: */ Chris@0: protected function setParametersFromReflection($controller, Route $route) { Chris@0: $entity_types = $this->getEntityTypes(); Chris@0: $parameter_definitions = $route->getOption('parameters') ?: []; Chris@0: Chris@0: $result = FALSE; Chris@0: Chris@0: if (is_array($controller)) { Chris@0: list($instance, $method) = $controller; Chris@0: $reflection = new \ReflectionMethod($instance, $method); Chris@0: } Chris@0: else { Chris@0: $reflection = new \ReflectionFunction($controller); Chris@0: } Chris@0: Chris@0: $parameters = $reflection->getParameters(); Chris@0: foreach ($parameters as $parameter) { Chris@0: $parameter_name = $parameter->getName(); Chris@0: // If the parameter name matches with an entity type try to set the Chris@0: // upcasting information automatically. Therefore take into account that Chris@0: // the user has specified some interface, so the upcasting is intended. Chris@0: if (isset($entity_types[$parameter_name])) { Chris@0: $entity_type = $entity_types[$parameter_name]; Chris@0: $entity_class = $entity_type->getClass(); Chris@0: if (($reflection_class = $parameter->getClass()) && (is_subclass_of($entity_class, $reflection_class->name) || $entity_class == $reflection_class->name)) { Chris@0: $parameter_definitions += [$parameter_name => []]; Chris@0: $parameter_definitions[$parameter_name] += [ Chris@0: 'type' => 'entity:' . $parameter_name, Chris@0: ]; Chris@0: $result = TRUE; Chris@0: } Chris@0: } Chris@0: } Chris@0: if (!empty($parameter_definitions)) { Chris@0: $route->setOption('parameters', $parameter_definitions); Chris@0: } Chris@0: return $result; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets the upcasting information using the _entity_* route defaults. Chris@0: * Chris@0: * Supports the '_entity_view' and '_entity_form' route defaults. Chris@0: * Chris@0: * @param \Symfony\Component\Routing\Route $route Chris@0: * The route object. Chris@0: */ Chris@0: protected function setParametersFromEntityInformation(Route $route) { Chris@0: if ($entity_view = $route->getDefault('_entity_view')) { Chris@0: list($entity_type) = explode('.', $entity_view, 2); Chris@0: } Chris@0: elseif ($entity_form = $route->getDefault('_entity_form')) { Chris@0: list($entity_type) = explode('.', $entity_form, 2); Chris@0: } Chris@0: Chris@0: // Do not add parameter information if the route does not declare a Chris@0: // parameter in the first place. This is the case for add forms, for Chris@0: // example. Chris@0: if (isset($entity_type) && isset($this->getEntityTypes()[$entity_type]) && (strpos($route->getPath(), '{' . $entity_type . '}') !== FALSE)) { Chris@0: $parameter_definitions = $route->getOption('parameters') ?: []; Chris@0: Chris@0: // First try to figure out whether there is already a parameter upcasting Chris@0: // the same entity type already. Chris@0: foreach ($parameter_definitions as $info) { Chris@0: if (isset($info['type']) && (strpos($info['type'], 'entity:') === 0)) { Chris@0: // The parameter types are in the form 'entity:$entity_type'. Chris@0: list(, $parameter_entity_type) = explode(':', $info['type'], 2); Chris@0: if ($parameter_entity_type == $entity_type) { Chris@0: return; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: if (!isset($parameter_definitions[$entity_type])) { Chris@0: $parameter_definitions[$entity_type] = []; Chris@0: } Chris@0: $parameter_definitions[$entity_type] += [ Chris@0: 'type' => 'entity:' . $entity_type, Chris@0: ]; Chris@0: if (!empty($parameter_definitions)) { Chris@0: $route->setOption('parameters', $parameter_definitions); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Set the upcasting route objects. Chris@0: * Chris@0: * @param \Symfony\Component\Routing\Route $route Chris@0: * The route object to add the upcasting information onto. Chris@0: */ Chris@0: public function setRouteOptions(Route $route) { Chris@0: if ($controller = $this->getControllerClass($route->getDefaults())) { Chris@0: // Try to use reflection. Chris@0: if ($this->setParametersFromReflection($controller, $route)) { Chris@0: return; Chris@0: } Chris@0: } Chris@0: Chris@0: // Try to use _entity_* information on the route. Chris@0: $this->setParametersFromEntityInformation($route); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets the list of all entity types. Chris@0: * Chris@0: * @return \Drupal\Core\Entity\EntityTypeInterface[] Chris@0: */ Chris@0: protected function getEntityTypes() { Chris@0: if (!isset($this->entityTypes)) { Chris@18: $this->entityTypes = $this->entityTypeManager->getDefinitions(); Chris@0: } Chris@0: return $this->entityTypes; Chris@0: } Chris@0: Chris@0: }