1 of 63

2 of 63

Juanen Bernal

Drupal developer en idealista/news

Profesor Drupal 8 en OpenWebinars

Maintainer S3 File System y

File Version

irc: jansete

@jansev3n

3 of 63

Qué vamos a ver

  • Introducción al sistema de routing
  • Rutas y controladores
  • Usando parámetros en las rutas
  • Parameter converters
  • Entendiendo en profundidad los routing.yml
  • Rutas complejas: route callbacks y Path Processors
  • Alterando rutas
  • Clases relacionadas con el sistema de routing

4 of 63

¿Os acordáis de ...?

5 of 63

¿y si ahora quiero que se llame blog?

¿por qué no aparezco en Google?

¿cómo defino nuevas URLs?

¿dónde pongo los ficheros php?

¿puedo controlar el acceso?

The requested URL was not found on this server.

POST, GET, PATCH, HEAD, PUT, DELETE ...

¿y la europea?

¿cómo hago un enlace a una URL interna?

¿ qué hacemos con las cabeceras?

/index.php?noticia=71

6 of 63

Sistema de routing en Drupal 8

  • Gestiona una colección de rutas que mapean una URL con un método/función que devuelve un contenido.
  • Está basado en el componente Routing de Symfony.
  • Toma el relevo de hook_menu() para la gestión de las rutas (entradas de menú, tabs, actions y contextual links forman parte de otros subsistemas en 8).

7 of 63

¿Cuándo se ejecuta?

8 of 63

Flujo de una petición en Drupal 8

  1. Bootstrap
  2. Creación de Drupal Kernel
  3. Inicialización del Contenedor de Servicios
  4. Añadir el contenedor a la clase estática Drupal.
  5. Intentar devolver la página desde la caché
  • Cargar variables e includes
  • Registrar Stream Wrappers
  • Crear objeto HTTP Request
  • Permitir a Drupal Kernel manejarlo y generar la respuesta.
  • Enviar la respuesta
  • Terminar la petición

9 of 63

10 of 63

Rutas y controladores: creando nuestra primera página

/hello-world

11 of 63

Elementos principales

  • Route
    • Elemento/objeto que contiene la información de la ruta.
  • Path
    • URI a la que se asocia una ruta (puede tener parámetros).
  • Controller
    • Clase que engloba varios métodos que devuelven el contenido.
    • También usado para referirnos al método que devuelve el contenido.
  • Action
    • Método de la clase Controller que devuelve el contenido.

12 of 63

¿Qué necesitamos para declarar una ruta?

  • into_the_wild/
    • src/
      • Controller/
        • HelloController.php
    • into_the_wild.info.yml
    • into_the_wild.routing.yml

13 of 63

into_the_wild.routing.yml

  • Archivo YAML situado en la raíz del módulo en el que definiremos nuestras rutas.

into_the_wild.hello_world:

path: '/hello-world'

defaults:

_controller: \Drupal\into_the_wild\Controller\HelloController::helloWorld

_title: 'Hello world'

requirements:

_permission: 'access content'

14 of 63

Controladores

  • Los controladores son archivos PHP situados en src/Controller que se encargan de devolver el contenido de nuestra ruta.
  • Los métodos pueden devolver:
    • Render arrays
    • Symfony\Component\HttpFoundation\Response
      • JsonResponse
      • RedirectResponse
      • BinaryFileResponse
      • BigPipeResponse
      • etc

15 of 63

HelloController.php

<?php

namespace Drupal\into_the_wild\Controller;

use Drupal\Core\Controller\ControllerBase;

class HelloController extends ControllerBase {

public function helloWorld(Request $request) {

return [

'#type' => 'markup',

'#markup' => $this->t('Hello world'),

];

}

}

16 of 63

Voilà!

17 of 63

Usando parámetros en las rutas

/hello/{name}

18 of 63

Usando parámetros en rutas

  • Drupal usa parámetros en sus rutas para que estas puedan ser dinámicas. Ejemplo: node/1, node/2, node/3, etc.

into_the_wild.hello_name:

path: '/hello/{name}'

defaults:

_controller: \Drupal\into_the_wild\Controller\HelloController::helloName

requirements:

_access: 'TRUE'

name: '[a-zA-Z]+'

19 of 63

Usando parámetros en rutas

  • El valor de la variable {name} definida en routing.yml la tendremos disponible en el controlador como argumento con el mismo nombre.

public function helloName($name) {

return [

'#type' => 'markup',

'#markup' => $this->t('Hello @name', ['@name' => $name]),

];

}

20 of 63

Definiendo valores por defecto

  • Podemos asignar un valor por defecto para que el parámetro no sea obligatorio.

into_the_wild.hello_name:

path: '/hello/{name}'

defaults:

_controller: \Drupal\into_the_wild\Controller\HelloController::helloName

name: 'Christopher'

requirements:

_access: 'TRUE'

name: '[a-zA-Z]+'

21 of 63

Otras curiosidades

  • Podemos ordenar los argumentos como queramos siempre y cuando se respeten los nombres definidos en routing.yml
  • Los parámetros sólo pueden ocupar un espacio entre dos /, o bien ser el último elemento del path.

22 of 63

Parameter converters

/hello/{user}

23 of 63

Parameters upcasting

  • Drupal nos permite aprovechar un parámetro de la URL para inyectar al controlador un elemento convertido gracias a los Parameter Converters.

into_the_wild.hello_user:

path: '/hello/{user}'

defaults:

_controller: \Drupal\into_the_wild\Controller\HelloController::helloUser

requirements:

_access: 'TRUE'

user: \d+

24 of 63

Parameters upcasting: condiciones

  • El nombre de la variable debe corresponder con el id del tipo de entidad.
  • El argumento del controlador debe estar correctamente tipado.

25 of 63

Parameters upcasting: condiciones

  • El nombre de la variable debe corresponder con el id del tipo de entidad, ej: user.

Ejemplo de entidad: \Drupal\user\Entitity\User

/**

* Defines the user entity class.

*

* The base table name here is plural, despite Drupal table naming standards,

* because "user" is a reserved word in many databases.

*

* @ContentEntityType(

* id = "user",

* label = @Translation("User"),

* …

* )

*/

26 of 63

Parameters upcasting: condiciones

  • El argumento del controlador debe estar correctamente tipado:
    • O que la clase de la entidad sea igual que la clase tipada.
    • O que la clase de la entidad sea subclase de la clase tipada.

Ejemplo de clase de entidad: \Drupal\user\Entitity\User

public function helloUser(UserInterface $user) {

return [

'#type' => 'markup',

'#markup' => $this->t('Hello @user', ['@user' => $user->getDisplayName()]),

];

}

27 of 63

¿Y si usamos dos parámetros de la misma entidad?

  • Podemos forzar el tipado de un parámetro en la definición de la ruta dentro del apartado options.

into_the_wild.hello_users:

path: '/hello/{user1}/{user2}'

defaults:

_controller: \Drupal\into_the_wild\Controller\HelloController::helloUsers

requirements:

_access: 'TRUE'

user1: \d+

user2: \d+

options:

parameters:

user1:

type: entity:user

user2:

type: entity:user

28 of 63

Obteniendo los valores originales

  • Podemos obtener también los valores “crudos” incluso cuando esta ruta esté usando Parameter Converters.

$request->attributes->get('_raw_variables')->get('user');

$route_match->getRawParameters('user');

29 of 63

Creando nuestro propio Parameter converter

30 of 63

Parameter Converters

  • Por defecto los Parameter Converters tienen compatibilidad con todas las entidades que dispongamos ya sean de configuración o de contenido.
  • Aunque sea difícil imaginar un caso de uso que no utilice entidades, también podemos crear nuestros propios Parameter Converters para este tipo de casos.

31 of 63

¿Qué necesitamos?

  • Servicio con el tag ‘paramconverter’

Y obviamente :)

  • Una ruta que use nuestro identificador de elemento.
  • Un controlador asociado a dicha ruta.

32 of 63

Parameter Converters: routing.yml

  • Vamos a definir nuestro Parameter Converter que se encargará del parámetro {tripper}.

into_the_wild.tripper:

path: '/tripper/{tripper}'

defaults:

_controller: \Drupal\into_the_wild\Controller\TripperController::view

requirements:

_access: 'TRUE'

33 of 63

Parameter Converters

  • Imaginemos una clase sencilla Tripper ...

class Tripper {

private $id;

public function __construct($id) {

$this->id = $id;

}

public function getId() {

return $this->id;

}

}

34 of 63

Parameter Converters: services.yml

  • Definimos nuestro servicio con el tag paramconverter en into_the_wild.services.yml

services:

into_the_wild.tripper_param_converter:

class: Drupal\into_the_wild\Routing\TripperParamConverter

tags:

- { name: paramconverter }

35 of 63

Parameter Converters: servicio

<?php

namespace Drupal\into_the_wild\Routing;

use ...

class TripperParamConverter implements ParamConverterInterface {

public function applies($definition, $name, Route $route) {

return !empty($definition['type']) && $definition['type'] == 'tripper';

}

public function convert($value, $definition, $name, array $defaults) {

return new Tripper($value);

}

}

36 of 63

Parameter Converters: routing.yml

into_the_wild.tripper:

path: '/tripper/{tripper}'

defaults:

_controller: \Drupal\into_the_wild\Controller\TripperController::view

requirements:

_access: 'TRUE'

options:

parameters:

tripper:

type: tripper

37 of 63

Parameter Converters: Controlador

class TripperController extends ControllerBase {

public function view(Tripper $tripper) {

return [

'#type' => 'markup',

'#markup' => $this->t('Tripper id is: @tripper_id', ['@tripper_id' => $tripper->getId()]),

];

}

}

38 of 63

Dando un repaso al routing.yml

39 of 63

Dando un repaso al routing.yml

into_the_wild.hello_users:

path: '/hello/{user1}/{user2}'

defaults:

_controller: \Drupal\into_the_wild\Controller\HelloController::helloUsers

requirements:

_access: 'TRUE'

user1: \d+

user2: \d+

methods: [GET, POST] #opcional

options: #opcional

parameters:

user1:

type: entity:user

user2:

type: entity:user

40 of 63

routing.yml: defaults

Define las propiedades por defecto de la ruta.

into_the_wild.hello_users:

defaults:

_controller: \Drupal\into_the_wild\Controller\HelloController::helloUsers

_controller: into_the_wild.service:helloUsers

_form: \Drupal\into_the_wild\Form\SettingsForm

_entity_view: 'node.teaser'

_entity_list: 'node_type'

_entity_form: 'node_type.add'

_title: 'Título de la página' #opcional

_title_context: 'Contexto de traducción' #opcional

_title_callback: \Drupal\into_the_wild\IntoTheWildRouting::titleCallback #opcional

41 of 63

routing.yml: requirements

into_the_wild.hello_users:

requirements:

_permission: 'access content'

_permission: 'access content, administer content types' # AND

_permission: 'access content + administer content types' # OR

_role: 'admin'

_role: 'admin, manager' # AND

_role: 'admin + manager' # OR

_access: 'TRUE'

_entity_access: 'node.view'

_custom_access: '\Drupal\into_the_wild\Controller\ExampleController::access'

_format: json

_content_type_format: json

_module_dependencies: 'image'

_module_dependencies: 'image, file_version' # OR WTF??

_module_dependencies: 'image + file_version' # AND WTF??

user1: \d+

user2: \d+

42 of 63

routing.yml: options

Opciones adicionales de interacción con la ruta.

into_the_wild.hello_users:

options:

_admin_route: 'TRUE'

_auth: ['basic_auth', 'cookie']

no_cache: 'TRUE'

parameters:

user1:

type: entity:user

user2:

type: entity:user

43 of 63

Rutas dinámicas con route callbacks

44 of 63

Rutas dinámicas con Route callbacks

  • Los archivos YAML son estáticos, pero … ¿y si tengo partes variables? ¡¡Necesito PHP!!
  • Core tiene esta necesidad para generar los image styles.

¿Cómo solucionamos esto?

45 of 63

image.routing.yml

  • Dentro de nuestro routing.yml podemos definir una lista de callbacks que devuelvan rutas de forma dinámica, los route_callbacks.

route_callbacks:

- '\Drupal\image\Routing\ImageStyleRoutes::routes'

46 of 63

Rutas dinámicas en core: image styles

<?php

namespace Drupal\image\Routing;

use ...

class ImageStyleRoutes implements ContainerInjectionInterface {

public function routes() {

$routes = [];

$directory_path = $this->streamWrapperManager->getViaScheme('public')->getDirectoryPath();

$routes['image.style_public'] = new Route(

'/' . $directory_path . '/styles/{image_style}/{scheme}',

[ '_controller' => 'Drupal\image\Controller\ImageStyleDownloadController::deliver'],

['_access' => 'TRUE']

);

return $routes;

}

}

47 of 63

Parámetros complejos con Path Processors

48 of 63

Parámetros complejos: path processors

  • Tenemos que trabajar con un parámetro multidirectorio y de longitud variable. Ejemplo:
    • /sites/default/files/styles/medium/public/2017-07/homer.png
    • /sites/default/files/styles/medium/public/2017-07/news/homer.png
    • /sites/default/files/styles/medium/public/2017-07/news/field_image/homer.png

¿Os suena de algo?

49 of 63

Image style path processor

Request URI

/sites/default/files

/styles/medium/public

/2017-07/homer.png?itok=7BXqWIP1

Route Path

/$directory_path

/styles/{image_style}/{scheme}

PathProcessorImageStyles::processInbound()

Path:

/sites/default/files

/styles/medium/public

GET:

file = /2017-07/homer.png

itok = 7BXqWIP1

50 of 63

Obteniendo el parámetro GET

<?php

namespace Drupal\image\Controller;

use

/**

* Defines a controller to serve image styles.

*/

class ImageStyleDownloadController extends FileDownloadController {

public function deliver(Request $request, $scheme, ImageStyleInterface $image_style) {

$target = $request->query->get('file');

$image_uri = $scheme . '://' . $target;

...

}

}

51 of 63

Parámetros complejos: Path processors

  • En el método inbound:
    • Pasamos a parámetro GET nuestra variable compleja
    • Adaptamos la URL para que encaje con la definida en el routing.
  • Posteriormente obtenemos en el controller el parámetro GET de la Request

52 of 63

Alterando rutas

53 of 63

Alterando rutas

  • Cualquier ruta (yml o route callbacks) puede ser alterada escuchando el evento RoutingEvents::ALTER.
  • Escuchando este evento también podríamos añadir nuevas rutas dinámicas, pero no es su propósito, para estos casos hay que usar los route callbacks.

54 of 63

Definiendo el Event Subscriber

  • Dentro de nuestro services.yml definimos nuestro servicio con el tag ‘event_subscriber’.

services:

into_the_wild.route_subscriber:

class: Drupal\into_the_wild\Routing\RouteSubscriber

tags:

- { name: event_subscriber }

55 of 63

Alterando una ruta

<?php

namespace Drupal\into_the_wild\Routing;

use Drupal\Core\Routing\RouteSubscriberBase;

use Symfony\Component\Routing\RouteCollection;

class RouteSubscriber extends RouteSubscriberBase {

protected function alterRoutes(RouteCollection $collection) {

if ($route = $collection->get('user.login')) {

$route->setPath('/login');

}

}

}

56 of 63

Clases relacionadas

57 of 63

Clases y servicios relacionados

  • Route
  • RouteMatch
  • CurrentRouteMatch
  • RouteProvider
  • Url
  • Link
  • Request
  • CurrentPathStack
  • PathMatcher
  • AliasManager
  • AliasStorage

58 of 63

Enlaces de interés

59 of 63

60 of 63

Plataforma de formación IT con más de 50 cursos: ansible, symfony, vagrant, etc, etc ...

Un curso nuevo cada semana.

A finales de año contaremos con la primera parte de un curso de Backend para Drupal 8.

61 of 63

62 of 63

63 of 63