如何创建一个自定义的路由加载器

3.4 版本
维护中的版本

什么是自定义的路由加载器 

自定义路由加载器(Custom Route Loader)使你能够基于一些约定或pattern(条件)来生成路由。这种用法的一个很好的例子是FOSRestBundle ,它的路由是基于控制器的action方法名称来生成的。

即便使用自定义的路由加载器,你仍然需要手动修改路由配置(如app/config/routing.yml)。

现在的很多bundle都在使用他们自己的路由加载器去完成以上面描述的情况,例如,FOSRestBundleJMSI18nRoutingBundleKnpRadBundleSonataAdminBundle

加载路由 

路由在symfony程序中是通过DelegatingLoader来加载的。这个加载器使用了若干其他loader(delegates/代理)来加载不同类型的资源,如YAML文件和控制器中的@Route@Method注释。这个专用的加载器实现了LoaderInterface,因此产生了两个重要的方法:supports()load()

从Symfony标准版的routing.yml中拿到这几行:

1
2
3
4
# app/config/routing.yml
app:
    resource: '@AppBundle/Controller/'
    type:     annotation

当主加载器解析它时,会尝试遍历所有被注册的委托加载器,然后把给定的资源(@AppBundle/Controller/)和类型(type,如annotation等)作为参数,调用 supports()方法。当其中的一个加载器返回true时,load()方法会被调用,它应该返回一个含有一组Route对象的RouteCollection

以这种方式加载的路由,将被路由器(Router)给缓存起来。定义在默认格式中(XML、YML、PHP文件)的路由,都将以这种方式来操作。

创建一个自定义的加载器 

为了从一些自定义资源(例如,从annotations,YAML 或XML文件以外的资源)中加载路由,你需要创建一个自定义的路由加载器。加载器要实现LoaderInterface接口。

大多数情况下,应继承Loader 而不是自行实现 LoaderInterface

下面的加载器例程支持加载extra类型的路由资源。这个类型名称不能与其他加载器所支持的资源类型相同以免冲突。你要做的只是取一个你想要的具体名称。资源名称本身并未真的用在示例中:

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
// src/AppBundle/Routing/ExtraLoader.php
namespace AppBundle\Routing;
 
use Symfony\Component\Config\Loader\Loader;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
 
class ExtraLoader extends Loader
{
    private $loaded = false;
 
    public function load($resource, $type = null)
    {
        if (true === $this->loaded) {
            throw new \RuntimeException('Do not add the "extra" loader twice');
        }
 
        $routes = new RouteCollection();
 
        // prepare a new route
        // 准备一个新路由
        $path = '/extra/{parameter}';
        $defaults = array(
            '_controller' => 'AppBundle:Extra:extra',
        );
        $requirements = array(
            'parameter' => '\d+',
        );
        $route = new Route($path, $defaults, $requirements);
 
        // add the new route to the route collection
        // 添加新路由到route collection
        $routeName = 'extraRoute';
        $routes->add($routeName, $route);
 
        $this->loaded = true;
 
        return $routes;
    }
 
    public function supports($resource, $type = null)
    {
        return 'extra' === $type;
    }
}

确保你指定的控制器真实存在。在这个例子中你要去创建一个AppBundleExtraController控制器,里面有extraAction方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
// src/AppBundle/Controller/ExtraController.php
namespace AppBundle\Controller;
 
use Symfony\Component\HttpFoundation\Response;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
 
class ExtraController extends Controller
{
    public function extraAction($parameter)
    {
        return new Response($parameter);
    }
}

现在定义一个ExtraLoader服务:

1
2
3
4
5
6
# app/config/services.yml
services:
    app.routing_loader:
        class: AppBundle\Routing\ExtraLoader
        tags:
            - { name: routing.loader }
1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
 
    <services>
        <service id="app.routing_loader" class="AppBundle\Routing\ExtraLoader">
            <tag name="routing.loader" />
        </service>
    </services>
</container>
1
2
3
4
5
6
7
8
9
use Symfony\Component\DependencyInjection\Definition;
 
$container
    ->setDefinition(
        'app.routing_loader',
        new Definition('AppBundle\Routing\ExtraLoader')
    )
    ->addTag('routing.loader')
;

注意这个routing.loader标签。使用这个标签的所有服务都将被标记为潜在的路由加载器,并且会被作为专门的路由加载器被添加到 routing.loader 服务中,该服务是DelegatingLoader的实例。

使用自定义加载器 

如果你没有做任何其他事,你自定义的路由加载器将不会 被调用。剩下要做的,就是将下面几行添加到路由配置中:

1
2
3
4
# app/config/routing.yml
app_extra:
    resource: .
    type: extra
1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
 
    <import resource="." type="extra" />
</routes>
1
2
3
4
5
6
7
// app/config/routing.php
use Symfony\Component\Routing\RouteCollection;
 
$collection = new RouteCollection();
$collection->addCollection($loader->import('.', 'extra'));
 
return $collection;

这里的type键极为重要。它的值应该是“extra”,因为ExtraLoader所支持的就是这个类型,而且这还能确保ExtraLoaderload()方法被调用。这个resource键对于ExtraLoader来说是无足轻重的,所以被设置为“.”。

被定义为使用自定义路由加载器的路由,将被框架自动缓存。因此,每当你在“加载器“的类中做出修改时,不要忘记清除缓存。

更为先进的加载器 

如果你自定义的路由加载器和前例一样继承自Loader,你还可以利用Loader类所提供的解析器(resolver),一个LoaderResolver实例,来加载次级路由资源(secondary routing resources):

当然你仍然需要去实现supports()load()方法。只要你想去加载其他资源时 - 例如一个YAML路由配置文件 - 你就要调用import()方法:

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
// src/AppBundle/Routing/AdvancedLoader.php
namespace AppBundle\Routing;
 
use Symfony\Component\Config\Loader\Loader;
use Symfony\Component\Routing\RouteCollection;
 
class AdvancedLoader extends Loader
{
    public function load($resource, $type = null)
    {
        $collection = new RouteCollection();
 
        $resource = '@AppBundle/Resources/config/import_routing.yml';
        $type = 'yaml';
 
        $importedRoutes = $this->import($resource, $type);
 
        $collection->addCollection($importedRoutes);
 
        return $collection;
    }
 
    public function supports($resource, $type = null)
    {
        return 'advanced_extra' === $type;
    }
}

资源名称和导入的路由配置的类型可以是任何东西,通常应该是被路由配置加载器所支持的(如,YAML、XML、PHP、annotation等)。

本文,包括例程代码在内,采用的是 Creative Commons BY-SA 3.0 创作共用授权。

登录symfonychina 发表评论或留下问题(我们会尽量回复)