URL-e w Drupalu 11.4: zamiast sklejać stringi, użyj API core
Drupal 8 ukazał się w 2015 roku, a mimo to w kodzie wciąż można znaleźć URL-e ręcznie składane ze stringów zamiast generowanych za pomocą narzędzi dostępnych w core. Takie skróty nadal powodują problemy w Drupalu 11.4.
Link zbudowany jako
'/node/' . $id . '?source=' . $source może działać
poprawnie, dopóki w serwisie nie pojawi się alias, prefiks językowy albo
wartość parametru zawierająca &. Drupal ma już API
obsługujące te przypadki. Pokazujemy, kiedy używać
Url::fromRoute(), Url::fromUri() i
Url::fromUserInput() oraz jak renderować linki bez utraty
metadanych cache.
Przykłady dotyczą Drupala 11.4 i zostały zweryfikowane na wersji core 11.4.9. Fragmenty kodu są przeznaczone do użycia we własnych modułach i wymagają działającej instalacji Drupala.
W tym artykule:- Które API do URL-i wybrać?
- Generowanie linków na podstawie tras i encji
- Zewnętrzne URI i ścieżki wpisane przez użytkownika
- Parametry zapytania, fragmenty i opcje językowe
- Renderowanie linków i sprawdzanie dostępu
- Metadane cache, gdy potrzebny jest string
- Co powinien sprawdzić zespół?
- Potrzebujesz pomocy z kodem Drupala?
Które API do URL-i wybrać?
Zacznij od tego, jakie informacje o adresie docelowym są już dostępne w kodzie. Jeżeli znana jest nazwa trasy, przekaż ją Drupalowi zamiast ręcznie odtwarzać jej aktualną ścieżkę. Jeżeli encja została już wczytana, można skorzystać z jej szablonu linku.
| Co masz do dyspozycji | Czego użyć | Przykład |
|---|---|---|
| Trasę Drupala i jej parametry | Url::fromRoute() |
entity.node.canonical z parametrem
node |
| Wczytaną encję | $entity->toUrl() |
Kanoniczna strona węzła |
| Zewnętrzne URI | Url::fromUri() |
https://example.com/docs |
| Lokalny zasób bez trasy | Url::fromUri() ze schematem base: |
base:robots.txt |
| Względną ścieżkę wpisaną przez redaktora | Url::fromUserInput() |
/contact, ?page=2 lub
#details |
Tekst linku i obiekt Url |
Link::fromTextAndUrl()->toRenderable() |
Link w tablicy renderowania |
Obiekt URL opisuje adres docelowy. Samo jego utworzenie nie potwierdza jednak, że adres jest poprawny ani że użytkownik ma dostęp do wskazanego zasobu. Ma to szczególne znaczenie w przypadku linków do stron administracyjnych oraz adresów tworzonych na podstawie danych pochodzących od użytkowników.
Generowanie linków na podstawie tras i encji
Rozważ ręcznie zbudowaną ścieżkę:
// Avoid building a known Drupal route as a string.
$path = '/node/' . $node_id;
Taki zapis uzależnia kod od konkretnej struktury ścieżki i omija mechanizm generowania URL-i w Drupalu. Zastąp go nazwą trasy i jej parametrami:
use Drupal\Core\Url;
// Example: node 42 exists on the site.
$url = Url::fromRoute('entity.node.canonical', ['node' => 42]);
Drupal może wtedy zastosować mechanizmy przetwarzania generowanych
ścieżek, w tym skonfigurowane aliasy i prefiksy językowe. Wynik zależy
od konfiguracji serwisu. Nie zakładaj, że węzeł (node) 42 zawsze będzie
dostępny pod adresem /node/42 ani że każdy język będzie
miał własny prefiks.
Jeżeli węzeł został już wczytany, pobierz jego kanoniczny URL:
// $node is a loaded Drupal\node\NodeInterface instance.
$url = $node->toUrl('canonical');
W przypadku innych typów encji sprawdź, jakie szablony linków są dla nich dostępne. Nie każda encja ma stronę kanoniczną. Generowanie URL-a nie zastępuje również sprawdzenia, czy encja istnieje i czy użytkownik może ją zobaczyć.
W Twig link do znanej trasy można utworzyć funkcją
path():
<a href="{{ path('entity.node.canonical', {'node': node.id}) }}">
{{ 'Read more'|t }}
</a>
Przykład zakłada, że szablon otrzymuje węzeł, a komponent prawidłowo
uwzględnia wymagania dotyczące dostępu i cache. Gdy potrzebny jest
bezwzględny URL trasy, użyj funkcji Twig url(). Żadna z
tych funkcji nie nadaje użytkownikowi uprawnień do strony docelowej.
Zewnętrzne URI i ścieżki wpisane przez użytkownika
Url::fromUri() stosuj wtedy, gdy masz URI, a nie nazwę
trasy:
use Drupal\Core\Url;
$documentation = Url::fromUri('https://example.com/docs');
$email = Url::fromUri('mailto:[email protected]');
$robots = Url::fromUri('base:robots.txt');
Schemat base: pozwala utworzyć adres względem ścieżki
bazowej instalacji Drupala. Jest przeznaczony dla zasobów, które nie są
obsługiwane przez routing Drupala. Nie sprawdza, czy plik rzeczywiście
istnieje.
Nie przekazuj /node/42 bezpośrednio do
fromUri() jako bezwzględnego URI: zwykła ścieżka względna
nie ma schematu. Dla względnej ścieżki wpisanej przez redaktora użyj
fromUserInput():
use Drupal\Core\Url;
$contact = Url::fromUserInput('/contact');
$next_page = Url::fromUserInput('?page=2');
$section = Url::fromUserInput('#details');
Metoda wymaga wartości zaczynającej się od /,
? albo #. Gdy istnieje pasująca trasa, może
wykorzystać routing i przetwarzanie ścieżek Drupala. Przyjmuje jednak
również lokalne ścieżki, które nie odpowiadają żadnej trasie.
Metoda ta nie służy do wykrywania niedziałających
linków. Nieprawidłowe dane należy obsłużyć podczas walidacji
formularza, zamiast wyświetlać użytkownikowi komunikat o wyjątku.
Zewnętrzne adresy wpisywane przez użytkowników muszą przejść
walidację zgodną z przeznaczeniem pola. Pole przeznaczone na adresy
internetowe powinno mieć jasno określone dozwolone schematy, zwykle
http i https. Mechanizm przekierowań może
dodatkowo wymagać listy dozwolonych hostów. fromUri()
tworzy obiekt URL, ale nie zastępuje walidacji przekierowań, kontroli
żądań do zewnętrznych serwerów ani reguł biznesowych.
Parametry zapytania, fragmenty i opcje językowe
Doklejanie '?q=' . $value powoduje problemy, gdy wartość
zawiera spacje, ampersandy lub inne znaki zastrzeżone. Przekaż
niezakodowane wartości w opcji query i pozostaw ich
kodowanie Drupalowi:
use Drupal\Core\Url;
$url = Url::fromRoute('entity.node.canonical', ['node' => 42], [
'query' => [
'search' => 'R&D / Europe',
'page' => 2,
],
'fragment' => 'technical-details',
'absolute' => TRUE,
]);
Parametry w tym przykładzie pokazują jedynie sposób budowania adresu. Samo dodanie tych parametrów nie sprawi, że strona węzła zacznie obsługiwać wyszukiwanie lub paginację. Fragment zadziała tylko wtedy, gdy na stronie docelowej istnieje element HTML o odpowiednim ID.
Nie koduj wartości parametrów samodzielnie przed przekazaniem i nie
umieszczaj # na początku opcji fragment.
UrlHelper::buildQuery() przydaje się, gdy osobna integracja
rzeczywiście potrzebuje wyłącznie ciągu parametrów zapytania. Przy
budowaniu URL-a w Drupalu użyj opcji query zamiast
samodzielnie składać ciąg parametrów.
Opcja language przyjmuje obiekt języka, a nie jego kod
przekazany jako string. W klasie serwisu ze wstrzykniętym menedżerem
języków może to wyglądać następująco:
$language = $this->languageManager->getLanguage('pl');
if ($language === NULL) {
throw new \InvalidArgumentException('Polish is not configured.');
}
$url = $node->toUrl('canonical', ['language' => $language]);
Przykład zakłada wczytany $node oraz serwis
language_manager wstrzyknięty jako
$this->languageManager, o typie
Drupal\Core\Language\LanguageManagerInterface. Wskazanie
języka dla URL-a nie tworzy brakującego tłumaczenia treści. Zanim
założysz, że URL będzie miał konkretną postać, sprawdź również
konfigurację negocjacji języka i aliasów.
Dla linków w wiadomościach e-mail lub kanałach RSS
absolute => TRUE pozwala uniknąć adresu względnego. W
zadaniach uruchamianych z wiersza poleceń trzeba również zapewnić
właściwy kontekst URI serwisu. Samo ustawienie opcji
absolute nie określi właściwej domeny produkcyjnej.
Renderowanie linków i sprawdzanie dostępu
Jeżeli link ma być wyświetlany na stronie Drupala, pozostaw jego
generowanie systemowi renderowania zamiast ręcznie składać znacznik
<a>:
use Drupal\Core\Link;
use Drupal\Core\Url;
$url = Url::fromRoute('user.login');
$build['login'] = Link::fromTextAndUrl(t('Sign in'), $url)
->toRenderable();
Zwróć $build z funkcji odpowiedzialnej za zbudowanie
danego fragmentu strony. W klasie obiektowej użyj wstrzykniętego serwisu
tłumaczeń lub odpowiedniego traitu, zamiast dodawać kolejne globalne
wywołania serwisów. W tym proceduralnym przykładzie funkcja
t() pozwala zachować krótszy zapis.
Równoważna tablica renderowania wygląda tak:
$build['login'] = [
'#type' => 'link',
'#title' => t('Sign in'),
'#url' => $url,
];
Element linku odpowiada za generowanie odnośnika i przekazywanie związanych z nim metadanych. Nie sprawdza jednak automatycznie dostępu do trasy, gdy od tego ma zależeć widoczność linku. Jeśli link ma być widoczny wyłącznie dla użytkowników z dostępem do strony docelowej, dodaj wynik tej kontroli:
$build['login']['#access'] = $url->access(NULL, TRUE);
Wartość TRUE przekazana jako drugi argument sprawia, że
metoda zwraca obiekt wyniku wraz z informacjami o cache, a nie tylko
wartość logiczną. Przekazanie tego obiektu do #access
pozwala Drupalowi uwzględnić wynik podczas przechowywania wyrenderowanej
treści w pamięci podręcznej.
Takie sprawdzenie dostępu ma znaczenie dla URL-i powiązanych z
trasami. Dla URI niepowiązanego z trasą Url::access()
zwraca dostęp jako dozwolony. Nie oznacza to jednak sprawdzenia
uprawnień w zewnętrznym systemie. Niezależnie od widoczności linku trasa
docelowa nadal musi samodzielnie egzekwować kontrolę dostępu podczas
obsługi żądania.
Metadane cache, gdy potrzebny jest string
Niektóre integracje wymagają zwykłego ciągu znaków z adresem.
Url::toString() generuje taki ciąg, ale przy domyślnym
wywołaniu zwracany string nie zawiera metadanych cache. Jeżeli taki
string trafi do treści buforowanej przez Drupala, trzeba osobno zebrać i
przekazać związane z nim metadane cache:
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) zwraca obiekt GeneratedUrl.
W przykładzie adres jest wyświetlany jako zwykły tekst, a metadane
zebrane podczas jego generowania są przekazywane do tablicy
renderowania. W bardziej złożonym komponencie należy połączyć te
metadane z istniejącymi informacjami o cache, zamiast je zastępować.
Ten kod uwzględnia tylko metadane związane z generowaniem URL-a, a
nie wszystkie zależności komponentu. Jeżeli wynik zależy również od
etykiety encji, kontroli dostępu lub konfiguracji, trzeba osobno
uwzględnić związane z nimi metadane cache. Dla zwykłego klikalnego
odnośnika prostszym wyborem pozostaje toRenderable().
Nie myl Url::toString() z Link::toString().
Pierwsza metoda generuje URL, druga kod HTML linku. Dokumentacja klasy
Link w core zaleca użycie toRenderable() przy
renderowaniu HTML, aby zachować informacje o cache w systemie
renderowania Drupala.
Co powinien sprawdzić zespół?
Właściwe API eliminuje potrzebę ręcznego składania adresów, ale podczas przeglądu kodu nadal trzeba uwzględnić konfigurację serwisu. Przed scaleniem zmian warto sprawdzić następujące przypadki:
- Aliasy i języki: węzeł z aliasem, drugi język URL-a i brakujące tłumaczenie.
- Parametry zapytania: spacje,
&, znaki spoza ASCII oraz przypadki pozwalające wykryć podwójne kodowanie. - Kontekst instalacji: serwis w podkatalogu i bezwzględne linki generowane z wiersza poleceń.
- Dostęp i cache: użytkownicy z różnymi uprawnieniami, również po zapisaniu wyniku w pamięci podręcznej i przełączeniu na innego użytkownika.
- Dane wejściowe: poprawne lokalne odwołania, nieistniejące strony docelowe oraz wartości odrzucane przez reguły dotyczące dozwolonych schematów lub hostów.
- HTML: etykiety linków zawierające znaki
<i&oraz sprawdzenie, czy surowy kod HTML nie omija mechanizmu bezpiecznego kodowania znaków.
Własne reguły walidacji sprawdzaj testami jednostkowymi. Wygenerowane linki, aliasy, kontrolę dostępu i wyrenderowaną treść weryfikuj testami funkcjonalnymi. Nie wpisuj na sztywno jednej oczekiwanej ścieżki, jeśli testowane zachowanie powinno uwzględniać konfigurowalne aliasy.
Zasadę dla zespołu można streścić krótko: używaj tras dla znanych stron Drupala, szablonów linków dla wczytanych encji, URI dla adresów bez trasy oraz tablic renderowania dla linków wyświetlanych na stronie. Konwersję do stringa stosuj wtedy, gdy wymaga tego konkretny interfejs, a nie jako domyślny sposób budowania HTML.
Warto również przeczytać artykuł Łączenie stringów i zmiennych w PHP 8+: przykłady i częste błędy.
Potrzebujesz pomocy z kodem Drupala?
Ścieżki zapisane na sztywno, niespójna kontrola dostępu i linki działające inaczej po zmianie języka to konkretne obszary, które warto objąć przeglądem kodu. Zespół Droptica zajmujący się rozwojem stron na Drupalu może pomóc uporządkować moduły niestandardowe i zweryfikować ich działanie w serwisie. Porozmawiajmy o zakresie przeglądu kodu i zmianach, które warto wprowadzić w projekcie.