class EntityResolverManager

Same name and namespace in other branches
  1. 9 core/lib/Drupal/Core/Entity/EntityResolverManager.php \Drupal\Core\Entity\EntityResolverManager
  2. 8.9.x core/lib/Drupal/Core/Entity/EntityResolverManager.php \Drupal\Core\Entity\EntityResolverManager
  3. 10 core/lib/Drupal/Core/Entity/EntityResolverManager.php \Drupal\Core\Entity\EntityResolverManager

Sets the entity route parameter converter options automatically.

If controllers of routes with route parameters, type-hint the parameters with an entity interface, upcasting is done automatically.

Hierarchy

Expanded class hierarchy of EntityResolverManager

2 files declare their use of EntityResolverManager
EntityResolverManagerTest.php in core/tests/Drupal/Tests/Core/Entity/EntityResolverManagerTest.php
EntityRouteAlterSubscriber.php in core/lib/Drupal/Core/EventSubscriber/EntityRouteAlterSubscriber.php

File

core/lib/Drupal/Core/Entity/EntityResolverManager.php, line 15

Namespace

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

}

Members

Title Sort descending Modifiers Object type Summary
EntityResolverManager::$classResolver protected property The class resolver.
EntityResolverManager::$entityTypeManager protected property The entity type manager service.
EntityResolverManager::$entityTypes protected property The list of all entity types.
EntityResolverManager::getControllerClass protected function Gets the controller class using route defaults.
EntityResolverManager::getEntityTypes protected function Gets the list of all entity types.
EntityResolverManager::setParametersFromEntityInformation protected function Sets the upcasting information using the _entity_* route defaults.
EntityResolverManager::setParametersFromReflection protected function Sets the upcasting information using reflection.
EntityResolverManager::setRouteOptions public function Set the upcasting route objects.
EntityResolverManager::__construct public function Constructs a new EntityRouteAlterSubscriber.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.