如何定义抽象类和接口之间的关系

3.4 版本
维护中的版本

bundle的一个目标就是要创建“具有功能性但并不需要很多(如果有)依赖”的独立的bundle,允许你在其他程序中使用这种功能性,而毋须包含不必要的元素。

Doctrine 2.2带来了一个全新的工具,被称为ResolveTargetEntityListener,其功能是在Doctrine中拦截特定的调用,并且在你的元数据映射中实时重写targetEntity参数。这意味着在你的bundle中,你可以在自己的mapping中使用一个接口或抽象类,然后预期在实时条件下,正确地映射到某个具体的entity中。

这个功能允许你在两个不同的entity之间定义关联性,而毋须令其包含硬性依赖。

背景 

假设你有一个InvoiceBundle用来提供发票相关功能,还有一个CustomerBundle,包含了顾客管理工具。你希望保持此二功能独立,因为它们能够被用于其他系统而毋须带上另一个,但是对你自己的程序来说你要同时使用这两个。

在这种场合,你得有一个Invoice entity,它要关联到一个“不存在”的对象——即,一个InvoiceSubjectInterface。目标是要得到ResolveTargetEntityListener,然后,用一个“实现了该接口的真实对象”,来重置接口所提及的任何目标。

设置 

本文使用了下例中的两个基本entity(为了行文而不完整),解释了如何来设置和使用ResolveTargetEntityListener

一个Customer entity:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// src/Acme/AppBundle/Entity/Customer.php
 
namespace Acme\AppBundle\Entity;
 
use Doctrine\ORM\Mapping as ORM;
use Acme\CustomerBundle\Entity\Customer as BaseCustomer;
use Acme\InvoiceBundle\Model\InvoiceSubjectInterface;
 
/**
 * @ORM\Entity
 * @ORM\Table(name="customer")
 */
class Customer extends BaseCustomer implements InvoiceSubjectInterface
{
    // In this example, any methods defined in the InvoiceSubjectInterface
    // are already implemented in the BaseCustomer
    // 本例中,任何定义于InvoiceSubjectInterface中的方法皆已被BaseCustomer所实现
}

一个Invoice entity:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// src/Acme/InvoiceBundle/Entity/Invoice.php
 
namespace Acme\InvoiceBundle\Entity;
 
use Doctrine\ORM\Mapping AS ORM;
use Acme\InvoiceBundle\Model\InvoiceSubjectInterface;
 
/**
 * Represents an Invoice.
 *
 * @ORM\Entity
 * @ORM\Table(name="invoice")
 */
class Invoice
{
    /**
     * @ORM\ManyToOne(targetEntity="Acme\InvoiceBundle\Model\InvoiceSubjectInterface")
     * @var InvoiceSubjectInterface
     */
    protected $subject;
}

一个InvoiceSubjectInterface:

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
// src/Acme/InvoiceBundle/Model/InvoiceSubjectInterface.php
 
namespace Acme\InvoiceBundle\Model;
 
/**
 * An interface that the invoice Subject object should implement.
 * In most circumstances, only a single object should implement
 * this interface as the ResolveTargetEntityListener can only
 * change the target to a single object.
 * 这是invoice对象应该实现的接口。
 * 很多时候,只应有一个对象来实现这个接口,
 * 因为ResolveTargetEntityListener只能改变目标到一个对象
 */
interface InvoiceSubjectInterface
{
    // List any additional methods that your InvoiceBundle
    // will need to access on the subject so that you can
    // be sure that you have access to those methods.
    // 列出了每一个附加方法,你的InvoiceBundle需要用这些方法来
    // 访问Subject,这样可以确保你拥有那些方法的访问权
 
    /**
     * @return string
     */
    public function getName();
}

接下来,你需要配置,它告诉DoctrineBundle关于“替换”的细节:

1
2
3
4
5
6
7
8
# app/config/config.yml
doctrine:
    # ...
    orm:
        # ...
        resolve_target_entities:
            Acme\InvoiceBundle\Model\InvoiceSubjectInterface: Acme\AppBundle\Entity\Customer
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- app/config/config.xml -->
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:doctrine="http://symfony.com/schema/dic/doctrine"
    xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd
                        http://symfony.com/schema/dic/doctrine http://symfony.com/schema/dic/doctrine/doctrine-1.0.xsd">
 
    <doctrine:config>
        <doctrine:orm>
            <!-- ... -->
            <doctrine:resolve-target-entity interface="Acme\InvoiceBundle\Model\InvoiceSubjectInterface">Acme\AppBundle\Entity\Customer</doctrine:resolve-target-entity>
        </doctrine:orm>
    </doctrine:config>
</container>
1
2
3
4
5
6
7
8
9
// app/config/config.php
$container->loadFromExtension('doctrine', array(
    'orm' => array(
        // ...
        'resolve_target_entities' => array(
            'Acme\InvoiceBundle\Model\InvoiceSubjectInterface' => 'Acme\AppBundle\Entity\Customer',
        ),
    ),
));

结束时的思考 

使用ResolveTargetEntityListener,你就能够解耦自己的bundles,保持它们各自可用,却仍能定义不同对象之间的关联性。使用这种方法,你的bundle将变得易于独立维护。

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

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