drupal 8 in action, the route to the method

54
Drupal 8 in action, the route to the method @juanolalla #DrupalCampEs

Post on 21-Oct-2014

593 views

Category:

Technology


2 download

DESCRIPTION

 

TRANSCRIPT

Page 1: Drupal 8 in action, the route to the method

Drupal 8 in action,the route to the method

@juanolalla #DrupalCampEs

Page 2: Drupal 8 in action, the route to the method

about.me/juanolalla

Juan Olallaweb developer focused on drupalat @ideup (@gowex)

MotivationI’ve recently been contributing to Drupal 8 coreand I want to share with you what I've learned

Page 3: Drupal 8 in action, the route to the method

Wellcome to

Drupal 8 Routing

Page 4: Drupal 8 in action, the route to the method

Goodbye tohook_menu()

Page 5: Drupal 8 in action, the route to the method

Goodbye to hook_menu() for routingand for everything else...

Routing Routes defined in routing.yml and controllers

Menu links Defined in hook_default_menu_links()

Local actions Plugins defined in local_actions.yml

Local tasks Plugins defined in local_tasks.yml

Contextual links Plugins defined in contextual_links.yml

Breadcrumbs Implementing BreadcrumbBuilder plugins

Links can be related regardless of the path, managed by machine names.

Page 6: Drupal 8 in action, the route to the method

Drupal 8 uses Symfony2 Routingcomponent

And also uses Symfony CMFRoutingBundle for dynamic routing

Page 7: Drupal 8 in action, the route to the method

What is a route?

Page 8: Drupal 8 in action, the route to the method

A route is an object defined by

1. Path

2. Default values

3. Requirements

4. Options

5. Host

6. Schemes

7. Methods

Page 9: Drupal 8 in action, the route to the method

use Symfony\Component\Routing\Route;

$route = new Route(

'/archive/{month}', // path

array('controller' => 'showArchive'), // default values

array(

'month' => '[0-9]{4}-[0-9]{2}',

'subdomain' => 'www|m',

), // requirements

array(), // options

'{subdomain}.example.com', // host

array(), // schemes

array() // methods

);

Page 10: Drupal 8 in action, the route to the method

The Routing component gets routes from

• PHP code:

• a RouteCollection object

• closures

• class annotations

• External files:

• YAML

• XML

• PHP

Page 11: Drupal 8 in action, the route to the method

Drupal 8 uses YAML to define routesfoo.archive:

path: '/archive/{month}'

defaults:

_content:

'\Drupal\foo\Controller\FooController::showArchive'

requirements:

month: '[0-9]{4}-[0-9]{2}'

subdomain: 'www|m'

host: '{subdomain}.example.com'

Page 12: Drupal 8 in action, the route to the method

Let’s start with a plain “hello world”

using a /hello-world path

Page 13: Drupal 8 in action, the route to the method

Defining the route in YAML

/modules/foo/foo.routing.yml

foo.hello_world:

path: '/hello-world'

defaults:

_controller:

'\Drupal\foo\Controller\FooController::helloWorld'

requirements:

_access: 'TRUE'

Page 14: Drupal 8 in action, the route to the method

Creating the controller

/modules/foo/lib/Controller/FooController.php

namespace Drupal\foo\Controller;

use Symfony\Component\HttpFoundation\Response;

class FooController {

public function helloWorld() {

$response = new Response('Hello World!');

$response->headers->set('Content-Type', 'text/plain');

return $response;

}

}

Page 15: Drupal 8 in action, the route to the method

Hello World!

Page 16: Drupal 8 in action, the route to the method

Drupal 8 will use PSR-4 for module classes insteadof PSR-0

/modules/foo/lib/Controller/FooController.phpinstead of/modules/foo/lib/Drupal/foo/Controller/FooController.php

drupal.org issue: https://drupal.org/node/1971198

Page 17: Drupal 8 in action, the route to the method

Returning a JSON responsenamespace Drupal\foo\Controller;

use Symfony\Component\HttpFoundation\JsonResponse

class FooController {

public function helloWorld() {

return new JsonResponse(

array('greeting' => 'Hello World!')

);

}

}

Page 18: Drupal 8 in action, the route to the method

{"greeting":"Hello World!"}

Page 19: Drupal 8 in action, the route to the method

Returning "hello world" insideDrupal content

using a /hello-world path

Page 20: Drupal 8 in action, the route to the method

Defining the route in YAMLfoo.hello_world:

path: '/hello-world'

defaults:

_content:

'\Drupal\foo\Controller\FooController::helloWorld'

_title: 'Greeting'

requirements:

_permission: 'access content'

Page 21: Drupal 8 in action, the route to the method

Returning a render arraynamespace Drupal\foo\Controller;

use Drupal\Core\Controller\ControllerBase;

class FooController extends ControllerBase {

public function helloWorld() {

$build = array();

$build['#markup'] = $this->t('Hello World!');

return $build;

}

}

Page 22: Drupal 8 in action, the route to the method
Page 23: Drupal 8 in action, the route to the method

What about dynamic page titles?

drupal_set_title() is being removed

Use _title_callback in the route defaultsdefaults:

_title_callback:

'\Drupal\foo\Controller\FooController::getTitle'

Or return it as part of the main render array$build['#title'] = ...

Page 24: Drupal 8 in action, the route to the method

Returning a "hello world"greeting configuration format /admin/config/people/greeting

Page 25: Drupal 8 in action, the route to the method

Defining the route in YAML

foo.admin_greeting:

path: '/admin/config/people/greeting'

defaults:

_form: '\Drupal\foo\Form\FooForm'

requirements:

_permission: 'administer greeting'

Page 26: Drupal 8 in action, the route to the method

use Drupal\Core\Form\ConfigFormBase;

class FooForm extends ConfigFormBase {

public function getFormId() { return 'foo_form'; }

public function buildForm(array $form, array &$form_state) {

$form['foo_greeting'] = array(

'#type' => 'textfield',

'#title' => $this->t('Greeting'),

'#default_value' => 'Hellow World!',

);

return parent::buildForm($form, $form_state);

}

}

Page 27: Drupal 8 in action, the route to the method
Page 28: Drupal 8 in action, the route to the method

FormBase implements FormInterfacenamespace Drupal\Core\Form;

interface FormInterface {

public function getFormId();

public function buildForm(array $form, array &$form_state);

public function validateForm(array &$form, array &$form_state);

public function submitForm(array &$form, array &$form_state);

}

Page 29: Drupal 8 in action, the route to the method

Advanced Routing

Page 30: Drupal 8 in action, the route to the method

defaults:

# Control the full response apart from Drupal extra content

_controller: '\Drupal\book\Controller\BookController::bookExport'

# Put content in the main region and let Drupal do the rest

_content: '\Drupal\book\Controller\BookController::bookRender'

# Get the buildForm method of a class extending a Drupal Form

_form: '\Drupal\book\Form\BookSettingsForm'

Page 31: Drupal 8 in action, the route to the method

Entity defaults

defaults:

# Call buildForm for the action entity add operation Form

_entity_form: 'action.add'

# Display a list of taxonomy_vocabulary entity objects

_entity_list: 'taxonomy_vocabulary'

# Show the full view mode of a user entity object

_entity_view: 'user.full'

Page 32: Drupal 8 in action, the route to the method

Passing parameters to the controller method# Parameter defined in the path

path: '/comment/{comment}/approve'

# Default parameter

path: '/admin/structure/types'

defaults:

entity_type: 'node_type'

# Optional (both in the path and with a default value)

path: '/admin/config/search/path/{keys}'

defaults:

keys: NULL

Page 33: Drupal 8 in action, the route to the method

node.overview_types:

path: '/admin/structure/types'

defaults:

_content:

'\Drupal\Core\Entity\Controller\EntityListController::listing'

entity_type: 'node_type'

_title: 'Content types'

class EntityListController extends ControllerBase {

public function listing($entity_type) {

return $this->entityManager()

->getListController($entity_type)

->render();

}

}

Page 34: Drupal 8 in action, the route to the method

Parameters are upcasted to entities by entity type idcontact.personal_page

path: '/user/{user}/contact'

contact.site_page_category:

path: '/contact/{contact_category}'

class ContactController {

public function contactPersonalPage(UserInterface $user) {...}

public function contactSitePage(

CategoryInterface $contact_category = NULL) {...}

}

Page 35: Drupal 8 in action, the route to the method

Explicitly converting parameters into entitiesfoo.hello_user:

path: '/{foo_user}/hello/{user}'

# e.g. /1/hello/2

defaults:

_content: '\...\FooController::helloUser'

options:

parameters:

foo_user:

type: 'entity:user'

# {foo_user} will also be passed as a user entity

Page 36: Drupal 8 in action, the route to the method

Regular expressions requirements for parameters# {op} parameter can only be 'enable' or 'disable'

views_ui.operation:

path: '/admin/structure/views/view/{view}/{op}'

requirements:

op: 'enable|disable'

# {user} parameter can only be a number

user.view:

path: '/user/{user}'

requirements:

user: \d+

Page 37: Drupal 8 in action, the route to the method

Access Check_you_shall_not_pass: TRUE

Page 38: Drupal 8 in action, the route to the method

Drupal 8 doesn't implement Symfony2 Securitycomponent, and manages access from routing

Symfony uses a separate Security component for controlling route accessthat is too complex to integrate in Drupal 8.

Drupal 8 implements access checkers services, which will ignore, allow ordeny the access based on a requirement on the route.

Page 39: Drupal 8 in action, the route to the method

Checking Permissionsnode.overview_types:

path: '/admin/structure/types'

defaults:

_content:

'\Drupal\Core\Entity\Controller\EntityListController::listing'

entity_type: 'node_type'

_title: 'Content types'

requirements:

_permission: 'administer content types'

Page 40: Drupal 8 in action, the route to the method

Still setting permissions in hook_permission()function node_permission() {

return array(

'administer content types' => array(

'title' => t('Administer content types'),

'restrict access' => TRUE,

),

);

}

I asked for hook_permission() in IRC #drupal-contribute<timplunkett> juanolalla: no plans to replace that AFAIK

Page 41: Drupal 8 in action, the route to the method

Common access checkers provided by Drupal core# Always pass, or 'FALSE' to always fail and block access

requirements:

_access: 'TRUE'

# Role IDs separated by "," (any role) or "+" (all roles)

requirements:

_role: 'authenticated,admin'

# Passes if the user is logged in

requirements:

_user_is_logged_in: 'TRUE'

Page 42: Drupal 8 in action, the route to the method

Other useful access checkers provided# Checks if any/all (,/+) the specified modules are enabled

requirements:

_module_dependencies: 'node + search'

# Checks access to the specified entity_type.operation

requirements:

_entity_access: 'node.edit'

# Checks access to the specified entity_type:entity_bundle

requirements:

_entity_create_access: 'taxonomy_term:{taxonomy_vocabulary}'

Page 43: Drupal 8 in action, the route to the method

Declaring your own custom access checker

Declare your requirement checker in .routing.ymlrequirements:

_you_shall_not_pass: 'TRUE'

Register an access checker class as a service (in .services.yml)services:

access_check.foo.gandalf:

class: Drupal\foo\Access\GandalfAccessCheck

tags:

- { name: access_check }

Page 44: Drupal 8 in action, the route to the method

Writting your own custom access checker classclass GandalfAccessCheck implements StaticAccessCheckInterface {

public function appliesTo() {

return array('_you_shall_not_pass');

}

public function access(Route $route, Request $request) {

$requirement = $route->getRequirement('_you_shall_not_pass');

if (!in_array('https', $route->getSchemes())

&& $requirement == 'TRUE') {

return static::DENY; // or return static::ALLOW

} // No return means other checkers will decide

}

}

Page 45: Drupal 8 in action, the route to the method

Access mode options, checking any or all requirementsnode.add_page:

path: '/node/add'

defaults:

_title: 'Add page'

_content: '\Drupal\node\Controller\NodeController::addPage'

options:

_access_mode: 'ANY'

requirements:

_permission: 'administer content types'

_node_add_access: 'node'

Page 46: Drupal 8 in action, the route to the method

Building Dynamic RoutesImplement a route subscriber class

Page 47: Drupal 8 in action, the route to the method

/core/modules/system/Routing/RouteSubscriber.phpnamespace Drupal\system\Routing;

use Drupal\Core\Routing\RouteSubscriberBase;

use Symfony\Component\Routing\Route;

use Symfony\Component\Routing\RouteCollection;

/**

* Provides dynamic routes for theme administration.

*/

class RouteSubscriber extends RouteSubscriberBase {

protected function routes(RouteCollection $collection) {...}

}

Page 48: Drupal 8 in action, the route to the method

protected function routes(RouteCollection $collection) {

foreach (list_themes() as $theme) {

$route = new Route(

'admin/appearance/settings/' . $theme->name,

array(

'_form' => '\Drupal\system\Form\ThemeSettingsForm',

'theme_name' => $theme->name

),

array('_permission' => 'administer themes'),

);

$collection->add(

'system.theme_settings_' . $theme->name, $route);

}

}

Page 49: Drupal 8 in action, the route to the method

Dependency injectionKeep your controllers clean and thin

Page 50: Drupal 8 in action, the route to the method

Our classes shouldn't have hard-coded dependenciesclass SongController {

public function getRandomSong() {

// db_query() is a hard-coded dependency

$songs = db_query('SELECT name FROM {songs}')->fetchCol();

$song = $songs[rand(0, count($songs) - 1)];

return new JsonResponse(

array('song' => $song)

);

}

}

Page 51: Drupal 8 in action, the route to the method

Decoupling the dependency

protected $database; // Drupal\Core\Database\Connection;

public function getRandomSong() {

$songs = $this->database

->query('SELECT name FROM {songs}')->fetchCol();

return $songs[rand(0, count($songs) - 1)];

}

Page 52: Drupal 8 in action, the route to the method

Getting the dependency injecteduse Drupal\Core\Database\Connection;

use Drupal\Core\DependencyInjection\ContainerInjectionInterface;

use Symfony\Component\DependencyInjection\ContainerInterface;

class SongController implements ContainerInjectionInterface {

protected $database;

public function __construct(Connection $database) {

$this->database = $database;

}

public static function create(ContainerInterface $container) {

// Instantiate the controller with a database object

return new static($container->get('database'));

}

Page 53: Drupal 8 in action, the route to the method

Want to see more coming?

/join #drupal-contribute

Page 54: Drupal 8 in action, the route to the method

Thank you!