Drupal 11.4 URLs: Core-APIs statt zusammengesetzter Strings
Drupal 8 erschien 2015, und trotzdem enthält Drupal-Code noch immer URLs, die aus Strings zusammengesetzt werden, statt die Werkzeuge aus dem Core zu nutzen. Diese Abkürzungen verursachen auch in Drupal 11.4 Probleme.
Ein Link wie '/node/' . $id . '?source=' . $source kann
so lange korrekt aussehen, bis die Website einen Alias, ein Sprachpräfix
oder einen Query-Wert mit & verwendet. Für genau diese
Fälle bringt Drupal bereits APIs mit. In diesem Artikel zeige ich, wann
Sie Url::fromRoute(), Url::fromUri() und
Url::fromUserInput() einsetzen und wie Sie Links rendern,
ohne Cache-Metadaten zu verlieren.
Die Beispiele beziehen sich auf Drupal 11.4 und wurden mit dem Core-Release Drupal 11.4.9 geprüft. Es handelt sich um Fragmente für eigene Module, die innerhalb einer gebooteten Drupal-Anwendung laufen.
In diesem Artikel:- Welche URL-API sollten Sie verwenden?
- Links aus Routen und Entitäten erzeugen
- Externe URIs und von Redakteuren eingegebene Pfade
- Query-Parameter, Fragmente und Sprachoptionen übergeben
- Links rendern und Zugriff prüfen
- Cache-Metadaten erhalten, wenn ein String nötig ist
- Was sollte Ihr Team testen?
- Unterstützung bei eigenem Drupal-Code
Welche URL-API sollten Sie verwenden?
Gehen Sie davon aus, welche Informationen Ihr Code über das Ziel hat. Wenn ein Routenname bekannt ist, übergeben Sie diesen Namen an Drupal, statt den aktuellen Pfad der Route nachzubauen. Wenn bereits eine Entität vorliegt, verwenden Sie deren Link-Template.
| Was Sie haben | Verwenden | Beispiel |
|---|---|---|
| Eine Drupal-Route und ihre Parameter | Url::fromRoute() |
entity.node.canonical mit dem Parameter
node |
| Eine geladene Entität | $entity->toUrl() |
Die kanonische Seite eines Nodes |
| Eine externe URI | Url::fromUri() |
https://example.com/docs |
| Eine bekannte lokale Ressource ohne Route | Url::fromUri() mit base: |
base:robots.txt |
| Ein relativer Pfad, den ein Redakteur eingegeben hat | Url::fromUserInput() |
/contact, ?page=2 oder
#details |
Linktext und ein Url-Objekt |
Link::fromTextAndUrl()->toRenderable() |
Ein Link in einem Render-Array |
Ein URL-Objekt beschreibt ein Ziel. Es genehmigt dieses Ziel nicht und gewährt auch keinen Zugriff darauf. Dieser Unterschied ist wichtig bei Links auf Verwaltungsseiten und bei Werten, die von Nutzern stammen.
Links aus Routen und Entitäten erzeugen
Betrachten Sie diesen manuell zusammengesetzten Pfad:
// Avoid building a known Drupal route as a string.
$path = '/node/' . $node_id;
Er schreibt die Pfadstruktur fest und umgeht die URL-Erzeugung von Drupal. Ersetzen Sie ihn durch den Routennamen und die Parameter:
use Drupal\Core\Url;
// Example: node 42 exists on the site.
$url = Url::fromRoute('entity.node.canonical', ['node' => 42]);
Drupal kann dann die ausgehende Pfadverarbeitung der Website
anwenden, einschließlich konfigurierter Aliase und Sprachpräfixe. Der
resultierende Pfad hängt von der Konfiguration ab: Gehen Sie nicht davon
aus, dass Node 42 immer zu /node/42 wird oder dass immer
ein Sprachpräfix vorhanden ist.
Wenn Sie einen Node bereits geladen haben, fragen Sie die Entität nach ihrer kanonischen URL:
// $node is a loaded Drupal\node\NodeInterface instance.
$url = $node->toUrl('canonical');
Prüfen Sie bei anderen Entitätstypen, welche Link-Templates sie bereitstellen. Nicht jede Entität hat eine kanonische Seite, und das Erzeugen einer URL ersetzt nicht die Prüfung, ob eine Entität existiert oder ob der aktuelle Nutzer sie sehen darf.
In Twig lässt sich eine bekannte Route mit path()
verlinken:
<a href="{{ path('entity.node.canonical', {'node': node.id}) }}">
{{ 'Read more'|t }}
</a>
Das setzt voraus, dass das Template einen Node erhält und die
umgebende Komponente die Anforderungen an Zugriff und Cachebarkeit
erfüllt. Verwenden Sie die Twig-Funktion url(), wenn Sie
eine absolute Routen-URL brauchen. Keine der beiden Funktionen macht ein
Ziel für einen Nutzer ohne Berechtigung zugänglich.
Externe URIs und von Redakteuren eingegebene Pfade
Url::fromUri() ist die richtige Wahl, wenn Ihr Ziel eine
URI und kein Routenname ist:
use Drupal\Core\Url;
$documentation = Url::fromUri('https://example.com/docs');
$email = Url::fromUri('mailto:[email protected]');
$robots = Url::fromUri('base:robots.txt');
Das Schema base: erzeugt eine URL relativ zum Basispfad
der Drupal-Installation. Es ist für Ressourcen gedacht, von denen Sie
wissen, dass das Routing-System von Drupal sie nicht verarbeitet. Ob die
Datei existiert, prüft es nicht.
Übergeben Sie /node/42 nicht direkt an
fromUri(), als wäre es eine absolute URI: Ein gewöhnlicher
relativer Pfad hat kein Schema. Für eine relative Referenz, die ein
Redakteur eingegeben hat, verwenden Sie stattdessen
fromUserInput():
use Drupal\Core\Url;
$contact = Url::fromUserInput('/contact');
$next_page = Url::fromUserInput('?page=2');
$section = Url::fromUserInput('#details');
Diese Methode verlangt einen Wert, der mit /,
? oder # beginnt. Sie kann einen lokalen Pfad
über das Routing und die Pfadverarbeitung von Drupal auflösen, wenn eine
passende Route existiert. Sie akzeptiert aber auch lokale Pfade, zu
denen es keine Route gibt. Sie ist kein Werkzeug zur Prüfung
defekter Links. Behandeln Sie ungültige Eingaben in der
Formularvalidierung, statt eine Exception bis zum Besucher
durchzureichen.
Wenden Sie bei externen Zielen, die Nutzer angeben, zuerst die
vorgesehenen Regeln des Feldes an und erzeugen Sie erst danach die URL.
Ein Feld, das nur Weblinks zulassen soll, sollte die gewünschten
Schemata ausdrücklich erlauben, in der Regel http und
https. Ein Weiterleitungsprozess braucht unter Umständen
zusätzlich eine Allowlist für Hosts. fromUri() ist ein
Konstruktor und kein allgemeiner Validator für Weiterleitungen, externe
Anfragen oder Geschäftsregeln.
Query-Parameter, Fragmente und Sprachoptionen übergeben
Ein angehängtes '?q=' . $value wird mehrdeutig, sobald
der Wert Leerzeichen, kaufmännische Und-Zeichen oder andere reservierte
Zeichen enthält. Übergeben Sie unkodierte Werte in der Option
query und überlassen Sie die Kodierung Drupal:
use Drupal\Core\Url;
$url = Url::fromRoute('entity.node.canonical', ['node' => 42], [
'query' => [
'search' => 'R&D / Europe',
'page' => 2,
],
'fragment' => 'technical-details',
'absolute' => TRUE,
]);
Die Query oben zeigt nur, wie die URL aufgebaut wird. Eine Node-Seite unterstützt nicht automatisch Suche oder Paginierung, nur weil diese Parameter vorhanden sind. Ein Fragment braucht außerdem eine passende HTML-Element-ID auf der Zielseite.
Kodieren Sie die Query-Werte nicht vorab und setzen Sie kein
# in die Option fragment.
UrlHelper::buildQuery() von Drupal ist nützlich, wenn eine
separate Integration tatsächlich nur einen Query-String braucht. Für
eine Drupal-URL ist die Option query besser, als den String
selbst zusammenzusetzen.
Die Option language erwartet ein Sprachobjekt, keinen
Sprachcode als String. In einem Service mit injiziertem Language Manager
kann das so aussehen:
$language = $this->languageManager->getLanguage('pl');
if ($language === NULL) {
throw new \InvalidArgumentException('Polish is not configured.');
}
$url = $node->toUrl('canonical', ['language' => $language]);
Das Beispiel setzt voraus, dass $node geladen ist und
der Service language_manager als
$this->languageManager injiziert wurde, typisiert als
Drupal\Core\Language\LanguageManagerInterface. Die
Sprachauswahl in der URL legt keine fehlende Übersetzung des Inhalts an.
Prüfen Sie außerdem die Spracherkennung und die Aliase der Website,
bevor Sie eine bestimmte Ausgabe erwarten.
Bei Links in E-Mails oder Feeds verhindert
absolute => TRUE ein relatives Ziel. Stellen Sie bei
Kommandozeilen-Jobs sicher, dass die Anwendung den richtigen
Site-URI-Kontext hat. Eine absolute URL anzufordern, erzeugt nicht von
selbst den gewünschten Produktions-Hostnamen.
Links rendern und Zugriff prüfen
Wenn ein Link auf einer Drupal-Seite erscheint, lassen Sie ihn im
Render-System, statt ein <a>-Tag
zusammenzusetzen:
use Drupal\Core\Link;
use Drupal\Core\Url;
$url = Url::fromRoute('user.login');
$build['login'] = Link::fromTextAndUrl(t('Sign in'), $url)
->toRenderable();
Geben Sie $build aus dem passenden Build-Callback
zurück. Verwenden Sie in einer objektorientierten Klasse den injizierten
Übersetzungsservice oder den Translation-Trait, statt weitere globale
Serviceaufrufe einzuführen. Hier hält t() das prozedurale
Beispiel kurz.
Das entsprechende Render-Array sieht so aus:
$build['login'] = [
'#type' => 'link',
'#title' => t('Sign in'),
'#url' => $url,
];
Ein Link-Render-Element übernimmt die Erzeugung des Links und die zugehörigen Bubbleable-Metadaten. Die Zugriffsprüfung der Route, die Sie für eine bedingte Anzeige brauchen, führt es jedoch nicht automatisch durch. Wenn der Link nur erscheinen soll, wenn der aktuelle Nutzer auf das Ziel zugreifen darf, ergänzen Sie ein Zugriffsergebnis:
$build['login']['#access'] = $url->access(NULL, TRUE);
Das zweite Argument fordert ein Zugriffsergebnis-Objekt mit seinen
Cachebarkeits-Informationen an, statt nur eines booleschen Werts. Wenn
Sie dieses Objekt über #access übergeben, kann Drupal das
Ergebnis beim Caching der gerenderten Ausgabe berücksichtigen.
Diese Prüfung ist bei URLs mit Route sinnvoll. Für eine URI ohne
Route gibt Url::access() ein erlaubtes Ergebnis zurück. Die
Berechtigungen eines externen Servers prüft es nicht. Die Zielroute muss
ihre eigenen Zugriffsregeln bei jeder Anfrage durchsetzen, unabhängig
davon, ob ein Link sichtbar ist.
Cache-Metadaten erhalten, wenn ein String nötig ist
Manche Integrationen brauchen eine URL als einfachen String.
Url::toString() liefert ihn, doch der Standard-Rückgabewert
enthält keine Cache-Metadaten. Wenn dieser String Teil gecachter
Drupal-Ausgabe wird, sammeln Sie die Metadaten und geben Sie sie
ausdrücklich weiter:
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) gibt ein GeneratedUrl-Objekt
zurück. Das Beispiel zeigt damit eine Adresse als reinen Text an und
behält dabei die Metadaten, die bei der URL-Erzeugung gesammelt wurden.
Führen Sie sie in einem komplexeren Build mit den vorhandenen
Cachebarkeits-Informationen der Komponente zusammen, statt diese zu
ersetzen.
Das deckt die Metadaten aus der URL-Erzeugung ab, nicht jede
Abhängigkeit der umgebenden Komponente. Hängt die Ausgabe auch von einem
Entitätslabel, einem Zugriffsergebnis oder einem Konfigurationsobjekt
ab, nehmen Sie diese Abhängigkeiten ebenfalls auf. Für einen normalen
klickbaren Link bleibt toRenderable() die einfachere
Wahl.
Verwechseln Sie Url::toString() nicht mit
Link::toString(). Die erste Methode erzeugt eine URL, die
zweite Link-Markup. Die Link-Dokumentation im Core
empfiehlt toRenderable() für die HTML-Ausgabe, damit die
Cache-Informationen in der Render-Pipeline von Drupal bleiben.
Was sollte Ihr Team testen?
Die richtige API ersetzt das manuelle Zusammensetzen von Strings, doch ein Review muss trotzdem die Konfiguration der Website abdecken. Diese Fälle würde ich vor dem Merge einer Änderung prüfen:
- Aliase und Sprachen: ein Node mit Pfad-Alias, eine zweite URL-Sprache und eine fehlende Übersetzung.
- Query-Werte: Leerzeichen,
&, Nicht-ASCII-Zeichen und Werte, an denen doppelte Kodierung sichtbar wird. - Installationskontext: eine Website in einem Unterverzeichnis und absolute Links aus einem Kommandozeilen-Job.
- Zugriff und Caching: Nutzer mit unterschiedlichen Berechtigungen, auch bei bereits gefülltem Render-Cache nach einem Nutzerwechsel.
- Nutzereingaben: gültige lokale Referenzen, ein fehlendes Ziel und Werte, die die Schema- oder Host-Regeln des Feldes ablehnen.
- Markup: Linkbeschriftungen mit Zeichen wie
<oder&, ohne das Escaping durch rohes Markup zu umgehen.
Verwenden Sie Unit-Tests für Ihre eigenen Eingaberegeln und funktionale Tests für erzeugte Links, Aliase, Zugriff und gerenderte Ausgabe. Prüfen Sie nicht auf einen festen Pfad, wenn das getestete Verhalten konfigurierbare Aliase berücksichtigen soll.
Die Regel für das Team ist kurz: Routen für bekannte Drupal-Seiten, Link-Templates für geladene Entitäten, URIs für Ziele ohne Route und Render-Arrays für Links auf Seiten. Die Umwandlung in einen String sollte eine bewusste Grenze sein, nicht der Standardweg, HTML zu erzeugen.
Lesen Sie auch: Strings und Variablen in PHP 8+ verbinden: Beispiele und typische Fehler.
Unterstützung bei eigenem Drupal-Code
Wenn Ihre Module fest codierte Pfade, uneinheitliche Zugriffsprüfungen oder gecachte Links enthalten, die sich je nach Sprache unterschiedlich verhalten, lohnt sich ein gezieltes Code-Review. Das Drupal-Entwicklungsteam von Droptica unterstützt Sie beim Refactoring eigener Module und prüft ihr Verhalten auf Ihrer Website. Kontaktieren Sie uns, um über den Code zu sprechen, den Sie verbessern möchten.