Quando un utente richiede un URL, Drupal deve determinare:
- quale route corrisponde all'URL
- se l'utente può accedervi
- quale codice deve essere eseguito
- quale risposta deve essere restituita
Una route associa un percorso URL a un componente incaricato di produrre il contenuto, normalmente un controller o un form.
Il controller è una classe PHP il cui metodo viene richiamato dalla route. Il controller può restituire un render array, che Drupal trasformerà nella pagina finale, o un oggetto Response, se deve controllare direttamente la risposta HTTP.
E' possibile creare un menu link per rendere raggiungibile la route attraverso un menu.
Una route può richiedere che l'utente abbia una determinata permission prima di consentire l'accesso.
La route
Le route sono definite nel file .routing.yml. Ogni route è identificata da un nome univoco, normalmente formato dal machine name del modulo seguito da un identificatore descrittivo. Per definire una route bisogna indicare le chiavi:
- path che definisce il percorso della route
- defaults che indica valori di default
- requirements che definisce i vincoli di accesso
E' anche possibile indicare nella chiave methods i metodi HTTP accettati.
nome_modulo.forecast:
path: '/weather/{display}' # display è un parametro dell'url
defaults:
_controller: '\Drupal\nome_modulo\Controller\ForecastController::build' # Fully Qualified Class Name del controller
_title: 'Weather forecast'# titolo della pagina e tag <title>
display: 'summary' # valore di default del parametro
requirements:
_permission: 'view weather forecast' # questa permission verrà definita in permissions.yml
_access: 'TRUE' # alternativa se si vuole indicare che la route è accessibile a chiunque
_custom_access: '\Drupal\nome_modulo\Access\ForecastAccessCheck::access' # alternativa se si vogliono controlli più specifici
_entity_access: 'node.view' # da usare se la route opera su entità
display: 'summary|extended' # valori accettati per il parametro display
methods: [GET]
nome_modulo.settings:
path: '/admin/config/services/nome-modulo'
defaults:
_form: '\Drupal\nome_modulo\Form\SettingsForm' # non si usa _controller perchè è un form
_title: 'Nome Modulo settings'
requirements:
_permission: 'administer weather settings'
nome_modulo.forecast:
path: '/weather/{display}' # apici singoli!
Validazione del parametro
Quando il valore del parametro passato nella route non soddisfa il pattern indicato nella chiave requirements Drupal non considera valida la route e restituisce una risposta HTTP 404 Not Found. Quindi nel controller non è necessario inserire questo tipo di validazione:
// codice da NON inserire nel controller
$display = in_array($display, ['summary', 'extended']) ? $display : 'summary';
Route parameter e query parameter
- route parameter: /weather/summary
- query parameter: /weather?display=summary
Se il controller deve leggere un query parameter:
use Symfony\Component\HttpFoundation\Request;
public function build(Request $request): array {
$display = $request->query->get('display', 'summary');
// ...
}
Parameter upcasting
Quando un parametro rappresenta un'entità Drupal, il routing system può convertire automaticamente l'ID presente nell'URL nell'oggetto corrispondente:
nome_modulo.alert:
path: '/weather-alert/{node}'
defaults:
_controller: '\Drupal\nome_modulo\Controller\AlertController::build'
_title: 'Weather alert'
requirements:
_entity_access: 'node.view' # verifica che l'utente possa visualizzare un'entità di tipo node
options:
parameters:
node:
type: entity:node # indica che il parametro deve essere convertito in un'entità di tipo node
Nel controller:
use Drupal\node\NodeInterface;
public function build(NodeInterface $node): array {
// ...
}
Se l'entità non esiste Drupal restituisce una risposta di errore 404 Not Found.
Il controller
Nel file src/Controller/ForecastController.php:
<?php
declare(strict_types=1);
namespace Drupal\nome_modulo\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\nome_modulo\Service\ForecastClientInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Returns responses for the Nome Modulo forecast routes.
*/
final class ForecastController extends ControllerBase
{
/**
* Constructs a ForecastController object.
*/
public function __construct(
private readonly ForecastClientInterface $forecastClient // Dependency Injection
) {}
/**
* {@inheritdoc}
*/ public static function create( ContainerInterface $container): static {
return new static($container->get(ForecastClientInterface::class));
}
/**
* Builds the weather forecast page.
*/
public function build(string $display): array {
return [
"#theme" => "nome_modulo_forecast",
"#forecast" => $this->forecastClient->getForecast(),
"#display" => $display,
"#cache" => [
"tags" => ["nome_modulo:forecast"],
"max-age" => 1800
],
];
}
}
ControllerBase
ControllerBase è una classe di utilità destinata ai controller che contengono soltanto una quantità limitata di logica.
Il controller non dovrebbe occuparsi direttamente di:
- eseguire chiamate HTTP
- accedere al database
- trasformare dati complessi
- gestire la cache applicativa
- contenere business logic
- compiere operazioni che richiedono test unitari articolati
Queste responsabilità devono essere delegate ai servizi.
Restituzione di una risposta in JSON
use Symfony\Component\HttpFoundation\JsonResponse;
public function status(): JsonResponse {
return new JsonResponse([
'status' => 'ok',
]);
}
Redirect
use Symfony\Component\HttpFoundation\RedirectResponse;
public function redirectToForecast(): RedirectResponse {
return $this->redirect('nome_modulo.forecast');
}
Restituzione di una risposta di errore 404
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
throw new NotFoundHttpException();
Permissions
Nel file .permissions.yml:
view weather forecast: # le permissions si possono scrivere con gli spazi!
title: 'View weather forecast'
description: 'View the weather forecast provided by Nome Modulo.'
administer weather settings:
title: 'Administer weather settings'
description: 'Change the weather provider and forecast settings.'
restrict access: true
Si può anche aggiungere la chiave restrict access: true che mostra nell'interfaccia un avviso che invita ad assegnare la permission soltanto a ruoli fidati.
Avendo dichiarato nel modulo che è necessaria la permission view weather forecast Drupal verifica automaticamente che l'utente ce l'abbia e in caso contrario restituisce una risposta HTTP 403 Forbidden (non sono necessari controlli sul ruolo dell'utente).
E' consigliabile indicare una permission per ogni azione che un utente può compiere, ad esempio:
view weather forecast:
title: '...'
description: '...'
administer weather settings:
title: '...'
description: '...'
manage weather alerts:
title: '...'
description: '...'
Custom access check
Quando una permission non è sufficiente perché l'accesso dipende da condizioni dinamiche, ad esempio la località in cui si trova l'utente o un certo intervallo temporale, la route può utilizzare:
requirements:
_custom_access: '\Drupal\nome_modulo\Access\ForecastAccessCheck::access'
Il metodo custom deve restituire un oggetto che implementa AccessResultInterface.
Aggiungere link in un menu
Per fornire un link alla route in un menu si usa il file .links.menu.yml:
nome_modulo.forecast: # Plugin ID del menu link, cioè l'identificativo univoco
title: 'Weather forecast' # il testo mostrato nel menu, è traducibile
description: 'View the current weather forecast.'
route_name: nome_modulo.forecast
menu_name: main
weight: 10
nome_modulo.external_weather:
title: 'External weather service'
url: 'https://example.com'
nome_modulo.settings:
title: 'Weather forecast'
description: 'Configure the weather forecast integration.'
route_name: nome_modulo.settings
parent: system.admin_config_services
weight: 10