Named Routes and Generating URLs
Build URLs from a route name and its parameters so paths stay changeable in one place.
$this->generateUrl(string $route, array $parameters = [], int $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH): stringEvery route has a name, a unique identifier such as app_article_show. Generating a URL means asking the router for the path of a named route with given parameters, instead of writing the path out by hand. The router already knows the path, the placeholders and their requirements, so it is the only thing in the application that should be building URLs.
Why never hard-code a path
A hard-coded '/articles/' . $id is a second copy of the route's path. Change the route to /posts/{id} and every copy is quietly wrong - no error, just broken links. Generated URLs change with the route.
In a controller and in a template
In a controller extending AbstractController, generateUrl() takes the route name, an array of parameters, and an optional reference type. Its default output is an absolute path such as /articles/7. Pass UrlGeneratorInterface::ABSOLUTE_URL for a full https://host/articles/7, which is what you need in an email or any link leaving the page.
In Twig, path('app_article_show', {id: 7}) gives the path and url('app_article_show', {id: 7}) gives the absolute URL. Both take the route name and the same parameters array, but their optional third argument is a boolean - relative for path() and schemeRelative for url() - not one of generateUrl()'s reference-type constants.
Shorthand and longhand
$this->redirectToRoute('app_article_show', ['id' => 7]) is shorthand for $this->redirect($this->generateUrl('app_article_show', ['id' => 7])). Both return a 302 redirect to the same URL.
What trips people up
Any parameter that is not a placeholder is appended as a query string rather than ignored.
Example
<?php
// src/Controller/ArticleController.php
declare(strict_types=1);
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
final class ArticleController extends AbstractController
{
#[Route('/articles/{id<\d+>}', name: 'app_article_show', methods: ['GET'])]
public function show(int $id): Response
{
return new Response(sprintf('Article %d', $id));
}
#[Route('/articles/latest', name: 'app_article_latest', priority: 1, methods: ['GET'])]
public function latest(): Response
{
// Default reference type: an absolute path. -> /articles/7
$path = $this->generateUrl('app_article_show', ['id' => 7]);
// A full URL, for emails and anything leaving this page.
// -> https://example.com/articles/7
$absolute = $this->generateUrl(
'app_article_show',
['id' => 7],
UrlGeneratorInterface::ABSOLUTE_URL,
);
// 'ref' is not a placeholder, so it becomes a query string.
// -> /articles/7?ref=latest
$tracked = $this->generateUrl('app_article_show', ['id' => 7, 'ref' => 'latest']);
// Shorthand for $this->redirect($path): a 302 to /articles/7.
return $this->redirectToRoute('app_article_show', ['id' => 7]);
}
}
When to use it
- Link from a listing page to a detail page without repeating the detail route's path.
- Redirect to a canonical URL after handling a request.
- Put a full https:// link to a page into an email or a JSON response.
More examples
Links in a Twig template
`path()` and `url()` differ only in whether the host and scheme are included. Use `path()` for links inside the site and `url()` for canonical tags, feeds, and anything that will be read outside the page. Reaching for `|raw` here would disable the escaping that keeps generated query values safe.
{# templates/article/index.html.twig #}
{# path() returns /articles/7 #}
<a href="{{ path('app_article_show', {id: 7}) }}">Read article 7</a>
{# url() returns https://example.com/articles/7 #}
<link rel="canonical" href="{{ url('app_article_show', {id: 7}) }}">
{# Extra keys become a query string: /articles/7?ref=index #}
<a href="{{ path('app_article_show', {id: 7, ref: 'index'}) }}">With tracking</a>
{# path() output is escaped by auto-escaping like any other value;
it never needs |raw #}
Both reference types from one route
The four reference types are ABSOLUTE_PATH (the default), ABSOLUTE_URL, NETWORK_PATH and RELATIVE_PATH. `GET /articles/7/share` returns 200 with those three strings. You want this when an API response has to tell a client where a resource lives rather than assume the client can guess.
<?php
// src/Controller/ShareController.php
declare(strict_types=1);
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
final class ShareController extends AbstractController
{
#[Route('/articles/{id<\d+>}/share', name: 'app_article_share', methods: ['GET'])]
public function share(int $id): JsonResponse
{
return new JsonResponse([
// /articles/7
'path' => $this->generateUrl('app_article_show', ['id' => $id]),
// //example.com/articles/7
'network' => $this->generateUrl(
'app_article_show',
['id' => $id],
UrlGeneratorInterface::NETWORK_PATH,
),
// https://example.com/articles/7
'absolute' => $this->generateUrl(
'app_article_show',
['id' => $id],
UrlGeneratorInterface::ABSOLUTE_URL,
),
]);
}
}
The same generation from any class
Outside a controller there is no `generateUrl()`, so the class declares what it needs in its constructor, typed on the interface. How that object is supplied without you writing `new` is the subject of a later stage; what matters here is that `generate()` and `generateUrl()` are the same call with the same arguments.
<?php
// src/Url/ArticleLinkBuilder.php
declare(strict_types=1);
namespace App\Url;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
final class ArticleLinkBuilder
{
// generateUrl() on a controller is a shortcut; the real work is
// done by an object implementing UrlGeneratorInterface, whose
// generate() takes exactly the same three arguments.
public function __construct(
private readonly UrlGeneratorInterface $urlGenerator,
) {
}
public function absoluteUrl(int $id): string
{
return $this->urlGenerator->generate(
'app_article_show',
['id' => $id],
UrlGeneratorInterface::ABSOLUTE_URL,
);
}
}
Discussion