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: ...@@ -311,6 +311,11 @@ services:
parent: container.trait parent: container.trait
tags: tags:
- { name: plugin_manager_cache_clear } - { name: plugin_manager_cache_clear }
entity_route_subscriber:
class: Drupal\Core\EventSubscriber\EntityRouteProviderSubscriber
arguments: ['@entity.manager']
tags:
- { name: event_subscriber }
entity.definitions.installed: entity.definitions.installed:
class: Drupal\Core\KeyValueStore\KeyValueStoreInterface class: Drupal\Core\KeyValueStore\KeyValueStoreInterface
factory_method: get factory_method: get
......
...@@ -300,6 +300,20 @@ public function getFormObject($entity_type, $operation) { ...@@ -300,6 +300,20 @@ public function getFormObject($entity_type, $operation) {
return $this->handlers['form'][$operation][$entity_type]; 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} * {@inheritdoc}
*/ */
......
...@@ -223,6 +223,16 @@ public function getListBuilder($entity_type); ...@@ -223,6 +223,16 @@ public function getListBuilder($entity_type);
*/ */
public function getFormObject($entity_type, $operation); 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. * Checks whether a certain entity type has a certain handler.
* *
......
...@@ -433,6 +433,13 @@ public function hasFormClasses() { ...@@ -433,6 +433,13 @@ public function hasFormClasses() {
return !empty($this->handlers['form']); return !empty($this->handlers['form']);
} }
/**
* {@inheritdoc}
*/
public function hasRouteProviders() {
return !empty($this->handlers['route_provider']);
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
...@@ -477,6 +484,13 @@ public function hasViewBuilderClass() { ...@@ -477,6 +484,13 @@ public function hasViewBuilderClass() {
return $this->hasHandlerClass('view_builder'); return $this->hasHandlerClass('view_builder');
} }
/**
* {@inheritdoc}
*/
public function getRouteProviderClasses() {
return !empty($this->handlers['route_provider']) ? $this->handlers['route_provider'] : [];
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
......
...@@ -225,6 +225,10 @@ public function getHandlerClass($handler_type); ...@@ -225,6 +225,10 @@ public function getHandlerClass($handler_type);
* - access: The name of the class that is used for access checks. The class * - access: The name of the class that is used for access checks. The class
* must implement \Drupal\Core\Entity\EntityAccessControlHandlerInterface. * must implement \Drupal\Core\Entity\EntityAccessControlHandlerInterface.
* Defaults to \Drupal\Core\Entity\EntityAccessControlHandler. * 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(); public function getHandlerClasses();
...@@ -282,6 +286,22 @@ public function setFormClass($operation, $class); ...@@ -282,6 +286,22 @@ public function setFormClass($operation, $class);
*/ */
public function hasFormClasses(); 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. * 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: ...@@ -5,15 +5,6 @@ node.multiple_delete_confirm:
requirements: requirements:
_permission: 'administer nodes' _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: node.add_page:
path: '/node/add' path: '/node/add'
defaults: defaults:
...@@ -48,24 +39,6 @@ entity.node.preview: ...@@ -48,24 +39,6 @@ entity.node.preview:
node_preview: node_preview:
type: '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: entity.node.version_history:
path: '/node/{node}/revisions' path: '/node/{node}/revisions'
defaults: defaults:
......
...@@ -34,6 +34,9 @@ ...@@ -34,6 +34,9 @@
* "delete" = "Drupal\node\Form\NodeDeleteForm", * "delete" = "Drupal\node\Form\NodeDeleteForm",
* "edit" = "Drupal\node\NodeForm" * "edit" = "Drupal\node\NodeForm"
* }, * },
* "route_provider" = {
* "html" = "Drupal\node\Entity\NodeRouteProvider",
* },
* "list_builder" = "Drupal\node\NodeListBuilder", * "list_builder" = "Drupal\node\NodeListBuilder",
* "translation" = "Drupal\node\NodeTranslationHandler" * "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 @@ ...@@ -30,6 +30,9 @@
* "list_builder" = "Drupal\user\UserListBuilder", * "list_builder" = "Drupal\user\UserListBuilder",
* "view_builder" = "Drupal\Core\Entity\EntityViewBuilder", * "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
* "views_data" = "Drupal\user\UserViewsData", * "views_data" = "Drupal\user\UserViewsData",
* "route_provider" = {
* "html" = "Drupal\user\Entity\UserRouteProvider",
* },
* "form" = { * "form" = {
* "default" = "Drupal\user\ProfileForm", * "default" = "Drupal\user\ProfileForm",
* "cancel" = "Drupal\user\Form\UserCancelForm", * "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: ...@@ -133,14 +133,6 @@ user.page:
requirements: requirements:
_user_is_logged_in: 'TRUE' _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: user.login:
path: '/user/login' path: '/user/login'
defaults: defaults:
...@@ -151,26 +143,6 @@ user.login: ...@@ -151,26 +143,6 @@ user.login:
options: options:
_maintenance_access: TRUE _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: user.cancel_confirm:
path: '/user/{user}/cancel/confirm/{timestamp}/{hashed_pass}' path: '/user/{user}/cancel/confirm/{timestamp}/{hashed_pass}'
defaults: defaults:
......
...@@ -1366,6 +1366,24 @@ public function testGetEntityTypeFromClassAmbiguous() { ...@@ -1366,6 +1366,24 @@ public function testGetEntityTypeFromClassAmbiguous() {
$this->entityManager->getEntityTypeFromClass('\Drupal\apple\Entity\Apple'); $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. * Gets a mock controller class name.
* *
...@@ -1546,6 +1564,14 @@ public static function create(ContainerInterface $container) { ...@@ -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. * 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