如何定义抽象类和接口之间的关系
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将变得易于独立维护。