跳到内容

动态路由器

编辑此页

此路由器的实现配置为从 RouteProviderInterface 加载路由。 此接口可以轻松地通过 Doctrine 实现。 请参阅以下章节,了解有关默认 PHPCR-ODM 提供程序的更多详细信息,并在下方了解基于 Doctrine ORM 的实现。 如果这些不符合您的需求,您可以构建自己的路由提供程序

您可以配置路由增强器,以确定使用哪个控制器来处理请求,以避免将控制器名称硬编码到路由文档中。

要完全理解动态路由器的功能,另请阅读路由组件文档

配置

加载动态路由器所需的最低配置是指定路由提供程序后端,并在路由器链中注册动态路由器。

注意

当您的项目也使用CoreBundle时,配置 cmf_core 上的持久性就足够了,您无需为动态路由器重复配置。

1
2
3
4
5
6
7
8
9
10
# app/config/packages/cmf_routing.yaml
cmf_routing:
    chain:
        routers_by_id:
            router.default: 200
            cmf_routing.dynamic_router: 100
    dynamic:
        persistence:
            phpcr:
                enabled: true

当没有配置或 cmf_routing.dynamic.enabled 设置为 false 时,动态路由器服务将根本不会加载,从而允许您将 ChainRouter 与您自己的路由器一起使用。

匹配过程

大多数匹配过程在CMF Routing 组件的文档中进行了描述。 唯一的区别是,此 bundle 会将 contentDocument 放入请求属性中,而不是放入路由默认值中,以避免在为当前请求生成 URL 时出现问题。

如果您的控制器应该与路由引用的内容一起工作,则可以(并且应该)在其 Action 方法中声明参数 $contentDocument。 请注意,ContentBundle(不再维护)提供了一个默认控制器,当您不需要任何逻辑时,该控制器使用指定的模板渲染内容。

自定义控制器操作可能如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// src/AppBundle/Controller/ContentController.php
namespace AppBundle\Controller;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

/**
 * A custom controller to handle a content specified by a route.
 */
class ContentController extends Controller
{
    /**
     * @param object $contentDocument the name of this parameter is defined
     *      by the RoutingBundle. You can also expect any route parameters
     *      or $template if you configured templates_by_class (see below).
     *
     * @return Response
     */
    public function demoAction($contentDocument)
    {
        // ... do things with $contentDocument and gather other information
        $customValue = 42;

        return $this->render('content/demo.html.twig', [
            'cmfMainContent' => $contentDocument,
            'custom_parameter' => $customValue,
        ]);
    }
}

注意

DynamicRouter 在匹配过程开始时触发一个事件,请在组件文档中阅读有关此事件的更多信息。

为路由配置控制器

要配置哪个控制器用于哪个路由,您可以配置路由增强器。 它们中的许多都在实现 RouteObjectInterface 的路由上运行。 此接口告知路由了解其内容,并通过方法 getRouteContent() 返回内容。 (如果您想了解有关此接口的更多信息,请参阅 CMF Routing 组件。)

如果配置了,则可能发生的增强功能包括(按优先级顺序排列)

  1. (显式控制器):如果在 getRouteDefaults() 中设置了 _controller,则没有增强器会覆盖该控制器。 如果尚未设置,则仍将插入 _template
  2. controllers_by_type:要求路由文档在 getRouteDefaults() 中返回“type”值。 优先级:60
  3. controllers_by_class:要求路由文档是 RouteObjectInterface 的实例,并为 getRouteContent() 返回一个对象。 检查内容文档是否为映射中类名的 instanceof,如果匹配,则使用该控制器。 使用 Instanceof 而不是直接比较,以处理代理类和其他扩展类。 优先级:50
  4. templates_by_class:要求路由文档是 RouteObjectInterface 的实例,并为 getRouteContent() 返回一个对象。 检查内容文档是否为映射中类名的 instanceof,如果匹配,则该模板将设置为 '_template'。 模板的优先级:40,通用控制器设置为优先级:30
  5. 如果 _template$defaults 中,但到目前为止尚未确定控制器(既未在路由上设置,也未在按类型或类别的控制器中匹配),则选择通用控制器。 优先级:10
  6. 选择默认控制器。 此控制器可以使用默认模板来渲染内容,这可能会进一步决定如何处理此内容。 另请参阅 ContentBundle 文档。 优先级:-100

请参阅配置参考,了解如何配置这些增强器。

如果 ContentBundle 存在于您的应用程序中,则通用控制器和默认控制器默认使用该 bundle 提供的 ContentController

提示

要查看一些示例,请查看 CMF sandbox,特别是路由 fixtures 加载。

提示

您还可以为特定用例定义自己的 RouteEnhancer 类。 请参阅自定义动态路由器。 使用优先级将您的增强器插入到正确的顺序中。

Doctrine PHPCR-ODM 集成

RoutingBundle 附带了 PHPCR-ODM 的路由提供程序实现。 PHPCR 非常适合数据的树状结构。 如果您将 PHPCR-ODM 与提供的路由文档一起使用,则只需将提供程序服务保留为默认值即可。

默认提供程序加载请求中路径处的路由以及所有父路径,以允许某些路径段成为参数。 如果您需要不同的路由加载方式,或者例如从不使用参数,则可以编写自己的提供程序实现,通过使用您自己的服务实现 RouteProviderInterface 来进行优化,并将该服务指定为 cmf_routing.dynamic.route_provider_service_id

PHPCR-ODM 路由文档

所有路由类都必须扩展 Symfony 核心 Route 类。 默认的 PHPCR-ODM 路由文档还实现了 RouteObjectInterface,以将路由与内容链接起来。 它将核心路由的所有功能映射到存储,因此您可以使用 setDefaultsetRequirementsetOptionsetHostnamePattern。 此外,在创建路由时,您可以定义是否应将 .{_format} 附加到模式,并使用 requirements 配置所需的 _format。 另一个构造函数参数使您可以控制路由是否应附加尾部斜杠,因为这无法用 PHPCR 名称表示。 默认情况下没有尾部斜杠。 这两个选项也可以稍后通过 setter 方法更改。

所有路由都位于配置的根路径下,例如 /cms/routes。 可以在 PHP 代码中创建新路由,如下所示

1
2
3
4
5
6
7
8
use Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr\Route;

$route = new Route();
$route->setParentDocument($dm->find(null, '/cms/routes'));
$route->setName('projects');

// set explicit controller
$route->setDefault('_controller', 'app.controller::specialAction');

除非最终用户应该更改 URL 或控制器,否则上述示例可能应该作为在 Symfony 配置文件中配置的路由来完成。

要将内容链接到此路由,只需在文档上设置它即可

1
2
3
4
5
use Symfony\Cmf\Bundle\ContentBundle\Doctrine\Phpcr\Content;

// ...
$content = new Content('my content'); // Content must be a mapped class
$route->setRouteContent($content);

这将使路由将文档放入请求参数中,如果您的控制器指定了名为 $contentDocument 的参数,则将向其传递此文档。

您还可以为 URL 使用变量模式,并使用 setRequirement 定义要求,并使用 setDefault 定义默认值

1
2
3
4
// do not forget leading slash if you want /projects/{id} and not /projects{id}
$route->setVariablePattern('/{id}');
$route->setRequirement('id', '\d+');
$route->setDefault('id', 1);

这定义了一个匹配 URL /projects/<number> 以及 /projects 的路由,因为 id 参数有一个默认值。 这将匹配 /projects/7 以及 /projects,但不匹配 /projects/x-4。 文档仍然存储在 /routes/projects。 这将起作用,因为如上所述,路由提供程序将在所有可能的路径中查找路由文档,并选择第一个匹配的文档。 在我们的示例中,如果 /routes/projects/7 处有一个匹配的路由文档(没有其他参数),则会选择它。 否则,路由检查 /routes/projects 是否具有匹配的模式。 如果没有,则检查 /routes 上的顶级文档是否有匹配的模式。

模式、默认值和要求的语义和规则与核心路由中的完全相同。 如果您有多个参数,或参数之后的静态位,请将它们作为变量模式的一部分

1
2
3
$route->setVariablePattern('/{context}/item/{id}');
$route->setRequirement('context', '[a-z]+');
$route->setRequirement('id', '\d+');

注意

RouteDefaultsValidator 验证路由默认参数。 有关更多信息,请参阅自定义动态路由器

通过上面的示例,如果您的路由上设置了内容,并且有一个带有 {id} 的变量模式,则您的控制器可以同时期望 $id 参数和 $contentDocument。 内容可用于定义每个 id 都相同的介绍部分。 如果您不需要内容,也可以省略在路由文档上设置内容文档。

注意

有关路由和翻译的信息,请参阅下方

Doctrine ORM 集成

或者,您可以通过指定配置的 persistence.orm 部分来使用 Doctrine ORM 提供程序。 它执行类似的工作,但顾名思义,从 ORM 数据库加载 Route 实体。

注意

如果您的应用程序没有至少 DoctrineBundle 1.3.0,则必须安装 CoreBundle 才能使用此功能。

ORM 路由实体

如果您使用 ORM 路由提供程序(Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Orm\RouteProvider),则本节中的示例适用。 它使用 Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Orm\RoutestaticPrefix 字段来查找路由候选对象。

Symfony Cmf 路由系统允许我们从路由加载任何内容。 这意味着实体路由可以引用不同类型的实体。 但是 Doctrine ORM 无法建立这种映射关联。 为此,ORM RouteProvider 遵循 FQN:id 的模式。 也就是说,完整的模型类名称,然后是冒号,然后是 id。 您只需要使用 RouteObjectInterface::CONTENT_ID 键将其添加到路由的默认参数中。 cmf_routing.content_repository 服务可以帮助您轻松完成此操作。 可以在 PHP 代码中创建新路由,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// src/AppBundle/DataFixtures/ORM/LoadPostData.php
namespace AppBundle\DataFixtures\ORM;

use AppBundle\Entity\Post;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Orm\Route;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;

class LoadPostData implements FixtureInterface, ContainerAwareInterface
{
    use ContainerAwareTrait;

    /**
     * @param ObjectManager $manager
     */
    public function load(ObjectManager $manager)
    {
        $post = new Post();
        $post->setTitle('My Content');
        $manager->persist($post);
        $manager->flush(); // flush to be able to use the generated id

        $contentRepository = $this->container->get('cmf_routing.content_repository');

        $route = new Route();
        $route->setName('my-content');
        $route->setStaticPrefix('/my-content');
        $route->setDefault(RouteObjectInterface::CONTENT_ID, $contentRepository->getContentId($post));
        $route->setContent($post);
        $post->addRoute($route); // Create the backlink from content to route

        $manager->persist($post);
        $manager->flush();
    }
}

现在,CMF 将能够处理 URL /my-content 的请求。

注意

在路由上设置内容之前,请确保内容已经具有 id。 到内容的路由链接仅适用于单列 id。

本示例中的 Post 实体内容可能如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// src/AppBundle/Entity/Post.php
namespace AppBundle\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Symfony\Cmf\Component\Routing\RouteReferrersInterface;

/**
 * @ORM\Table(name="post")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\PostRepository")
 */
class Post implements RouteReferrersInterface
{
    /** .. fields like title and body */

    /**
     * @var RouteObjectInterface[]|ArrayCollection
     *
     * @ORM\ManyToMany(targetEntity="Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Orm\Route", cascade={"persist", "remove"})
     */
    private $routes;

    public function __construct()
    {
        $this->routes = new ArrayCollection();
    }

    /**
     * @return RouteObjectInterface[]|ArrayCollection
     */
    public function getRoutes()
    {
        return $this->routes;
    }

    /**
     * @param RouteObjectInterface[]|ArrayCollection $routes
     */
    public function setRoutes($routes)
    {
        $this->routes = $routes;
    }

    /**
     * @param RouteObjectInterface $route
     *
     * @return $this
     */
    public function addRoute($route)
    {
        $this->routes[] = $route;

        return $this;
    }

    /**
     * @param RouteObjectInterface $route
     *
     * @return $this
     */
    public function removeRoute($route)
    {
        $this->routes->removeElement($route);

        return $this;
    }
}

由于您在路由上设置了 content_id 默认值,因此控制器可以期望 $contentDocument 参数。 您现在可以配置哪个模板或哪个特殊控制器应该处理 Post 实体,使用 配置参考 中说明的 templates_by_type resp. controllers_by_type 配置。

ORM 路由支持更多内容,例如路由参数、要求和默认值。 这在路由文档部分中进行了解释。

区域设置和路由对象

PHPCR-ODM 路由和 ORM 路由都不应是已翻译的文档本身。 路由表示单个 url,不建议在同一 url 下提供多个翻译。

请确保在配置中配置有效的区域设置,以便 bundle 可以最佳地处理区域设置。 配置参考列出了一些调整行为和性能的选项。

对于多语言网站,您有两种选择: 每种语言一个路由对象,并将 _locale 设置为默认值; 具有 add_locale_pattern 选项的单个路由。

每种语言一个路由

使用此方法,您可以为每种语言存储一个单独的路由对象。 这些路由中的每一个都指向相同的翻译内容对象。 要将语言提取到 Symfony 中,请将默认值 _locale 设置为相应的本地值。

然后,Symfony 翻译系统将拾取它。

当选择为内容生成哪个路由时,ContentAwareGenerator 尊重 _locale 默认值。

这种方法的优点是您可以拥有完全翻译的 URL,这对于提高 URL 的可读性和 SEO 目的可能是理想的。

带有区域模式的单个路由

如果您对每种语言的 URL 相同感到满意,并且只想使用不同的区域设置前缀,则可以存储使用选项 add_locale_pattern 设置为 true 创建的单个路由对象。

此标志使路由在其路径前添加 /{_locale},这意味着它将匹配路径的第一部分作为区域设置。 然后,Symfony 将拾取该区域设置。

使用 DynamicRouter 生成 URL

除了将传入的请求与一组参数匹配之外,Router 还负责从路由及其参数生成 URL。 DynamicRouterSymfony 的 URL 生成功能增加了更多功能。

提示

下面的所有 Twig 示例都使用 path 函数(该函数生成不带域名的 URL),但也可以与 url 函数一起使用。

此外,您可以为生成器指定其他参数,如果路由包含动态模式,则将使用这些参数,否则将像标准路由一样附加为查询字符串。

2.3

由于 `symfony-cmf/routing: 2.3.0`,路由文档应作为 `_route_object` 传递到路由参数中,并且应使用特殊的路由名称 `cmf_routing_object`。 当使用旧版本的路由时,您需要将路由文档作为路由名称传递。

您可以直接将 Route 对象与路由器一起使用

1
2
{# myRoute is an object of class Symfony\Component\Routing\Route #}
<a href="{{ path('cmf_routing_object', {_route_object: myRoute}) }}">Read on</a>

当使用 PHPCR-ODM 持久层时,路由文档的存储库路径被视为路由名称。 因此,您可以指定存储库路径来生成路由

1
2
{# Create a link to / on this server #}
<a href="{{ path('/cms/routes') }}>Home</a>

注意

将 PHPCR-ODM 文档的路径硬编码到模板中是危险的。 管理员用户可能会以破坏应用程序的方式编辑或删除它们。 如果路由必须肯定存在,则它可能应该是静态配置的路由。 但是路由名称可能来自代码等。

DynamicRouter 使用 URL 生成器,该生成器在 RouteReferrersInterface 上运行。这意味着您还可以从任何实现了此接口并为其提供路由的对象生成路由

1
2
{# myContent implements RouteReferrersInterface #}
<a href="{{ path('cmf_routing_object', {_route_object: myContent}) }}>Read on</a>

提示

如果同一内容有多个路由,则首选与当前请求区域设置匹配的路由

此外,生成器还理解带有空路由名称的 content_id 参数,并尝试从配置的内容存储库中查找实现 RouteReferrersInterface 的内容

1
2
3
<a href="{{ path('cmf_routing_object', {'content_id': '/cms/content/my-content'}) }}>
    Read on
</a>

注意

确切地说,如果不需要写入路由,内容实现 RouteReferrersReadInterface 就足够了。有关命名方案的更多信息,请参阅 Bundle 标准

有关实施细节,请参阅 cmf 路由组件文档中“动态路由”部分。

RouterInterface 定义了方法 getRouteCollection 以获取路由器中所有可用的路由。DynamicRouter 能够提供这样的集合,但是此功能默认情况下处于禁用状态,以避免转储大量路由。您可以将 cmf_routing.dynamic.route_collection_limit 设置为大于 0 的值,以使路由器返回限制内的路由,或设置为 false 以禁用限制并返回所有路由。

启用此选项后,像 router:debug 命令或 FOSJsRoutingBundle 这样的工具也将显示来自数据库的路由。

对于 FOSJsRoutingBundle 的情况,如果您使用即将发布的 bundle 版本 2,您可以将 fos_js_routing.router 配置为 router.default 以避免包含动态路由。

处理 RedirectRoutes

此 bundle 还提供了一个控制器来处理 RedirectionRouteInterface 文档。您需要为此接口配置路由增强器

1
2
3
4
5
# app/config/packages/cmf_routing.yaml
cmf_routing:
    dynamic:
        controllers_by_class:
            Symfony\Cmf\Component\Routing\RedirectRouteInterface: cmf_routing.redirect_controller::redirectAction
这项工作,包括代码示例,已根据 Creative Commons BY-SA 3.0 许可获得许可。
目录
    版本