如何为若干Doctrine实现提供Model类

3.4 版本
维护中的版本

当你打造一个bundle,但它并不只使用Doctrine ORM,而是同时使用CouchDB ODM、MongoDB ODM或是PHPCR ODM的时候,你应该始终只写一个model类。Doctrine bunlde提供了一个compiler pass用于注册你的多个model类的映射。

对于毋需复用的bundle来说,最简单的选项是把你的model类存放在默认的位置:对于Doctrine ORM是Entity,而Doctrine ODM则是Document。但对于可复用的bundle,不要去复制model类(到文件夹下),而是获取自动映射,办法就是compiler pass。

在你的bundle类中,编写以下代码来注册compiler pass。下例是对CmfRoutingBundle而写就,因此部分代码可以根据你的需求来调整:

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
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\DoctrineOrmMappingsPass;
use Doctrine\Bundle\MongoDBBundle\DependencyInjection\Compiler\DoctrineMongoDBMappingsPass;
use Doctrine\Bundle\CouchDBBundle\DependencyInjection\Compiler\DoctrineCouchDBMappingsPass;
use Doctrine\Bundle\PHPCRBundle\DependencyInjection\Compiler\DoctrinePhpcrMappingsPass;
 
class CmfRoutingBundle extends Bundle
{
    public function build(ContainerBuilder $container)
    {
        parent::build($container);
        // ...
 
        $modelDir = realpath(__DIR__.'/Resources/config/doctrine/model');
        $mappings = array(
            $modelDir => 'Symfony\Cmf\RoutingBundle\Model',
        );
 
        $ormCompilerClass = 'Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\DoctrineOrmMappingsPass';
        if (class_exists($ormCompilerClass)) {
            $container->addCompilerPass(
                DoctrineOrmMappingsPass::createXmlMappingDriver(
                    $mappings,
                    array('cmf_routing.model_manager_name'),
                    'cmf_routing.backend_type_orm',
                    array('CmfRoutingBundle' => 'Symfony\Cmf\RoutingBundle\Model')
            ));
        }
 
        $mongoCompilerClass = 'Doctrine\Bundle\MongoDBBundle\DependencyInjection\Compiler\DoctrineMongoDBMappingsPass';
        if (class_exists($mongoCompilerClass)) {
            $container->addCompilerPass(
                DoctrineMongoDBMappingsPass::createXmlMappingDriver(
                    $mappings,
                    array('cmf_routing.model_manager_name'),
                    'cmf_routing.backend_type_mongodb',
                    array('CmfRoutingBundle' => 'Symfony\Cmf\RoutingBundle\Model')
            ));
        }
 
        $couchCompilerClass = 'Doctrine\Bundle\CouchDBBundle\DependencyInjection\Compiler\DoctrineCouchDBMappingsPass';
        if (class_exists($couchCompilerClass)) {
            $container->addCompilerPass(
                DoctrineCouchDBMappingsPass::createXmlMappingDriver(
                    $mappings,
                    array('cmf_routing.model_manager_name'),
                    'cmf_routing.backend_type_couchdb',
                    array('CmfRoutingBundle' => 'Symfony\Cmf\RoutingBundle\Model')
            ));
        }
 
        $phpcrCompilerClass = 'Doctrine\Bundle\PHPCRBundle\DependencyInjection\Compiler\DoctrinePhpcrMappingsPass';
        if (class_exists($phpcrCompilerClass)) {
            $container->addCompilerPass(
                DoctrinePhpcrMappingsPass::createXmlMappingDriver(
                    $mappings,
                    array('cmf_routing.model_manager_name'),
                    'cmf_routing.backend_type_phpcr',
                    array('CmfRoutingBundle' => 'Symfony\Cmf\RoutingBundle\Model')
            ));
        }
    }
}

注意class_exists检查。这是十分关键的,因为你不希望自己的bundle对全部Doctrine bundles有某个强制依赖,而是让用户来选择使用哪个。

compiler pass(编译器传递)对于Doctrine所提供的全部驱动:Annotations、XML、Yaml、PHP以及StaticPHP都提供了工厂方法。该方法的参数如下:

  • 目录绝对路径的一组map(映射)/hash,映射到namespace;

  • 一个容器参数(container parameter)的数组,供你的bundle在指定“需要使用的Doctrine Manager”的名字时使用。在上例中,CmfRoutingBundle存储的将被使用的manager名字,是在cmf_routing.model_manager_name参数之下。compiler pass将附带这个Doctrine要用来“指定默认manager名字”的参数。找到的第一个参数将被使用,映射(mapping)将被注册到(那个名字所对应的)manager。

  • 一个可选的容器参数之名称,它会被compiler pass用到,来决定“是否此种类型的Doctrine将被使用”。如果你的用户安装了一种以上的Doctrine bundle,那么这个参数就有意义了,只不过你的bundle只会用到Doctrine的某一种类型;

  • 一组假名的map/hash,映射到namespace。它在命名约定上应该与Doctrine auto-mapping(自动映射)的相同。在上例中,此参数允许用户调用$om->getRepositary('CmfRoutingBundle:Route')

工厂方法使用的是Doctrine的SymfonyFileLocator,意味着它只能找到XML和YAML映射文件,如果这些文件在名称里没有包含完整命名空间的话。这在设计上即是如此:SymfonyFileLocator把事情简化的办法就是假定这些文件都是类的“快捷版本”,因为它们把类名作为文件名(如BlogPost.orm.xml)。

如果你需要映射基类,你应该像下面这样,注册compiler pass时使用DefaultFileLocator。这段代码来自DoctrineOrmMappingsPass,并修正为使用DefaultFileLocator来替换SymofnyFileLocator

1
2
3
4
5
6
7
8
9
10
11
12
13
private function buildMappingCompilerPass()
{
    $arguments = array(array(realpath(__DIR__ . '/Resources/config/doctrine-base')), '.orm.xml');
    $locator = new Definition('Doctrine\Common\Persistence\Mapping\Driver\DefaultFileLocator', $arguments);
    $driver = new Definition('Doctrine\ORM\Mapping\Driver\XmlDriver', array($locator));
 
    return new DoctrineOrmMappingsPass(
        $driver,
        array('Full\Namespace'),
        array('your_bundle.manager_name'),
        'your_bundle.orm_enabled'
    );
}

注意毋须提供命名空间的假名,除非你的用户被预期为向Doctrine请求那个基类。

现在把你的映射文件放到/Resources/config/doctrine-base下,并使用FQCN类名,以.来分隔而不是\,例如 Other.Namespace.Model.Name.orm.xml。你不可以混用两种分隔符,否则SymofnyFileLocator会混淆。

相对应地继续调整其他的Doctrine implementations即可。

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

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