Mercurial > hg > isophonics-drupal-site
diff core/lib/Drupal/Core/Entity/EntityResolverManager.php @ 0:4c8ae668cc8c
Initial import (non-working)
author | Chris Cannam |
---|---|
date | Wed, 29 Nov 2017 16:09:58 +0000 |
parents | |
children | af1871eacc83 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/lib/Drupal/Core/Entity/EntityResolverManager.php Wed Nov 29 16:09:58 2017 +0000 @@ -0,0 +1,229 @@ +<?php + +namespace Drupal\Core\Entity; + +use Drupal\Core\DependencyInjection\ClassResolverInterface; +use Symfony\Component\Routing\Route; + +/** + * 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. + */ +class EntityResolverManager { + + /** + * The entity manager. + * + * @var \Drupal\Core\Entity\EntityManagerInterface + */ + protected $entityManager; + + /** + * The class resolver. + * + * @var \Drupal\Core\DependencyInjection\ClassResolverInterface + */ + protected $classResolver; + + /** + * Constructs a new EntityRouteAlterSubscriber. + * + * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager + * The entity manager. + * @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver + * The class resolver. + */ + public function __construct(EntityManagerInterface $entity_manager, ClassResolverInterface $class_resolver) { + $this->entityManager = $entity_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 (strpos($controller, ':') === FALSE) { + 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. + list($class_or_service, $method) = explode(':', $controller, 2); + return [$this->classResolver->getInstanceFromDefinition($class_or_service), $method]; + } + elseif (strpos($controller, '::') !== FALSE) { + // 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)) { + list($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(); + if (($reflection_class = $parameter->getClass()) && (is_subclass_of($entity_class, $reflection_class->name) || $entity_class == $reflection_class->name)) { + $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')) { + list($entity_type) = explode('.', $entity_view, 2); + } + elseif ($entity_form = $route->getDefault('_entity_form')) { + list($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]) && (strpos($route->getPath(), '{' . $entity_type . '}') !== FALSE)) { + $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']) && (strpos($info['type'], 'entity:') === 0)) { + // The parameter types are in the form 'entity:$entity_type'. + list(, $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->entityManager->getDefinitions(); + } + return $this->entityTypes; + } + +}