Commit 1601b088 authored by alexpott's avatar alexpott

Issue #2350503 by dawehner: Add route generation handlers for entities

parent d7e814a6
......@@ -311,6 +311,11 @@ services:
parent: container.trait
tags:
- { name: plugin_manager_cache_clear }
entity_route_subscriber:
class: Drupal\Core\EventSubscriber\EntityRouteProviderSubscriber
arguments: ['@entity.manager']
tags:
- { name: event_subscriber }
entity.definitions.installed:
class: Drupal\Core\KeyValueStore\KeyValueStoreInterface
factory_method: get
......
......@@ -300,6 +300,20 @@ public function getFormObject($entity_type, $operation) {
return $this->handlers['form'][$operation][$entity_type];
}
/**
* {@inheritdoc}
*/
public function getRouteProviders($entity_type) {
if (!isset($this->handlers['route_provider'][$entity_type])) {
$route_provider_classes = $this->getDefinition($entity_type, TRUE)->getRouteProviderClasses();
foreach ($route_provider_classes as $type => $class) {
$this->handlers['route_provider'][$entity_type][$type] = $this->createHandlerInstance($class, $this->getDefinition($entity_type));
}
}
return isset($this->handlers['route_provider'][$entity_type]) ? $this->handlers['route_provider'][$entity_type] : [];
}
/**
* {@inheritdoc}
*/
......
......@@ -223,6 +223,16 @@ public function getListBuilder($entity_type);
*/
public function getFormObject($entity_type, $operation);
/**
* Gets all route provider instances.
*
* @param string $entity_type
* The entity type for this route providers.
*
* @return \Drupal\Core\Entity\Routing\EntityRouteProviderInterface[]
*/
public function getRouteProviders($entity_type);
/**
* Checks whether a certain entity type has a certain handler.
*
......
......@@ -433,6 +433,13 @@ public function hasFormClasses() {
return !empty($this->handlers['form']);
}
/**
* {@inheritdoc}
*/
public function hasRouteProviders() {
return !empty($this->handlers['route_provider']);
}
/**
* {@inheritdoc}
*/
......@@ -477,6 +484,13 @@ public function hasViewBuilderClass() {
return $this->hasHandlerClass('view_builder');
}
/**
* {@inheritdoc}
*/
public function getRouteProviderClasses() {
return !empty($this->handlers['route_provider']) ? $this->handlers['route_provider'] : [];
}
/**
* {@inheritdoc}
*/
......
......@@ -225,6 +225,10 @@ public function getHandlerClass($handler_type);
* - access: The name of the class that is used for access checks. The class
* must implement \Drupal\Core\Entity\EntityAccessControlHandlerInterface.
* Defaults to \Drupal\Core\Entity\EntityAccessControlHandler.
* - route_provider: (optional) A list of class names, keyed by a group
* string, which will be used to define routes related to this entity
* type. These classes must implement
* \Drupal\Core\Entity\Routing\EntityRouteProviderInterface.
*/
public function getHandlerClasses();
......@@ -282,6 +286,22 @@ public function setFormClass($operation, $class);
*/
public function hasFormClasses();
/**
* Indicates if this entity type has any route provider.
*
* @return bool
*/
public function hasRouteProviders();
/**
* Gets all the route provide handlers.
*
* Much like forms you can define multiple route provider handlers.
*
* @return string[]
*/
public function getRouteProviderClasses();
/**
* Returns the list class.
*
......
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Routing\EntityRouteProviderInterface.
*/
namespace Drupal\Core\Entity\Routing;
use Drupal\Core\Entity\EntityTypeInterface;
/**
* Allows entity types to provide routes.
*/
interface EntityRouteProviderInterface {
/**
* Provides routes for entities.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type
*
* @return \Symfony\Component\Routing\RouteCollection|\Symfony\Component\Routing\Route[]
* Returns a route collection or an array of routes keyed by name, like
* route_callbacks inside 'routing.yml' files.
*/
public function getRoutes(EntityTypeInterface $entity_type);
}
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\EntityRouteProviderSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Routing\RouteBuildEvent;
use Drupal\Core\Routing\RoutingEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Routing\RouteCollection;
/**
* Ensures that routes can be provided by entity types.
*/
class EntityRouteProviderSubscriber implements EventSubscriberInterface {
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* Constructs a new EntityRouteProviderSubscriber instance.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
*/
public function __construct(EntityManagerInterface $entity_manager) {
$this->entityManager = $entity_manager;
}
/**
* Provides routes on route rebuild time.
*
* @param \Drupal\Core\Routing\RouteBuildEvent $event
* The route build event.
*/
public function onDynamicRouteEvent(RouteBuildEvent $event) {
$route_collection = $event->getRouteCollection();
foreach ($this->entityManager->getDefinitions() as $entity_type) {
if ($entity_type->hasRouteProviders()) {
foreach ($this->entityManager->getRouteProviders($entity_type->id()) as $route_provider) {
// Allow to both return an array of routes or a route collection,
// like route_callbacks in the routing.yml file.
$routes = $route_provider->getRoutes($entity_type);
if ($routes instanceof RouteCollection) {
$route_collection->addCollection($routes);
}
elseif (is_array($routes)) {
foreach ($routes as $route_name => $route) {
$route_collection->add($route_name, $route);
}
}
}
}
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events[RoutingEvents::DYNAMIC][] = ['onDynamicRouteEvent'];
return $events;
}
}
......@@ -5,15 +5,6 @@ node.multiple_delete_confirm:
requirements:
_permission: 'administer nodes'
entity.node.edit_form:
path: '/node/{node}/edit'
defaults:
_entity_form: 'node.edit'
requirements:
_entity_access: 'node.update'
options:
_node_operation_route: TRUE
node.add_page:
path: '/node/add'
defaults:
......@@ -48,24 +39,6 @@ entity.node.preview:
node_preview:
type: 'node_preview'
entity.node.canonical:
path: '/node/{node}'
defaults:
_controller: '\Drupal\node\Controller\NodeViewController::view'
_title_callback: '\Drupal\node\Controller\NodeViewController::title'
requirements:
_entity_access: 'node.view'
entity.node.delete_form:
path: '/node/{node}/delete'
defaults:
_entity_form: 'node.delete'
_title: 'Delete'
requirements:
_entity_access: 'node.delete'
options:
_node_operation_route: TRUE
entity.node.version_history:
path: '/node/{node}/revisions'
defaults:
......
......@@ -34,6 +34,9 @@
* "delete" = "Drupal\node\Form\NodeDeleteForm",
* "edit" = "Drupal\node\NodeForm"
* },
* "route_provider" = {
* "html" = "Drupal\node\Entity\NodeRouteProvider",
* },
* "list_builder" = "Drupal\node\NodeListBuilder",
* "translation" = "Drupal\node\NodeTranslationHandler"
* },
......
<?php
/**
* @file
* Contains \Drupal\node\Entity\NodeRouteProvider.
*/
namespace Drupal\node\Entity;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\Routing\EntityRouteProviderInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* Provides routes for nodes.
*/
class NodeRouteProvider implements EntityRouteProviderInterface {
/**
* {@inheritdoc}
*/
public function getRoutes( EntityTypeInterface $entity_type) {
$route_collection = new RouteCollection();
$route = (new Route('/node/{node}'))
->addDefaults([
'_controller' => '\Drupal\node\Controller\NodeViewController::view',
'_title_callback' => '\Drupal\node\Controller\NodeViewController::title',
])
->setRequirement('_entity_access', 'node.view');
$route_collection->add('entity.node.canonical', $route);
$route = (new Route('/node/{node}/delete'))
->addDefaults([
'_entity_form' => 'node.delete',
'_title' => 'Delete',
])
->setRequirement('_entity_access', 'node.delete')
->setOption('_node_operation_route', TRUE);
$route_collection->add('entity.node.delete_form', $route);
$route = (new Route('/node/{node}/edit'))
->setDefault('_entity_form', 'node.edit')
->setRequirement('_entity_access', 'node.update')
->setOption('_node_operation_route', TRUE);
$route_collection->add('entity.node.edit_form', $route);
return $route_collection;
}
}
......@@ -30,6 +30,9 @@
* "list_builder" = "Drupal\user\UserListBuilder",
* "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
* "views_data" = "Drupal\user\UserViewsData",
* "route_provider" = {
* "html" = "Drupal\user\Entity\UserRouteProvider",
* },
* "form" = {
* "default" = "Drupal\user\ProfileForm",
* "cancel" = "Drupal\user\Form\UserCancelForm",
......
<?php
/**
* @file
* Contains \Drupal\user\Entity\UserRouteProvider.
*/
namespace Drupal\user\Entity;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\Routing\EntityRouteProviderInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* Provides routes for the user entity.
*/
class UserRouteProvider implements EntityRouteProviderInterface {
/**
* {@inheritdoc}
*/
public function getRoutes(EntityTypeInterface $entity_type) {
$route_collection = new RouteCollection();
$route = (new Route('/user/{user}'))
->setDefaults([
'_entity_view' => 'user.full',
'_title_callback' => 'Drupal\user\Controller\UserController::userTitle',
])
->setRequirement('_entity_access', 'user.view');
$route_collection->add('entity.user.canonical', $route);
$route = (new Route('/user/{user}/edit'))
->setDefaults([
'_entity_form' => 'user.default',
'_title_callback' => 'Drupal\user\Controller\UserController::userTitle',
])
->setOption('_admin_route', TRUE)
->setRequirement('_entity_access', 'user.update');
$route_collection->add('entity.user.edit_form', $route);
$route = (new Route('/user/{user}/cancel'))
->setDefaults([
'_title' => 'Cancel account',
'_entity_form' => 'user.cancel',
])
->setOption('_admin_route', TRUE)
->setRequirement('_entity_access', 'user.delete');
$route_collection->add('entity.user.cancel_form', $route);
return $route_collection;
}
}
......@@ -133,14 +133,6 @@ user.page:
requirements:
_user_is_logged_in: 'TRUE'
entity.user.canonical:
path: '/user/{user}'
defaults:
_entity_view: 'user.full'
_title_callback: 'Drupal\user\Controller\UserController::userTitle'
requirements:
_entity_access: 'user.view'
user.login:
path: '/user/login'
defaults:
......@@ -151,26 +143,6 @@ user.login:
options:
_maintenance_access: TRUE
entity.user.edit_form:
path: '/user/{user}/edit'
defaults:
_entity_form: 'user.default'
_title_callback: 'Drupal\user\Controller\UserController::userTitle'
options:
_admin_route: TRUE
requirements:
_entity_access: 'user.update'
entity.user.cancel_form:
path: '/user/{user}/cancel'
defaults:
_title: 'Cancel account'
_entity_form: 'user.cancel'
options:
_admin_route: TRUE
requirements:
_entity_access: 'user.delete'
user.cancel_confirm:
path: '/user/{user}/cancel/confirm/{timestamp}/{hashed_pass}'
defaults:
......
......@@ -1366,6 +1366,24 @@ public function testGetEntityTypeFromClassAmbiguous() {
$this->entityManager->getEntityTypeFromClass('\Drupal\apple\Entity\Apple');
}
/**
* @covers ::getRouteProviders
*/
public function testGetRouteProviders() {
$apple = $this->getMock('Drupal\Core\Entity\EntityTypeInterface');
$apple->expects($this->once())
->method('getRouteProviderClasses')
->willReturn(['default' => 'Drupal\Tests\Core\Entity\TestRouteProvider']);
$this->setUpEntityManager(array(
'apple' => $apple,
));
$apple_route_provider = $this->entityManager->getRouteProviders('apple');
$this->assertInstanceOf('Drupal\Tests\Core\Entity\TestRouteProvider', $apple_route_provider['default']);
$this->assertAttributeInstanceOf('Drupal\Core\Extension\ModuleHandlerInterface', 'moduleHandler', $apple_route_provider['default']);
$this->assertAttributeInstanceOf('Drupal\Core\StringTranslation\TranslationInterface', 'stringTranslation', $apple_route_provider['default']);
}
/**
* Gets a mock controller class name.
*
......@@ -1546,6 +1564,14 @@ public static function create(ContainerInterface $container) {
}
/**
* Provides a test entity route provider.
*/
class TestRouteProvider extends EntityHandlerBase {
}
/**
* Provides a test config entity storage for base field overrides.
*/
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment