路由参数 #

一、参数基础 #

1.1 基本参数 #

路由参数使用花括号{}定义,会自动传递给控制器方法:

php
<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class UserController extends AbstractController
{
    #[Route('/user/{id}', name: 'app_user_show')]
    public function show(int $id): Response
    {
        return new Response("User ID: $id");
    }

    #[Route('/category/{category}/product/{productId}', name: 'app_product_show')]
    public function productShow(string $category, int $productId): Response
    {
        return new Response("Category: $category, Product ID: $productId");
    }
}

1.2 参数类型转换 #

Symfony会自动将参数转换为对应类型:

php
<?php

class ArticleController extends AbstractController
{
    #[Route('/article/{id}', name: 'app_article_show')]
    public function show(int $id): Response
    {
        return new Response("Article ID: $id (type: " . gettype($id) . ")");
    }

    #[Route('/price/{amount}', name: 'app_price_show')]
    public function priceShow(float $amount): Response
    {
        return new Response("Price: $amount");
    }

    #[Route('/flag/{enabled}', name: 'app_flag_show')]
    public function flagShow(bool $enabled): Response
    {
        return new Response("Enabled: " . ($enabled ? 'Yes' : 'No'));
    }
}

二、参数约束 #

2.1 正则表达式约束 #

php
<?php

class BlogController extends AbstractController
{
    #[Route('/blog/{id}', name: 'app_blog_show', requirements: ['id' => '\d+'])]
    public function show(int $id): Response
    {
        return new Response("Blog ID: $id");
    }

    #[Route('/page/{slug}', name: 'app_page_show', requirements: ['slug' => '[a-z0-9-]+'])]
    public function pageShow(string $slug): Response
    {
        return new Response("Page Slug: $slug");
    }

    #[Route('/archive/{year}/{month}', name: 'app_archive', requirements: [
        'year' => '\d{4}',
        'month' => '\d{2}'
    ])]
    public function archive(int $year, int $month): Response
    {
        return new Response("Archive: $year-$month");
    }
}

2.2 常用约束示例 #

php
<?php

class ValidationController extends AbstractController
{
    #[Route('/user/{id}', name: 'app_user_id', requirements: ['id' => '\d+'])]
    public function userId(int $id): Response
    {
        return new Response("User ID (数字): $id");
    }

    #[Route('/user/{username}', name: 'app_user_username', requirements: ['username' => '[a-zA-Z0-9_]+'])]
    public function userUsername(string $username): Response
    {
        return new Response("Username (字母数字下划线): $username");
    }

    #[Route('/post/{slug}', name: 'app_post_slug', requirements: ['slug' => '[a-z0-9-]+'])]
    public function postSlug(string $slug): Response
    {
        return new Response("Post Slug (小写数字横线): $slug");
    }

    #[Route('/api/{version}', name: 'app_api_version', requirements: ['version' => 'v1|v2|v3'])]
    public function apiVersion(string $version): Response
    {
        return new Response("API Version (枚举): $version");
    }

    #[Route('/file/{path}', name: 'app_file_path', requirements: ['path' => '.+'])]
    public function filePath(string $path): Response
    {
        return new Response("File Path (任意): $path");
    }
}

2.3 约束表 #

约束 说明 匹配示例
\d+ 一个或多个数字 123, 456
\d{4} 四位数字 2024
[a-z]+ 小写字母 hello
[a-zA-Z]+ 大小写字母 Hello
[a-z0-9-]+ 小写字母数字横线 my-page-1
[a-zA-Z0-9_]+ 字母数字下划线 user_name
.+ 任意字符 any/thing
v1|v2|v3 枚举值 v1, v2, v3

三、可选参数 #

3.1 默认值方式 #

php
<?php

class ProductController extends AbstractController
{
    #[Route('/products/{page}', name: 'app_product_list', defaults: ['page' => 1])]
    public function list(int $page): Response
    {
        return new Response("Products Page: $page");
    }

    #[Route('/search/{category}/{keyword}', name: 'app_search', defaults: ['category' => 'all', 'keyword' => null])]
    public function search(string $category, ?string $keyword): Response
    {
        return new Response("Category: $category, Keyword: " . ($keyword ?? 'none'));
    }
}

3.2 可空参数 #

php
<?php

class ArticleController extends AbstractController
{
    #[Route('/articles/{tag}', name: 'app_articles_by_tag')]
    public function byTag(?string $tag = null): Response
    {
        if ($tag === null) {
            return new Response('All Articles');
        }
        return new Response("Articles with tag: $tag");
    }

    #[Route('/news/{year}/{month}', name: 'app_news_archive', defaults: ['month' => null])]
    public function archive(int $year, ?int $month = null): Response
    {
        if ($month === null) {
            return new Response("News for year: $year");
        }
        return new Response("News for $year-$month");
    }
}

3.3 多级可选参数 #

php
<?php

class ReportController extends AbstractController
{
    #[Route(
        '/report/{year}/{month}/{day}',
        name: 'app_report',
        defaults: ['month' => null, 'day' => null],
        requirements: ['year' => '\d{4}', 'month' => '\d{2}', 'day' => '\d{2}']
    )]
    public function report(int $year, ?int $month, ?int $day): Response
    {
        if ($day !== null) {
            return new Response("Report for $year-$month-$day");
        }
        if ($month !== null) {
            return new Response("Report for $year-$month");
        }
        return new Response("Report for $year");
    }
}

四、特殊参数 #

4.1 _format参数 #

php
<?php

class FeedController extends AbstractController
{
    #[Route('/feed.{_format}', name: 'app_feed', requirements: ['_format' => 'xml|json|rss'])]
    public function feed(string $_format): Response
    {
        return new Response("Feed format: $_format");
    }

    #[Route('/api/users.{_format}', name: 'api_users', requirements: ['_format' => 'json|xml'])]
    public function users(string $_format): Response
    {
        $data = ['users' => [['id' => 1, 'name' => 'John']]];
        
        if ($_format === 'xml') {
            $xml = new \SimpleXMLElement('<root/>');
            foreach ($data['users'] as $user) {
                $userXml = $xml->addChild('user');
                $userXml->addChild('id', $user['id']);
                $userXml->addChild('name', $user['name']);
            }
            return new Response($xml->asXML(), 200, ['Content-Type' => 'application/xml']);
        }
        
        return $this->json($data);
    }
}

4.2 _locale参数 #

php
<?php

class PageController extends AbstractController
{
    #[Route('/{_locale}/about', name: 'app_about', requirements: ['_locale' => 'en|zh|ja'])]
    public function about(string $_locale): Response
    {
        return new Response("About page in locale: $_locale");
    }

    #[Route('/{_locale}/contact', name: 'app_contact', requirements: ['_locale' => 'en|zh'])]
    public function contact(string $_locale): Response
    {
        return new Response("Contact page in locale: $_locale");
    }
}

4.3 _controller参数 #

php
<?php

#[Route('/legacy/{_controller}', name: 'app_legacy', requirements: ['_controller' => '.+'])]
public function legacy(string $_controller): Response
{
    return new Response("Legacy controller: $_controller");
}

五、参数转换 #

5.1 Doctrine实体转换 #

php
<?php

use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;

class UserController extends AbstractController
{
    #[Route('/user/{id}', name: 'app_user_show')]
    public function show(User $user): Response
    {
        return new Response("User: " . $user->getName());
    }

    #[Route('/user/{id}/edit', name: 'app_user_edit')]
    public function edit(User $user): Response
    {
        return new Response("Edit User: " . $user->getName());
    }
}

5.2 自定义参数转换器 #

php
<?php

use Symfony\Component\Routing\Attribute\ParamConverter;

class ArticleController extends AbstractController
{
    #[Route('/article/{slug}', name: 'app_article_show')]
    #[ParamConverter('article', class: 'App\Entity\Article', options: ['mapping' => ['slug' => 'slug']])]
    public function show(Article $article): Response
    {
        return new Response("Article: " . $article->getTitle());
    }
}

六、参数验证 #

6.1 内置验证 #

php
<?php

use Symfony\Component\Validator\Constraints as Assert;

class OrderController extends AbstractController
{
    #[Route('/order/{id}', name: 'app_order_show', requirements: ['id' => '\d+'])]
    public function show(int $id): Response
    {
        if ($id <= 0) {
            throw $this->createNotFoundException('Order not found');
        }
        return new Response("Order ID: $id");
    }
}

6.2 404处理 #

php
<?php

class ProductController extends AbstractController
{
    #[Route('/product/{id}', name: 'app_product_show', requirements: ['id' => '\d+'])]
    public function show(int $id, ProductRepository $repository): Response
    {
        $product = $repository->find($id);

        if (!$product) {
            throw $this->createNotFoundException('Product not found');
        }

        return new Response("Product: " . $product->getName());
    }
}

七、参数在模板中使用 #

7.1 生成URL #

php
<?php

class LinkController extends AbstractController
{
    #[Route('/links', name: 'app_links')]
    public function links(): Response
    {
        $url1 = $this->generateUrl('app_user_show', ['id' => 123]);
        $url2 = $this->generateUrl('app_product_show', ['category' => 'electronics', 'productId' => 456]);
        
        return new Response("
            <a href=\"$url1\">User 123</a>
            <a href=\"$url2\">Product 456</a>
        ");
    }
}

7.2 Twig模板中生成URL #

twig
{# 模板中生成URL #}
<a href="{{ path('app_user_show', {id: user.id}) }}">查看用户</a>

{# 带多个参数 #}
<a href="{{ path('app_product_show', {category: 'electronics', productId: product.id}) }}">
    查看产品
</a>

{# 绝对URL #}
<a href="{{ url('app_user_show', {id: user.id}) }}">查看用户(绝对路径)</a>

{# 检查路由是否存在 #}
{% if path('app_user_show', {id: 1}) is defined %}
    路由存在
{% endif %}

八、高级参数处理 #

8.1 参数默认值与约束组合 #

php
<?php

class BlogController extends AbstractController
{
    #[Route(
        '/blog/{page}/{limit}',
        name: 'app_blog_list',
        requirements: ['page' => '\d+', 'limit' => '\d+'],
        defaults: ['page' => 1, 'limit' => 10]
    )]
    public function list(int $page, int $limit): Response
    {
        return new Response("Blog list - Page: $page, Limit: $limit");
    }
}

8.2 参数顺序 #

php
<?php

class ArticleController extends AbstractController
{
    #[Route('/article/{id}/comments/{commentId}', name: 'app_article_comment')]
    public function comment(int $id, int $commentId): Response
    {
        return new Response("Article $id, Comment $commentId");
    }

    #[Route('/category/{category?}/article/{id}', name: 'app_category_article')]
    public function categoryArticle(?string $category, int $id): Response
    {
        return new Response("Category: " . ($category ?? 'none') . ", Article: $id");
    }
}

8.3 参数转义 #

php
<?php

class FileController extends AbstractController
{
    #[Route('/file/{path}', name: 'app_file_show', requirements: ['path' => '.+'])]
    public function show(string $path): Response
    {
        $safePath = basename($path);
        return new Response("File: $safePath");
    }
}

九、参数调试 #

9.1 查看路由参数 #

bash
# 查看特定路由的参数
php bin/console debug:router app_user_show

# 测试URL匹配
php bin/console router:match /user/123

# 输出参数信息
+--------------+---------------------------------------------------------+
| Property     | Value                                                   |
+--------------+---------------------------------------------------------+
| Requirements | id: \d+                                                 |
| Defaults     | _controller: App\Controller\UserController::show       |
+--------------+---------------------------------------------------------+

9.2 参数验证测试 #

bash
# 测试匹配
php bin/console router:match /blog/2024/03

# 测试不匹配
php bin/console router:match /blog/abc/def
# [ERROR] None of the routes match the URL

十、总结 #

本章学习了:

  • 路由参数基本用法
  • 参数类型转换
  • 正则表达式约束
  • 可选参数和默认值
  • 特殊参数(_format、_locale)
  • Doctrine实体转换
  • 参数验证和404处理
  • 模板中生成URL

下一章将学习 路由分组

最后更新:2026-03-28