URL dans Drupal 11.4: utilisez les API du core, pas des chaînes
Drupal 8 est sorti en 2015, et pourtant le code Drupal contient encore des URL assemblées en concaténant des chaînes au lieu d’utiliser les outils du core. Ces raccourcis posent toujours problème dans Drupal 11.4.
Un lien construit comme
'/node/' . $id . '?source=' . $source peut sembler correct
jusqu’au jour où le site utilise un alias, un préfixe de langue ou une
valeur de requête contenant &. Drupal dispose déjà
d’API pour ces cas. Dans cet article, je montre quand utiliser
Url::fromRoute(), Url::fromUri() et
Url::fromUserInput(), puis comment afficher des liens sans
perdre les métadonnées de cache.
Les exemples ciblent Drupal 11.4 et ont été vérifiés avec la version 11.4.9 du core. Ce sont des fragments pour des modules personnalisés exécutés dans une application Drupal amorcée.
Dans cet article:- Quelle API d’URL utiliser?
- Générer des liens à partir de routes et d’entités
- URI externes et chemins saisis par les rédacteurs
- Paramètres de requête, fragments et options de langue
- Afficher des liens et vérifier l’accès
- Conserver les métadonnées de cache quand une chaîne est nécessaire
- Que doit tester votre équipe?
- Besoin d’aide pour votre code Drupal personnalisé?
Quelle API d’URL utiliser?
Partez de ce que votre code sait de la destination. S’il connaît un nom de route, transmettez ce nom à Drupal au lieu de reconstruire le chemin actuel de la route. S’il dispose déjà d’une entité, utilisez le modèle de lien de cette entité.
| Ce que vous avez | À utiliser | Exemple |
|---|---|---|
| Une route Drupal et ses paramètres | Url::fromRoute() |
entity.node.canonical avec un paramètre
node |
| Une entité chargée | $entity->toUrl() |
La page canonique d’un nœud |
| Une URI externe | Url::fromUri() |
https://example.com/docs |
| Une ressource locale connue sans route | Url::fromUri() avec base: |
base:robots.txt |
| Un chemin relatif saisi par un rédacteur | Url::fromUserInput() |
/contact, ?page=2 ou
#details |
Un texte de lien et un objet Url |
Link::fromTextAndUrl()->toRenderable() |
Un lien dans un tableau de rendu |
Un objet URL représente une destination. À lui seul, il ne valide pas cette destination et n’accorde aucun accès. Cette distinction compte pour les liens vers des pages d’administration et pour les valeurs fournies par les utilisateurs.
Générer des liens à partir de routes et d’entités
Prenons ce chemin assemblé à la main:
// Avoid building a known Drupal route as a string.
$path = '/node/' . $node_id;
Il fige la structure du chemin et contourne la génération d’URL de Drupal. Remplacez-le par le nom de la route et ses paramètres:
use Drupal\Core\Url;
// Example: node 42 exists on the site.
$url = Url::fromRoute('entity.node.canonical', ['node' => 42]);
Drupal peut alors appliquer le traitement des chemins sortants du
site, y compris les alias et les préfixes de langue configurés. Le
chemin obtenu dépend de la configuration: ne partez pas du principe que
le nœud 42 devient toujours /node/42 ni qu’un préfixe de
langue est toujours présent.
Si vous avez déjà chargé un nœud, demandez à l’entité son URL canonique:
// $node is a loaded Drupal\node\NodeInterface instance.
$url = $node->toUrl('canonical');
Pour les autres types d’entités, vérifiez quels modèles de lien ils fournissent. Toutes les entités n’ont pas de page canonique, et générer une URL ne remplace pas la vérification de l’existence de l’entité ni du droit de l’utilisateur actuel à la consulter.
Dans Twig, une route connue peut être liée avec
path():
<a href="{{ path('entity.node.canonical', {'node': node.id}) }}">
{{ 'Read more'|t }}
</a>
Cela suppose que le template reçoit un nœud et que le composant qui
l’entoure gère ses exigences d’accès et de mise en cache. Utilisez la
fonction Twig url() lorsque vous avez besoin d’une URL de
route absolue. Aucune de ces deux fonctions ne rend une destination
accessible à un utilisateur qui n’en a pas la permission.
URI externes et chemins saisis par les rédacteurs
Url::fromUri() convient lorsque votre destination est
une URI et non un nom de route:
use Drupal\Core\Url;
$documentation = Url::fromUri('https://example.com/docs');
$email = Url::fromUri('mailto:[email protected]');
$robots = Url::fromUri('base:robots.txt');
Le schéma base: construit une URL relative au chemin de
base de l’installation Drupal. Il est prévu pour les ressources dont
vous savez qu’elles ne passent pas par le système de routage de Drupal.
Il ne vérifie pas que le fichier existe.
Ne passez pas /node/42 directement à
fromUri() comme s’il s’agissait d’une URI absolue: un
chemin relatif ordinaire n’a pas de schéma. Pour une référence relative
saisie par un rédacteur, utilisez plutôt
fromUserInput():
use Drupal\Core\Url;
$contact = Url::fromUserInput('/contact');
$next_page = Url::fromUserInput('?page=2');
$section = Url::fromUserInput('#details');
Cette méthode exige une valeur qui commence par /,
? ou #. Elle peut résoudre un chemin local via
le routage et le traitement des chemins de Drupal lorsqu’une route
correspondante existe, mais elle accepte aussi des chemins locaux qui ne
correspondent à aucune route. Ce n’est pas un outil de détection
de liens cassés. Traitez les saisies invalides dans la
validation du formulaire plutôt que de laisser une exception atteindre
le visiteur.
Pour les destinations externes fournies par les utilisateurs,
appliquez d’abord les règles prévues pour le champ, puis construisez
l’URL. Un champ limité aux liens web doit autoriser explicitement les
schémas voulus, en général http et https. Un
processus de redirection peut aussi nécessiter une liste d’hôtes
autorisés. fromUri() est un constructeur, pas un validateur
général pour les redirections, les requêtes distantes ou les règles
métier.
Paramètres de requête, fragments et options de langue
Ajouter '?q=' . $value crée une ambiguïté dès que la
valeur contient des espaces, des esperluettes ou d’autres caractères
réservés. Passez des valeurs non encodées dans l’option
query et laissez Drupal les encoder:
use Drupal\Core\Url;
$url = Url::fromRoute('entity.node.canonical', ['node' => 42], [
'query' => [
'search' => 'R&D / Europe',
'page' => 2,
],
'fragment' => 'technical-details',
'absolute' => TRUE,
]);
La requête ci-dessus illustre uniquement la construction de l’URL: une page de nœud ne gère pas automatiquement la recherche ou la pagination parce que ces paramètres sont présents. Un fragment nécessite aussi un ID d’élément HTML correspondant sur la page de destination.
N’encodez pas les valeurs de requête à l’avance et n’incluez pas
# dans l’option fragment.
UrlHelper::buildQuery() de Drupal est utile lorsqu’une
intégration distincte a réellement besoin d’une simple chaîne de
requête. Pour une URL Drupal, préférez l’option query à
l’assemblage manuel de cette chaîne.
L’option language attend un objet langue, pas un code de
langue sous forme de chaîne. Dans un service où le gestionnaire de
langues est injecté, cela peut ressembler à ceci:
$language = $this->languageManager->getLanguage('pl');
if ($language === NULL) {
throw new \InvalidArgumentException('Polish is not configured.');
}
$url = $node->toUrl('canonical', ['language' => $language]);
Cet exemple suppose que $node est chargé et que le
service language_manager est injecté dans
$this->languageManager, typé
Drupal\Core\Language\LanguageManagerInterface. Choisir la
langue de l’URL ne crée pas une traduction de contenu manquante.
Vérifiez aussi la négociation de langue et les alias du site avant
d’attendre un résultat précis.
Pour les liens utilisés dans des e-mails ou des flux,
absolute => TRUE évite une destination relative. Dans
les tâches en ligne de commande, assurez-vous que l’application dispose
du bon contexte d’URI du site: demander une URL absolue n’invente pas le
nom d’hôte de production voulu.
Afficher des liens et vérifier l’accès
Lorsqu’un lien apparaît sur une page Drupal, gardez-le dans le
système de rendu au lieu de concaténer une balise
<a>:
use Drupal\Core\Link;
use Drupal\Core\Url;
$url = Url::fromRoute('user.login');
$build['login'] = Link::fromTextAndUrl(t('Sign in'), $url)
->toRenderable();
Renvoyez $build depuis le callback de construction
concerné. Dans une classe orientée objet, utilisez le service de
traduction injecté ou le trait de traduction plutôt que d’ajouter
d’autres appels globaux à des services. Ici, t() garde
l’exemple procédural court.
Le tableau de rendu équivalent est le suivant:
$build['login'] = [
'#type' => 'link',
'#title' => t('Sign in'),
'#url' => $url,
];
Un élément de rendu de type lien gère la génération du lien et les métadonnées « bubbleable » associées. En revanche, il n’effectue pas automatiquement la vérification d’accès à la route nécessaire pour un affichage conditionnel. Si le lien ne doit apparaître que lorsque l’utilisateur actuel peut accéder à la destination, ajoutez un résultat d’accès:
$build['login']['#access'] = $url->access(NULL, TRUE);
Le second argument demande un objet de résultat d’accès, avec ses
informations de mise en cache, plutôt qu’un simple booléen. En
transmettant cet objet via #access, vous permettez à Drupal
de tenir compte du résultat lors de la mise en cache du rendu.
Cette vérification a du sens pour les URL routées. Pour une URI non
routée, Url::access() renvoie un résultat autorisé: il ne
vérifie pas les permissions d’un serveur distant. La route de
destination doit toujours appliquer ses propres règles d’accès
lorsqu’elle est demandée, que le lien soit visible ou non.
Conserver les métadonnées de cache quand une chaîne est nécessaire
Certaines intégrations exigent une URL sous forme de simple chaîne.
Url::toString() en produit une, mais sa valeur de retour
par défaut ne contient pas de métadonnées de cache. Si cette chaîne fait
partie d’une sortie Drupal mise en cache, collectez les métadonnées et
propagez-les explicitement:
use Drupal\Core\Url;
$url = Url::fromRoute('entity.node.canonical', ['node' => 42]);
$generated = $url->toString(TRUE);
$build = [
'#type' => 'container',
'address' => [
'#plain_text' => $generated->getGeneratedUrl(),
],
];
$generated->applyTo($build);
toString(TRUE) renvoie un objet
GeneratedUrl. L’exemple l’utilise pour afficher une adresse
en texte brut tout en conservant les métadonnées collectées pendant la
génération de l’URL. Dans un rendu plus complexe, fusionnez-les avec les
informations de mise en cache existantes du composant au lieu de les
remplacer.
Cela couvre les métadonnées issues de la génération d’URL, pas toutes
les dépendances du composant qui l’entoure. Si la sortie dépend aussi du
libellé d’une entité, d’un résultat d’accès ou d’un objet de
configuration, ajoutez également ces dépendances. Pour un lien cliquable
ordinaire, toRenderable() reste le choix le plus
simple.
Ne confondez pas Url::toString() et
Link::toString(). La première méthode génère une URL, la
seconde génère le balisage du lien. La documentation de
Link dans le core recommande toRenderable()
pour le rendu HTML, afin que les informations de cache restent dans le
pipeline de rendu de Drupal.
Que doit tester votre équipe?
La bonne API supprime l’assemblage manuel de chaînes, mais une revue doit tout de même couvrir la configuration du site. Je vérifierais ces cas avant de fusionner une modification:
- Alias et langues: un nœud avec un alias de chemin, une deuxième langue d’URL et une traduction manquante.
- Valeurs de requête: espaces,
&, caractères non ASCII et valeurs qui révéleraient un double encodage. - Contexte d’installation: un site servi depuis un sous-répertoire et des liens absolus générés par une tâche en ligne de commande.
- Accès et cache: des utilisateurs avec des permissions différentes, y compris avec un cache de rendu déjà rempli après un changement d’utilisateur.
- Saisie utilisateur: des références locales valides, une destination manquante et des valeurs rejetées par les règles de schéma ou d’hôte du champ.
- Balisage: des libellés de lien contenant des
caractères comme
<ou&, sans contourner l’échappement avec du balisage brut.
Utilisez des tests unitaires pour vos propres règles de saisie et des tests fonctionnels pour les liens générés, les alias, l’accès et le rendu. N’attendez pas un chemin fixe lorsque le comportement testé doit respecter des alias configurables.
La règle pour l’équipe est courte: des routes pour les pages Drupal connues, des modèles de lien pour les entités chargées, des URI pour les destinations sans route et des tableaux de rendu pour les liens dans les pages. La conversion en chaîne doit être une frontière délibérée, pas la manière par défaut de produire du HTML.
À lire aussi: Concaténer des chaînes et variables en PHP 8+: exemples et erreurs fréquentes.
Besoin d’aide pour votre code Drupal personnalisé?
Si vos modules contiennent des chemins codés en dur, des vérifications d’accès incohérentes ou des liens mis en cache qui se comportent différemment selon la langue, ces points méritent une revue de code ciblée. L’équipe de développement Drupal de Droptica peut vous aider à refactoriser vos modules personnalisés et à vérifier leur comportement sur votre site. Contactez-nous pour parler du code que vous souhaitez améliorer.