Symfony Routing 组件 CHANGELOG 全解析:从 2.1 到 8.2 的路由引擎演进路线图

原创2026-10-13 20:53:321,919 阅读
文章标签:后端

Symfony Routing 组件 CHANGELOG 全解析:从 2.1 到 8.2 的路由引擎演进路线图

本文以 CHANGELOG.md 为主线,系统梳理 Symfony Routing 组件(symfony/routing,项目描述为 "Maps an HTTP request to a set of configuration variables")从 2.1 到 8.2 的全部版本演进:包括 RouterBundle 与框架解耦、#[Route] 属性路由的引入、编译型匹配器/生成器、Requirement 通用约束常量库、条件表达式增强以及多次 BC Break 的完整破坏性变更明细。读者将获得一份可直接用于版本升级评估、迁移决策和配置选型的实战参考手册。

组件定位与版本脉络总览

Routing 组件是 Symfony 中负责"把 HTTP 请求映射为一组配置变量"的底层组件:UrlMatcher 负责将传入的 path info 匹配到具体路由,UrlGenerator 负责根据路由名与参数反向生成 URL。它不依赖完整的 Symfony 框架即可独立使用,README.md 中给出了最小可用示例:

use Symfony\Component\Routing\Generator\UrlGenerator;
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;

$route = new Route('/blog/{slug}', ['_controller' => BlogController::class]);
$routes = new RouteCollection();
$routes->add('blog_show', $route);

$context = new RequestContext();

$matcher = new UrlMatcher($routes, $context);
$parameters = $matcher->match('/blog/lorem-ipsum');
// ['_controller' => 'App\Controller\BlogController', 'slug' => 'lorem-ipsum', '_route' => 'blog_show']

$generator = new UrlGenerator($routes, $context);
$url = $generator->generate('blog_show', ['slug' => 'my-blog-post']);
// '/blog/my-blog-post'

从当前仓库的 composer.json 可见,本组件要求 PHP >=8.4.1,生产依赖仅 symfony/deprecation-contracts,与 Config、Console、HttpFoundation 等均为可选开发依赖,体现了其高度的独立性。

CHANGELOG 记录的时间跨度涵盖 2.1 ~ 8.2。按主题可归纳为四条主线:

  1. 配置格式与声明方式:XML 配置从支持到弃用再到移除,PHP/YAML/属性(Attribute)成为主流;
  2. 匹配与生成架构:从解释式 UrlMatcher/UrlGenerator 演进出编译型 CompiledUrlMatcher/CompiledUrlGenerator;
  3. 属性路由体系:从 Doctrine 注解(annotations)全面切换到原生 PHP 属性(attributes);
  4. 能力扩展:条件表达式、国际化路由、通用 Requirement 约束、框架集成解耦(RouterBundle)。

8.x:框架解耦与路由能力大扩展

8.2:RouterBundle 诞生——路由能力从 FrameworkBundle 整体迁移

8.2 是组件近期最重要的架构变更。此前 router 配置与相关服务由 FrameworkBundle 的 framework.router 提供;8.2 起新增 RouterBundle,把这些能力整体收编进组件自身。对应实现见 RouterBundle.php,其文档注释明确写道:"Provides the router and the services that load and generate routes"(提供路由器以及加载和生成路由的服务)。

RouterBundle::configure() 定义了 router 根节点的配置结构(RouterBundle.php):

配置项 类型 默认值 说明
enabled bool false 是否启用路由器;空配置不会自动启用(RouterBundleTest.php 专门测试了这一点)
resource scalar null 主路由资源,启用时必须配置,否则抛出 The child config "resource" under "router" must be configured.
type scalar — 资源的加载类型(如 xml、yaml、php)
default_uri scalar null 非 HTTP 环境下生成 URL 使用的默认 URI
http_port int 80 HTTP 端口
https_port int 443 HTTPS 端口
strict_requirements bool/null true true 时参数不满足 requirement 抛异常;false 时不抛异常并返回 null;null 时完全跳过参数校验。开发环境推荐 true,生产环境推荐 false 或 null
utf8 bool true 是否启用 UTF-8 匹配

RouterBundle::build() 注册了两个编译器相关的自动装配(RouterBundle.php):带 AsRouteLoader 属性的类自动打上 routing.route_loader 标签,带 AsRoutingConditionService 属性的类自动打上 routing.condition_service 标签(含 alias、priority 参数);同时注册 LocalizedRoutesPass 编译 pass。loadExtension() 则会导入 Resources/config/routing.php 与 Resources/config/console.php,并把 router.request_context.host/scheme 参数内联进 router.request_context 服务参数,避免运行时通过 ParameterBag::all() 急切解析 env 变量。

AsRouteLoader:声明式服务路由加载器

#<a href="https://link.gitcode.com/i/02d19a6f248e79fd699e20dd9b546eb9" target="_blank">AsRouteLoader] 属性([Attribute/AsRouteLoader.php)取代了 FrameworkBundle 的 RouteLoaderInterface,用于把服务标记为路由加载器,使其方法可被路由资源引用:

#[AsRouteLoader]
class SomeRouteLoader
{
    public function loadRoutes(): RouteCollection
    {
        // ...
    }
}

随后在路由配置中引用它:

some_routes:
    resource: 'some_route_loader::loadRoutes'
    type: service

RouterBundleTest.php 验证了该属性会为自动配置的服务打上 routing.route_loader 标签。

AsRoutingConditionService:条件表达式中调用服务

#<a href="https://link.gitcode.com/i/83932d78e530b7123295969853f28ced" target="_blank">AsRoutingConditionService] 属性([Attribute/AsRoutingConditionService.php)用于自动配置路由条件服务,使服务可以在路由 condition 表达式中被调用。构造函数接受 ?string $alias(在条件表达式中使用的别名)和 int $priority = 0(允许同名别名覆盖优先级):

#[AsRoutingConditionService('foo')]
class SomeFooService
{
    public function bar(): bool
    {
        // ...
    }
}

然后即可在路由条件中通过 service('foo').bar() 调用:

#[Route('/page', condition: "service('foo').bar()")]
public function page(): Response
{
    // ...
}

CHANGELOG 特别指出:该属性不再继承 AutoconfigureTag,因此它只标记自身携带该属性的类,而不会波及这些类的子类。

router:match 命令:命令行调试路由匹配

Command\RouterMatchCommand(Command/RouterMatchCommand.php)是 FrameworkBundle 曾经提供的 router:match 命令的组件级实现,用于模拟一次 path info 匹配、定位"为什么这条路由没匹配上"。其支持的参数如下:

参数/选项 类型 说明
path_info 必填参数 要匹配的路径
--method 选项 设置 HTTP 方法
--scheme 选项 设置 URI scheme(通常是 http 或 https)
--host 选项 设置 URI 主机
--verbose 选项 显示"未匹配"路由的详细原因

典型用法(来自命令自带的 help 文本):

php bin/console router:match /foo
php bin/console router:match /foo --method POST --scheme https --host symfony.com --verbose

该命令内部使用 TraceableUrlMatcher(Matcher/TraceableUrlMatcher.php)逐条追踪路由匹配痕迹。TraceableUrlMatcher 定义了三种匹配级别常量:ROUTE_DOES_NOT_MATCH = 0、ROUTE_ALMOST_MATCHES = 1、ROUTE_MATCHES = 2,能精确区分"完全没匹配""差一点就匹配(如路径对了但方法/条件不符)""成功匹配"三种状态。命令输出时对"几乎匹配"的路由给出原因说明,对成功匹配的路由还会联动调用 debug:router 输出该路由详情。

8.2 的其他路由能力扩展

  • firewall 选项:可在路由定义中声明,以 _firewall 路由默认值暴露。从 Attribute/Route.php 可见,#<a href="https://link.gitcode.com/i/4464652a9a13228a8abdb59c98c56770" target="_blank">Route] 构造参数 ?string $firewall 会被写入 $this->defaults['_firewall'];YAML 加载同样支持([YamlFileLoaderTest.php 断言 $route->getDefault('_firewall') === 'api')。RouteCompilerTest.php 还验证了 _firewall 不能被用作路径或主机参数名。
  • _query 默认查询参数:允许通过 _query 路由默认值定义默认查询参数,也支持在生成 URL 时传入查询参数(见下文 7.4 条目)。
  • RequestContext::runWith():在不泄漏修改的前提下,为另一个 host、scheme 或 base URL 生成和匹配 URL。实现见 RequestContext.php:它克隆当前上下文、临时应用传入值、执行回调,并在 finally 中恢复全部字段,即使回调抛异常也能还原。
  • Route::addCondition() / RouteCollection::addCondition() 与 add_condition import 键:把新条件与已有条件用 and 组合而非覆盖。Route::addCondition() 实现于 Route.php:空条件直接忽略,否则以 (%s) and (%s) 拼接;RouteCollection::addCondition()(RouteCollection.php)则遍历集合内所有路由逐条组合。YAML 侧的 add_condition 键与 import 用法见测试夹具 Tests/Fixtures/add_condition.yml:
route_with_conditions:
    path:          /route
    condition:     'context.getMethod() == "GET"'
    add_condition: 'request.isSecure()'

import_with_add_condition:
    resource:      validpattern.yml
    name_prefix:   added_
    add_condition: 'request.isSecure()'

PHP 配置器侧则通过 RouteConfigurator::addCondition()(Loader/Configurator/Traits/RouteTrait.php)实现,对应的完整示例见 Tests/Fixtures/php_dsl_add_condition.php。

8.1:prefix() 的 trailingSlashOnRoot 参数

8.1 为 CollectionConfigurator::prefix() 新增 $trailingSlashOnRoot 参数(默认 true),用于关闭根路由上的尾部斜杠。实现细节见 Loader/Configurator/CollectionConfigurator.php 与 Loader/Configurator/Traits/PrefixTrait.php:当 $trailingSlashOnRoot 为 false 时,路径恰好等于前缀加 / 的路由会被去尾斜杠。该能力在 YAML import 中对应 trailing_slash_on_root 键(见 Loader/Configurator/RoutesReference.php 的 ImportConfig 类型定义),测试覆盖见 YamlFileLoaderTest.php(/slash/ 保留、/no-slash 去斜杠)。

8.0:破坏性变更集中移除

8.0 移除了一批已弃用能力,升级时需重点排查:

  • 移除 XML 配置格式支持(5.x 起弃用),改用 YAML、PHP 或属性;
  • 移除 Annotation 命名空间下的类别名(6.4 起加入、7.4 起弃用),一律使用属性;
  • 移除属性类中的 getter/setter,改为公共属性(7.4 起弃用);
  • 移除受保护的 AttributeClassLoader::$routeAnnotationClass 属性与 setRouteAnnotationClass() 方法(7.2 起弃用),改用 AttributeClassLoader::setRouteAttributeClass();
  • PHP 配置文件中禁止访问 loader 的内部作用域,只能使用其公共 API(7.4 起弃用);
  • 给 UrlGenerator 传非数组 _query 参数会抛 InvalidParameterException。这一点在 Generator/UrlGenerator.php 中有两处校验:默认 _query 非数组抛异常、参数 _query 非数组抛异常。

7.x:属性路由体系的成熟与通用约束库扩充

7.4:属性自动注册路由服务与 PHP 数组 DSL

  • AttributeServicesLoader 与 RoutingControllerPass:允许从服务上的属性自动注册路由,将路由定义与控制器服务注册解耦。
  • _query 查询参数:UrlGenerator 支持通过 _query 参数生成查询字符串。UrlGeneratorTest.php 有大量用例:_query 中的键值会拼接为 URL 查询串,值 null 会被省略,非数组则抛 InvalidParameterException。
  • #<a href="https://link.gitcode.com/i/af880051a9e433a6b6468bbc08182ca2" target="_blank">Route] 属性支持多个 env 名称:env 参数可传字符串或数组(如 "dev"、["dev", "test"]),见 [Attribute/Route.php。
  • RequestContext 构造函数新增 $parameters 参数:可在构造时直接注入自定义参数(RequestContext.php),配合 getParameter()/setParameter()/hasParameter() 使用。
  • 支持用 PHP 数组声明路由,形状与对应 YAML 文件一致;配套新增 RoutesReference(Loader/Configurator/RoutesReference.php),它以 @psalm-type 的形式为 RouteConfig、ImportConfig、AliasConfig、RoutesConfig 提供完整的数组形状文档。ImportConfig 的字段即 import 键的权威清单:resource、type、exclude、prefix、name_prefix、trailing_slash_on_root、controller、methods、requirements、defaults、options、host、schemes、condition、add_condition、locale、format、utf8、stateless、firewall。

7.3:属性路由的别名与弃用声明

#<a href="https://link.gitcode.com/i/7b8658a0cecec94d685469afa07ddb28" target="_blank">Route] 属性支持 alias 参数(字符串、DeprecatedAlias 或二者的数组,见 [Attribute/Route.php)与 deprecated 声明。DeprecatedAlias 类位于 Attribute/DeprecatedAlias.php,用于为迁移中的路由名提供兼容别名并标记弃用。同时新增 Requirement::MONGODB_ID(<a href="https://link.gitcode.com/i/6622de70ed1432027bd34b787bb6781d" target="_blank">0-9a-f]{24}),用于校验十六进制格式的 MongoDB ObjectID([Requirement/Requirement.php)。

7.2:UID_RFC9562 常量

新增 Requirement::UID_RFC9562,校验 RFC 9562 格式的 UUID。实现上它直接复用 UID_RFC4122 的正则 <a href="https://link.gitcode.com/i/c86f9876c0c77f989d2e8f439be56fd6" target="_blank">0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}([Requirement/Requirement.php),并开始弃用 AttributeClassLoader::$routeAnnotationClass 属性。

7.1:{foo:bar} 参数映射语法

新增 {foo:bar} 语法,用于定义路由参数与其对应请求属性之间的映射。这条语法的解析逻辑位于 Route::extractInlineDefaultsAndRequirements()(Route.php):模式 {name:arg.path?} 中冒号后是控制器参数名、可选 .property 是属性路径,解析结果写入 _route_mapping 默认值。

7.0:彻底移除 Doctrine 注解

  • 移除 Doctrine annotations 支持,全面改用原生属性;
  • AnnotationClassLoader → AttributeClassLoader、AnnotationDirectoryLoader → AttributeDirectoryLoader、AnnotationFileLoader → AttributeFileLoader(同名类在 6.4 起已加入、6.4 起弃用,7.0 删除);
  • UrlMatcher::handleRouteRequirements() 新增 $routeParameters 参数(对应 6.1 起"不传第四个参数"的弃用)。

6.x:Requirement 常量库与条件表达式增强

6.2:通用路由约束常量库

6.2 是约束体系的大版本:新增 Requirement 枚举类,提供一组通用正则表达式常量,可直接用作路由参数 requirement。当前仓库 Requirement/Requirement.php 收录的常量完整清单如下:

常量 正则 用途
ASCII_SLUG [A-Za-z0-9]+(?:-[A-Za-z0-9]+)* ASCII slug(对应 symfony/string AsciiSlugger 默认实现)
CATCH_ALL .+ 通配任意内容
DATE_YMD `[0-9]{4}-(?:0[1-9] 1[012])-(?:0[1-9]
DIGITS [0-9]+ 纯数字
MONGODB_ID [0-9a-f]{24} MongoDB ObjectID(7.3 加入)
POSITIVE_INT [1-9][0-9]* 正整数(常见的 id 与分页参数)
UID_BASE32 [0-9A-HJKMNP-TV-Z]{26} 32 进制 UID
UID_BASE58 [1-9A-HJ-NP-Za-km-z]{22} 58 进制 UID
UID_RFC4122 [0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12} RFC 4122 UUID
UID_RFC9562 同 UID_RFC4122 RFC 9562 UUID(7.2 加入)
ULID [0-7][0-9A-HJKMNP-TV-Z]{25} ULID
UUID [0-9a-f]{8}-[0-9a-f]{4}-[13-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12} 通用 UUID(含 v1/v3/v4/v5)
UUID_V1~UUID_V8 各版本专属正则 按版本区分的 UUID

配套新增 EnumRequirement(Requirement/EnumRequirement.php),用于从 \BackedEnum 自动生成路由 requirement。构造函数接受枚举类名或枚举 case 列表,内部通过 preg_quote 转义每个 case 的 value 后用 | 连接;传入非 BackedEnum 类或混入不同枚举的 case 会抛 InvalidArgumentException。典型用法:

use Symfony\Component\Routing\Requirement\EnumRequirement;

$route = new Route('/article/{status}', [], ['status' => new EnumRequirement(ArticleStatus::class)]);

6.2 还带来:MissingMandatoryParametersException 新增 getMissingParameters() 与 getRouteName() 方法;允许 UTF-8 参数名;注解加载器支持 attribute 类型(annotation 的别名);生成 URL 时已编码的斜杠不再被解码或二次编码;条件表达式新增 params 变量。

6.4:FQCN 别名与表达式语言提供者

  • 路由从属性/注解加载时,在适用情况下自动生成 FQCN 与 FQCN::method 别名,便于以类名直接引用路由;
  • AnnotationClassLoader::setResolver() 增加原生返回类型;
  • 加入 Annotation 命名空间下所有类到 Attribute 的别名(7.4 起弃用、8.0 移除);
  • AddExpressionLanguageProvidersPass 从 FrameworkBundle 移入组件(见 DependencyInjection/AddExpressionLanguageProvidersPass.php)。

6.1:Requirement 库的开端

Requirement 与 EnumRequirement 在 6.1 首次登场;Requirement::POSITIVE_INT 则在 6.2 加入。6.1 还支持在注解加载器中声明 attribute 类型,并弃用不传 $routeParameters 给 handleRouteRequirements() 的用法。

5.x:属性路由的开端与配置器革命

5.2:#[Route] 属性与内联约束

  • 新增 #<a href="https://link.gitcode.com/i/438a39f2056afe5648edc6a724b9ad36" target="_blank">Route] 属性支持([Attribute/Route.php),这是路由声明方式的重大转折点。当前该属性支持 path(字符串或按 locale 的数组)、name、requirements、options、defaults、host、methods、schemes、condition、priority、locale、format、utf8、stateless、firewall、env、alias 等全部参数,且标记为 IS_REPEATABLE | TARGET_CLASS | TARGET_METHOD,可在同一目标上重复声明多条路由;
  • 支持 \A 和 \z 作为路由 requirement 的正则起始/结束锚点,对应 Route::sanitizeRequirement() 中剥离 ^/\A 前缀与 $/\z 后缀的逻辑(Route.php);
  • 支持 host 的内联 requirement 与 defaults 定义,即 {subdomain}.example.com 形式的 host 模式同样可内联约束与默认值。

5.1:Configurator 配置器与优先级体系

  • 新增 PhpFileLoader::callConfigurator() 受保护方法,作为自定义路由配置的扩展点;
  • 弃用 RouteCollectionBuilder,改用 RoutingConfigurator 配置器体系——后者成为 PHP 配置的主流入口,配套 CollectionConfigurator、RouteConfigurator、AliasConfigurator、ImportConfigurator 等类(见 Loader/Configurator/);
  • 属性路由新增 priority 选项,RouteCollection::add() 增加 $priority 参数——优先级决定了同路径多条路由的匹配先后;
  • 弃用 RouteCompiler::REGEX_DELIMITER 常量;
  • 新增 ExpressionLanguageProvider(Matcher/ExpressionLanguageProvider.php),为路由条件表达式暴露额外函数;
  • 支持 stateless 关键字:PHP、YAML、XML 配置中均可将路由声明为无状态(_stateless 默认值);
  • 新增 hosts 选项,可为每个 locale 单独配置 host;
  • 新增 RequestContext::fromUri(),便于从 URI 构建默认上下文(RequestContext.php,内部通过 parse_url 解析 scheme/host/port/path)。

5.3:按环境配置与已编码斜杠

  • 已编码斜杠在生成 URL 时不再被解码或二次编码(针对路径段;6.1 扩展至查询参数);
  • XML 与 YAML 加载器支持按环境(per-env)配置;
  • 新增 RoutingConfigurator::env() 获取当前环境;
  • 弃用通过参数数组创建 Route 注解类实例的方式。

4.x:编译型匹配/生成架构与 UTF-8 规范化

4.3:CompiledUrlMatcher / CompiledUrlGenerator 诞生

4.3 引入了组件至今仍在使用的编译型架构核心:

  • CompiledUrlMatcher 与 CompiledUrlMatcherDumper(Matcher/Dumper/):CompiledUrlMatcherDumper 把 RouteCollection 编译为可直接 include 的 PHP 类,CompiledUrlMatcherTrait 提供高性能的编译后匹配逻辑;
  • CompiledUrlGenerator 与 CompiledUrlGeneratorDumper(Generator/Dumper/):同样把 URL 生成逻辑编译为 PHP 类,CompiledUrlGeneratorDumper 生成的类可被 CompiledUrlGenerator 直接使用;
  • 相应地弃用 PhpGeneratorDumper、PhpMatcherDumper 以及 generator_base_class、generator_cache_class、matcher_base_class、matcher_cache_class 等 router 选项(5.0 正式移除);
  • Route 与 CompiledRoute 的 Serializable 实现方法被标记为 @internal、@final,建议改用 __serialize/__unserialize 扩展点(Route.php 即使用这两个方法);
  • 暴露 utf8 Route 选项;配置加载器与配置器中暴露默认的 locale、format;
  • 支持可调用(invokable)服务路由加载器。

4.4:资源导入的 exclude 与加载器迁移

  • import() 方法支持通过 exclude 模式排除导入资源;
  • 弃用 ServiceRouterLoader(改用 ContainerLoader)与 ObjectRouteLoader(改用 ObjectLoader)。

4.2 与 4.0:locale 回退与 UTF-8 强制

  • 4.2:国际化路由新增对无文化后缀 locale(cultureless locale)的回退;
  • 4.0(BC Break):不使用 utf8 选项就不能再使用 UTF-8 路由模式与 UTF-8 requirement,即 UTF-8 能力必须显式开启。

3.x:异常体系与配置关键字完善

3.4:新异常与配置能力

  • 新增 NoConfigurationException(Exception/NoConfigurationException.php);
  • 支持通过 @Route(name="prefix_") 为控制器下所有路由定义前缀;
  • 支持带优先级的路由加载器(prioritized routing loaders);
  • 将匹配参数与默认参数加入重定向响应;
  • YAML/XML 配置支持 controller 关键字。

3.3 与 3.2:参数弃用与类型化默认值

  • 3.3:弃用一批 class 参数:router.options.generator_class、generator_base_class、generator_dumper_class、matcher_class、matcher_base_class、matcher_dumper_class、matcher.cache_class、generator.cache_class(4.0 移除);
  • 3.2:XML 配置支持 bool、int、float、string、list、map 类型的默认值;支持 UTF-8 requirement。

2.x:从树形集合到扁平集合的架构转折

2.x 是组件奠基并发生两次重大 BC Break 的阶段,CHANGELOG 为此给出了完整迁移代码,是升级老项目最值得细读的部分。

2.2:pattern → path,_scheme/_method → schemes/methods

路由设置多项重命名(旧名将在 3.0 移除):

  • pattern 弃用,改用 path;
  • _scheme、_method requirement 迁移为独立的 schemes、methods 设置。

YAML 迁移对照:

# Before
article_edit:
    pattern: /article/{id}
    requirements: { '_method': 'POST|PUT', '_scheme': 'https', 'id': '\d+' }

# After
article_edit:
    path: /article/{id}
    methods: [POST, PUT]
    schemes: https
    requirements: { 'id': '\d+' }

XML 迁移对照:

<!-- Before -->
<route id="article_edit" pattern="/article/{id}">
    <requirement key="_method">POST|PUT</requirement>
    <requirement key="_scheme">https</requirement>
    <requirement key="id">\d+</requirement>
</route>

<!-- After -->
<route id="article_edit" pattern="/article/{id}" methods="POST PUT" schemes="https">
    <requirement key="id">\d+</requirement>
</route>

PHP 迁移对照:

// Before
$route = new Route();
$route->setPattern('/article/{id}');
$route->setRequirement('_method', 'POST|PUT');
$route->setRequirement('_scheme', 'https');

// After
$route = new Route();
$route->setPath('/article/{id}');
$route->setMethods(['POST', 'PUT']);
$route->setSchemes('https');

2.2:RouteCollection 扁平化 BC Break

RouteCollection 不再以树形结构工作,而是扁平的路由数组。用 PHP 构建集合时必须先向子集合添加路由、再将其加入父集合(YAML/XML 不受影响)。这是本次 BC Break 中影响面最大的一处:

// Before(错误顺序:先挂载子集合,再往里加路由)
$rootCollection = new RouteCollection();
$subCollection = new RouteCollection();
$rootCollection->addCollection($subCollection);
$subCollection->add('foo', new Route('/foo'));

// After(正确顺序:先填充子集合,再挂载)
$rootCollection = new RouteCollection();
$subCollection = new RouteCollection();
$subCollection->add('foo', new Route('/foo'));
$rootCollection->addCollection($subCollection);

并且必须自底向上逐层 addCollection:

$childCollection->addCollection($grandchildCollection);
$rootCollection->addCollection($childCollection);

同版本的其余变更:

  • 弃用 RouteCollection::getParent()、getRoot()(2.3 移除树概念);
  • 空前缀调用 addPrefix 不再允许:addPrefix('', $defaultsArray, $requirementsArray, $optionsArray) 这类用法必须改用 addDefaults()、addRequirements()、addOptions();
  • 弃用 addPrefix() 的 $options 参数与 getPrefix() 方法(前缀优化改由 dumper 在匹配时完成,且 2.2 中进一步增强了分组能力);
  • addCollection() 只应传单个参数,其余参数($prefix、$default、$requirements、$options)弃用,改用 addPrefix():
// Before
$parentCollection->addCollection($collection, '/prefix', [...], [...]);

// After
$collection->addPrefix('/prefix', [...], [...]);
$parentCollection->addCollection($collection);
  • @Route 支持方法默认参数值;相邻占位符无需分隔符(如 /{x}{y}{z}.{_format});占位符间起分隔作用的字符改为白名单机制,修复 /prefix{var}suffix 这类文本环绕变量的路由;
  • 变量默认 requirement 微调:原先禁止变量前后字符,现改为只禁止 / 与其后字符——旧规则下 /index.{_format} 会被 /index.ht/ml 匹配到;默认 requirement 改用占有型量词(possessive quantifiers),避免不必要的回溯,匹配性能提升最高约 20%;
  • ConfigurableRequirementsInterface::setStrictRequirements(null) 可完全关闭生成 URL 时的 requirement 校验,生产环境建议这样配置(参数必然满足约束,否则链接本身就坏了);
  • 路由名不再受限,非字母数字字符同样允许;
  • RouteCompilerInterface::compile(Route $route) 改为静态(仅影响自定义 RouteCompiler 的实现者);
  • UrlGenerator 支持生成相对路径与网络路径(如 ../parent-file、//example.com/dir/file)。generate() 第三个参数 $referenceType 推荐使用 UrlGeneratorInterface 常量,布尔参数旧写法仍兼容。

2.8:referenceType 常量与资源加载扩展

  • 弃用 $referenceType 参数的硬编码布尔值,改用 UrlGeneratorInterface 常量:
// Before
$router->generate('blog_show', ['slug' => 'my-blog-post'], true);

// After
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
$router->generate('blog_show', ['slug' => 'my-blog-post'], UrlGeneratorInterface::ABSOLUTE_URL);
  • 支持指定目录并递归加载其中全部路由配置文件;
  • 新增 ObjectRouteLoader 与 ServiceRouteLoader,允许通过调用对象/服务上的方法加载路由(4.4 分别被 ObjectLoader/ContainerLoader 取代)。

2.1 与 2.5、2.3:奠基与清理

  • 2.1:新增 RequestMatcherInterface、RequestContext::fromRequest()(RequestContext.php,从 HttpFoundation 的 Request 同步 base URL、path info、method、host、scheme、端口与查询串);UrlMatcher 在 scheme 不符时不再抛 \LogicException;新增 TraceableUrlMatcher;prefix(含导入路由)支持为占位符定义 options、默认值与 requirements;新增 RouterInterface::getRouteCollection;UrlMatcher 对路由参数只做一次 URL 解码(此前解码两次),并用 rawurldecode() 支持输入路径中的 +;RouteCollection::remove() 也会从父集合移除路由;新增 ConfigurableRequirementsInterface,允许在生成无效参数值的 URL 时禁用异常(改为生成空 URL)。
  • 2.3:新增 RequestContext::getQueryString()。
  • 2.5:弃用 ApacheMatcherDumper 与 ApacheUrlMatcher(性能收益有限且难以复刻 PHP 实现行为,3.0 移除)。

升级速查:破坏性变更一览

版本 变更 迁移动作
8.0 移除 XML 配置格式 改用 YAML / PHP / 属性
8.0 移除 Annotation 命名空间别名 改用 Attribute 命名空间
8.0 移除属性类 getter/setter 直接使用公共属性
8.0 移除 $routeAnnotationClass 属性 用 setRouteAttributeClass()
8.0 _query 非数组抛异常 保证 _query 传数组
7.0 移除 Doctrine 注解 全部改用原生属性
5.0 移除 PhpGeneratorDumper/PhpMatcherDumper 与相关 router 选项 使用编译型 dumper
4.0 UTF-8 路由必须显式 utf8 选项 涉及 UTF-8 时开启该选项
2.2 pattern → path,_scheme/_method → schemes/methods 按上文对照表迁移
2.2 RouteCollection 树→扁平 自底向上填充子集合后再挂载
2.2 addPrefix 空前缀用法移除 用 addDefaults/addRequirements/addOptions

结语

从 CHANGELOG 可以清晰地看到 Symfony Routing 组件十余年的设计走向:声明方式从 XML 走向 YAML/PHP/属性,匹配与生成从解释执行走向编译型 PHP 类,集成方式从 FrameworkBundle 内置走向独立的 RouterBundle,同时通过 Requirement 常量库、EnumRequirement、条件表达式与 runWith() 等机制不断降低路由配置的复杂度。对使用者而言,当前版本(8.x)应优先采用属性路由 + RouterBundle 配置 + 编译型匹配器/生成器的组合;对升级者而言,本文的破坏性变更速查表与 2.x 迁移对照代码,可作为逐版本升级的最小检查清单。

登录后查看全文
routing