Генерация URL из имени маршрута — критически важная тема. Никогда не хардкодите URL в Symfony. На экзамене проверяют все способы генерации, режимы и edge cases.
Генерация URL в контроллере
<?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;
class NavigationController extends AbstractController
{
public function example(): Response
{
// Relative path (default) — ABSOLUTE_PATH
$url = $this->generateUrl('product_show', ['id' => 42]);
// '/products/42'
// Absolute URL — with scheme and host
$url = $this->generateUrl('product_show', ['id' => 42],
UrlGeneratorInterface::ABSOLUTE_URL
);
// 'https://example.com/products/42'
// Network path — protocol-relative
$url = $this->generateUrl('product_show', ['id' => 42],
UrlGeneratorInterface::NETWORK_PATH
);
// '//example.com/products/42'
// Relative path — from current URL
$url = $this->generateUrl('product_show', ['id' => 42],
UrlGeneratorInterface::RELATIVE_PATH
);
// '../products/42' (depends on current URL)
return $this->redirect($url);
}
}
Режимы генерации URL
| Константа | Значение | Результат | Пример |
|---|---|---|---|
ABSOLUTE_PATH |
1 | Абсолютный путь (default) | /products/42 |
ABSOLUTE_URL |
0 | Полный URL | https://example.com/products/42 |
NETWORK_PATH |
2 | Без протокола | //example.com/products/42 |
RELATIVE_PATH |
3 | Относительный путь | ../products/42 |
Подвох экзамена:
ABSOLUTE_PATH(1) — это DEFAULT. НеABSOLUTE_URL(0). Многие путают:generateUrl('route')возвращает/path, а НЕhttps://.... Для полного URL с доменом нужно явно передатьABSOLUTE_URL.
Extra Parameters → Query String
<?php
declare(strict_types=1);
// Route: /products (no parameters in pattern)
$url = $this->generateUrl('product_list', [
'page' => 2,
'sort' => 'price',
'direction' => 'asc',
]);
// '/products?page=2&sort=price&direction=asc'
// Route: /products/{id}
$url = $this->generateUrl('product_show', [
'id' => 42, // Route parameter — goes into path
'tab' => 'reviews', // Extra parameter — goes into query string
'ref' => 'home', // Extra parameter — goes into query string
]);
// '/products/42?tab=reviews&ref=home'
// Fragment (#)
$url = $this->generateUrl('product_show', [
'id' => 42,
'_fragment' => 'reviews',
]);
// '/products/42#reviews'
Подвох экзамена: Параметры, НЕ определённые в route pattern, автоматически становятся query string.
_fragment— специальный параметр для URL fragment (#). На экзамене спрашивают, куда попадёт параметр, которого нет в маршруте — ответ: в query string.
Генерация URL в Twig
{# path() — relative path (ABSOLUTE_PATH) #}
<a href="{{ path('product_show', {id: product.id}) }}">
{{ product.name }}
</a>
{# /products/42 #}
{# url() — absolute URL (ABSOLUTE_URL) #}
<a href="{{ url('product_show', {id: product.id}) }}">
Share this product
</a>
{# https://example.com/products/42 #}
{# Extra parameters → query string #}
<a href="{{ path('product_list', {page: 2, sort: 'name'}) }}">
Next page
</a>
{# /products?page=2&sort=name #}
{# Fragment #}
<a href="{{ path('product_show', {id: 42, _fragment: 'reviews'}) }}">
See reviews
</a>
{# /products/42#reviews #}
{# Current route information #}
<p>Current route: {{ app.request.attributes.get('_route') }}</p>
{# product_show #}
<p>Route params: {{ app.request.attributes.get('_route_params')|json_encode }}</p>
{# {"id":"42"} #}
{# Active link highlighting #}
<nav>
<a href="{{ path('home') }}"
class="{{ app.request.attributes.get('_route') == 'home' ? 'active' : '' }}">
Home
</a>
<a href="{{ path('product_list') }}"
class="{{ app.request.attributes.get('_route') starts with 'product_' ? 'active' : '' }}">
Products
</a>
</nav>
Подвох экзамена:
path()генерирует относительный путь (/products/42),url()— абсолютный URL с доменом (https://example.com/products/42). В emails ВСЕГДА используйтеurl(), потому чтоpath()без домена не работает в email-клиенте.
Генерация URL в сервисах
<?php
declare(strict_types=1);
namespace App\Service;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class NotificationService
{
public function __construct(
private readonly UrlGeneratorInterface $urlGenerator,
) {
}
public function generateProductLink(int $productId): string
{
return $this->urlGenerator->generate(
'product_show',
['id' => $productId],
UrlGeneratorInterface::ABSOLUTE_URL,
);
}
public function generatePasswordResetLink(string $token): string
{
return $this->urlGenerator->generate(
'app_reset_password',
['token' => $token],
UrlGeneratorInterface::ABSOLUTE_URL,
);
// 'https://example.com/reset-password/abc123token'
}
}
RouterInterface vs UrlGeneratorInterface
<?php
declare(strict_types=1);
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\RouterInterface;
// UrlGeneratorInterface — only URL generation
// Methods: generate()
// RouterInterface extends UrlGeneratorInterface + UrlMatcherInterface
// Methods: generate(), match(), getRouteCollection()
// Best practice: inject the MINIMAL interface you need
// For generation only → UrlGeneratorInterface
// For matching + generation → RouterInterface
Подвох экзамена:
RouterInterfaceрасширяетUrlGeneratorInterface. Если сервису нужна только генерация URL — инжектитеUrlGeneratorInterface(принцип минимальной зависимости). Если нужен matching —RouterInterface.
Генерация URL в CLI (Console Commands)
<?php
declare(strict_types=1);
namespace App\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
#[AsCommand(name: 'app:send-notifications')]
class SendNotificationsCommand extends Command
{
public function __construct(
private readonly UrlGeneratorInterface $urlGenerator,
) {
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
// In CLI, there is NO Request → no host/scheme information
// ABSOLUTE_URL would generate: http://localhost/products/42
// Solution 1: Configure default URI
// config/packages/framework.yaml:
// framework:
// router:
// default_uri: 'https://example.com'
// Solution 2: Set context manually
$context = $this->urlGenerator->getContext();
$context->setHost('example.com');
$context->setScheme('https');
$context->setBaseUrl('');
$url = $this->urlGenerator->generate(
'product_show',
['id' => 42],
UrlGeneratorInterface::ABSOLUTE_URL,
);
// 'https://example.com/products/42'
$output->writeln('Generated URL: ' . $url);
return Command::SUCCESS;
}
}
Настройка default_uri
# config/packages/framework.yaml
framework:
router:
default_uri: 'https://example.com'
# Used when there's no Request (CLI, Messenger)
Подвох экзамена: В CLI (Console commands, Messenger handlers) нет HTTP-запроса, поэтому
ABSOLUTE_URLне знает домен. Без настройкиdefault_uriили ручного RequestContext,ABSOLUTE_URLсгенерирует URL сlocalhost. На экзамене спрашивают, как генерировать URL в CLI.
Генерация URL для Messenger Handlers
<?php
declare(strict_types=1);
namespace App\MessageHandler;
use App\Message\OrderConfirmation;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
#[AsMessageHandler]
final class OrderConfirmationHandler
{
public function __construct(
private readonly UrlGeneratorInterface $urlGenerator,
private readonly MailerInterface $mailer,
) {
}
public function __invoke(OrderConfirmation $message): void
{
// Messenger handlers run async — no Request context
// Requires default_uri configuration
$orderUrl = $this->urlGenerator->generate(
'order_show',
['id' => $message->orderId],
UrlGeneratorInterface::ABSOLUTE_URL,
);
// Send email with link...
}
}
Дебаг маршрутов
debug:router
# List ALL routes
php bin/console debug:router
# Output example:
# ------------- -------- -------- ------ ---------------------
# Name Method Scheme Host Path
# ------------- -------- -------- ------ ---------------------
# product_list GET ANY ANY /products
# product_show GET ANY ANY /products/{id}
# product_new GET|POST ANY ANY /products/new
# ------------- -------- -------- ------ ---------------------
# Show specific route
php bin/console debug:router product_show
# Output as JSON/XML/CSV
php bin/console debug:router --format=json
# Show only routes matching a name pattern
php bin/console debug:router --show-aliases
router:match
# Test URL matching
php bin/console router:match /products/42
# Output:
# [OK] Route "product_show" matches
#
# +--------------+-------------------------------------------+
# | Property | Value |
# +--------------+-------------------------------------------+
# | Route Name | product_show |
# | Path | /products/{id} |
# | Path Regex | {^/products/(?P<id>\d+)$}sDu |
# | Host | ANY |
# | Method | GET |
# | Requirements | id: \d+ |
# | Defaults | _controller: App\Controller\Product... |
# +--------------+-------------------------------------------+
# Match with specific method
php bin/console router:match /products --method=POST
# Match with host
php bin/console router:match /products --host=api.example.com
# Match with scheme
php bin/console router:match /products --scheme=https
Типичные результаты router:match
| Результат | Значение |
|---|---|
[OK] Route "name" matches |
Маршрут найден |
None of the routes match the path "/..." |
404 — URL не совпал ни с одним |
Route "name" almost matches but method "..." does not match |
405 — URL верный, метод нет |
Подвох экзамена:
router:match— главный инструмент отладки маршрутизации. Если URL совпадает с маршрутом, но метод неверный — ответ будет 405 Method Not Allowed (не 404). Команда показывает, какие маршруты "почти совпали".
Обработка ошибок генерации
<?php
declare(strict_types=1);
use Symfony\Component\Routing\Exception\RouteNotFoundException;
use Symfony\Component\Routing\Exception\MissingMandatoryParametersException;
use Symfony\Component\Routing\Exception\InvalidParameterException;
try {
// Route doesn't exist
$url = $this->generateUrl('nonexistent_route');
} catch (RouteNotFoundException $e) {
// Route "nonexistent_route" does not exist
}
try {
// Missing required parameter
$url = $this->generateUrl('product_show'); // {id} required!
} catch (MissingMandatoryParametersException $e) {
// Missing mandatory parameters: "id"
}
try {
// Parameter doesn't match requirement
$url = $this->generateUrl('product_show', ['id' => 'abc']); // \d+ required
} catch (InvalidParameterException $e) {
// Parameter "id" must match "\d+"
}
Генерация URL с Signed URLs
<?php
declare(strict_types=1);
use Symfony\Component\HttpKernel\UriSigner;
class SecureUrlService
{
public function __construct(
private readonly UrlGeneratorInterface $urlGenerator,
private readonly UriSigner $uriSigner,
) {
}
public function generateSignedUrl(int $userId): string
{
$url = $this->urlGenerator->generate(
'app_verify_email',
['id' => $userId],
UrlGeneratorInterface::ABSOLUTE_URL,
);
// Sign the URL — adds _hash parameter
return $this->uriSigner->sign($url);
// https://example.com/verify/42?_hash=abc123...
}
public function verifySignedUrl(string $url): bool
{
return $this->uriSigner->check($url);
}
}
Symfony 8.0:
UriSignerпроверяет целостность URL через HMAC-подпись. Подпись привязана кAPP_SECRET. Используется для email verification links, password reset и других одноразовых URL.