返回首页 Symfony2 Cookbook

Assetic

Bundles

缓存

Composer

配置

控制台

Controller

调试

部署

Doctrine

电子邮件

事件分发器

表达式

表单

前端

日志

分析器

请求

路由

安全

序列化

服务容器

会话

PSR-7

Symfony 版本

模板

测试

升级

验证

Web 服务器

Web 服务

工作流

如何定义虚拟类和接口之间的关系

Bundles 的目标之一是创建一个不具有多个(如果有的话)依赖关系的谨慎的功能 bundles,允许您在其他应用程序中使用该功能,而不包括不必要的项目。

Doctrine2.2 包括一个新的实用工具,称为 ResolveTargetEntityListener,通过截取 Doctrine 内部一定的调用并且在运行时重写您元数据映射的 targetEntity 参数作用。这意思是在您的 bundle 里,您可以在您的映射中使用一个接口或者虚拟类,并期望在运行时正确映射到一个具体的实体。

这个功能允许您在定义不同实体间的关系,而不让它们很难依赖。

背景

假设您有一个 invoiceBundle 提供进销存功能并且 CustomerBundle 包含客户管理工具。您想保持它们分开的状态,因为它们可以在分开的状态下用于其他系统,但对于您的应用程序,您想将其放在一起使用。

在这种情况下,您有一个与一个不存在对象相关的 Invoice 实体 InvoiceSubjectInterface。目标是让 ResolveTargetEntityListener 来取代任何提及与实体对象实现该接口的接口。

设置

本文章使用以下两种基本实体(简洁但不完整)来解释如何设置并使用 ResolveTargetEntityListener

Customer 实体:

// 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
}

Invoice 实体:

// 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:

// 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.
 */
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.

    /**
     * @return string
     */
    public function getName();
}

接下来,您需要配置监听器,告知 DoctrineBundle 关于替代的事情:

YAML:

# app/config/config.yml
doctrine:
    # ...
    orm:
        # ...
        resolve_target_entities:
            Acme\InvoiceBundle\Model\InvoiceSubjectInterface: Acme\AppBundle\Entity\Customer

XML:

<!-- 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>

PHP:

// app/config/config.php
$container->loadFromExtension('doctrine', array(
    'orm' => array(
        // ...
        'resolve_target_entities' => array(
            'Acme\InvoiceBundle\Model\InvoiceSubjectInterface' => 'Acme\AppBundle\Entity\Customer',
        ),
    ),
));

结语

有了 ResolveTargetEntityListener,您可以分离您的 bundles,让它们自己保持合用的状态,但是仍能够定义不同对象之间的关系。通过使用这种方法,您的 bundles 会更容易保持独立。