5

Dzień 5 z 18

Routing

Opublikowany

Pogłębiamy routing: jak Symfony dopasowuje URL do akcji, jak definiować wymagania parametrów i metody HTTP, jak generować adresy z nazw tras i debugować routing.

Czego się nauczysz

  • Jak działa routing — od URL do akcji
  • Konfiguracja config/routes.yaml (type: attribute)
  • Parametry ścieżki {id} i EntityValueResolver
  • Wymagania parametrów (requirements) z regex
  • Ograniczanie metod HTTP (methods)
  • Generowanie URL: path()/url(), generateUrl(), redirectToRoute()
  • Debugowanie tras: debug:router i router:match

Co zmieniło się w Symfony 8 vs 4.2

  • Atrybut #[Route] zamiast adnotacji i YAML
  • config/routes.yaml z type: attribute zamiast annotations.yaml
  • Wbudowany EntityValueResolver zamiast @ParamConverter
  • Wymagania jako nazwany argument atrybutu

Dzień 5: Routing

W Dniu 4 zbudowaliśmy dwie akcje (list i show) i połączyliśmy je linkami przez funkcję path(). Dziś zrozumiemy, jak to działa: jak Symfony dopasowuje adres URL do akcji, jak definiować wymagania i metody HTTP, jak generować adresy z nazw tras i jak debugować routing.

W Symfony 8 routing definiujemy atrybutami #[Route] bezpośrednio przy akcjach. Plik config/routes.yaml jedynie wskazuje Symfony, gdzie te atrybuty szukać (type: attribute).


Jak działa routing

Gdy klikniesz ofertę na stronie głównej, adres wygląda tak: /job/1. Skąd Symfony wie, którą akcję wywołać i skąd bierze obiekt Job?

Cała ścieżka żądania:

  1. Żądanie trafia do front controllera public/index.php.
  2. Router dopasowuje ścieżkę URL (/job/1) do trasy zdefiniowanej atrybutem #[Route].
  3. Symfony wywołuje odpowiednią akcję kontrolera, przekazując parametry z URL (id = 1).
  4. EntityValueResolver na podstawie id wczytuje obiekt Job i wstrzykuje go do akcji.

Adresy URL generujemy odwrotnie — z nazwy trasy (np. job_show) funkcją path(). Dzięki temu nigdy nie wpisujemy ścieżek na sztywno.


Konfiguracja routingu

W Symfony 8 trasy definiujemy atrybutami przy akcjach, a plik config/routes.yaml mówi tylko, skąd je wczytać:

# config/routes.yaml
controllers:
    resource:
        path: ../src/Controller/
        namespace: App\Controller
    type: attribute

Ten wpis ładuje trasy ze wszystkich kontrolerów w src/Controller/ (i podkatalogach), opisanych atrybutami.

Zmiana vs Symfony 4.2: oryginał używał pliku config/routes/annotations.yaml z type: annotation (adnotacje w DocBlockach). Dziś jest to config/routes.yaml z type: attribute — trasy to natywne atrybuty PHP 8, a SensioFrameworkExtraBundle nie jest potrzebny.


Trasy w JobController

Przypomnijmy definicje z Dnia 4 — cała konfiguracja trasy mieści się w atrybucie nad akcją:

#[Route('/', name: 'job_list', methods: ['GET'])]
public function list(JobRepository $jobs): Response
{
    // ...
}

#[Route('/job/{id}', name: 'job_show', methods: ['GET'], requirements: ['id' => '\d+'])]
public function show(Job $job): Response
{
    // ...
}

Zmienną w ścieżce zapisujemy w klamrach: {id}. Dla /job/1 parametr id przyjmie wartość 1. Nazwa trasy (name) służy do generowania URL-i — to jej używamy w path().

Konwencja nazw: trasy nazywamy małymi literami z podkreśleniem, np. job_list, job_show. Generator make:controller domyślnie dodaje prefiks app_ (np. app_job_list) — obie konwencje są poprawne, ważne aby trzymać się jednej.


Prefiks na poziomie klasy

Gdy wiele tras dzieli wspólny prefiks ścieżki i nazwy, można go wynieść na poziom klasy:

#[Route('/admin', name: 'admin_')]
final class DashboardController extends AbstractController
{
    #[Route('/jobs', name: 'jobs')]     // ścieżka: /admin/jobs,  nazwa: admin_jobs
    public function jobs(): Response { /* ... */ }
}

W naszym JobController nie stosujemy prefiksu klasowego, bo lista ofert jest stroną główną (/), a szczegóły mają ścieżkę /job/{id}. Prefiks przyda się później, np. w sekcji administracyjnej (/admin).


Wymagania parametrów (requirements)

Router waliduje parametry wyrażeniami regularnymi. W trasie job_show wymusiliśmy, aby id było liczbą:

#[Route('/job/{id}', name: 'job_show', requirements: ['id' => '\d+'])]

Dzięki \d+ trasa dopasuje /job/1, ale nie /job/abc — w tym drugim przypadku router jej nie dopasuje (i zwróci 404 lub sprawdzi kolejne trasy).

Zmiana vs Symfony 4.2: wymagania podajemy jako nazwany argument atrybutu (requirements: ['id' => '\d+']) zamiast składni adnotacji requirements={"id" = "\d+"}.


Metody HTTP

Trasę można ograniczyć do wybranych metod HTTP. Nasze strony tylko wyświetlają dane, więc akceptujemy wyłącznie GET:

#[Route('/', name: 'job_list', methods: ['GET'])]
#[Route('/job/{id}', name: 'job_show', methods: ['GET'], requirements: ['id' => '\d+'])]

Gdy dojdziemy do formularzy (dodawanie oferty), dodamy trasy z methods: ['GET', 'POST'].


Generowanie adresów URL

Zamiast wpisywać ścieżki ręcznie, budujemy je z nazwy trasy. Dzięki temu zmiana ścieżki w atrybucie nie wymaga poprawiania linków w całej aplikacji.

W szablonie Twig:

{# ścieżka względna: /job/1 #}
<a href="{{ path('job_show', { id: job.id }) }}">{{ job.position }}</a>

{# adres absolutny: http://localhost:8090/job/1 #}
<a href="{{ url('job_show', { id: job.id }) }}">...</a>

W kontrolerze:

// przekierowanie do innej trasy
return $this->redirectToRoute('job_list');

// samo wygenerowanie URL-a
$url = $this->generateUrl('job_show', ['id' => $job->getId()]);

W dowolnym serwisie wstrzykujemy UrlGeneratorInterface:

use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

public function __construct(private readonly UrlGeneratorInterface $urlGenerator) {}

$url = $this->urlGenerator->generate('job_show', ['id' => 1]);

Zmiana vs Symfony 4.2: w akcji używamy redirectToRoute() i generateUrl() z AbstractController, a w serwisach — UrlGeneratorInterface. W Twigu path() (względny) i url() (absolutny) działają jak wcześniej.


Debugowanie tras

Podczas pracy z routingiem przydają się dwie komendy. Lista wszystkich tras:

docker compose exec app php bin/console debug:router

Szczegóły jednej trasy (ścieżka, metody, wymagania):

docker compose exec app php bin/console debug:router job_show

Sprawdzenie, która trasa dopasuje dany URL (bardzo pomocne przy diagnozie 404):

docker compose exec app php bin/console router:match /job/1

Wskazówka: trasy Web Debug Toolbara i Profilera (/_wdt, /_profiler) też zobaczysz w debug:router — są ładowane tylko w środowisku dev z config/routes/.


Podsumowanie

Rozumiemy już routing w Jobeet:

  • ✅ trasy definiowane atrybutem #[Route], wczytywane przez config/routes.yaml (type: attribute),
  • ✅ parametry ścieżki ({id}) z walidacją przez requirements,
  • ✅ ograniczenie metod HTTP (methods: ['GET']),
  • ✅ generowanie URL-i z nazw tras: path()/url(), generateUrl(), redirectToRoute(),
  • ✅ debugowanie: debug:router i router:match.

W Dniu 6 wrócimy do modelu: przeniesiemy zapytania do repozytoriów, nauczymy się QueryBuilera i odfiltrujemy tylko aktywne, opublikowane oferty (zgodnie z historią F1).

Narzędzia / paczki

symfony/routing

Spis treści