How to Define Relationships with Abstract Classes and Interfaces

One of the goals of bundles is to create discrete bundles of functionality that do not have many (if any) dependencies, allowing you to use that functionality in other applications without including unnecessary items.

バンドルの目的の 1 つは、多くの (存在する場合) 依存関係を持たない機能の個別のバンドルを作成し、不要な項目を含めることなく他のアプリケーションでその機能を使用できるようにすることです。

Doctrine 2.2 includes a new utility called the ResolveTargetEntityListener, that functions by intercepting certain calls inside Doctrine and rewriting targetEntity parameters in your metadata mapping at runtime. It means that in your bundle you are able to use an interface or abstract class in your mappings and expect correct mapping to a concrete entity at runtime.

Doctrine 2.2 には ResolveTargetEntityListener と呼ばれる新しいユーティリティが含まれています。これは Doctrine 内の特定の呼び出しをインターセプトし、実行時にメタデータ マッピングの targetEntity パラメータを書き換えることによって機能します。これは、バンドル内で、マッピングでインターフェイスまたは抽象クラスを使用でき、実行時に具体的なエンティティへの正しいマッピングを期待できることを意味します。

This functionality allows you to define relationships between different entities without making them hard dependencies.

この機能により、異なるエンティティ間の関係を定義することができますが、それらを強い依存関係にする必要はありません。

Background

Suppose you have an InvoiceBundle which provides invoicing functionality and a CustomerBundle that contains customer management tools. You want to keep these separated, because they can be used in other systems without each other, but for your application you want to use them together.

請求機能を提供する InvoiceBundle と、顧客管理ツールを含む CustomerBundle があるとします。これらは他のシステムでは互いに使用せずに使用できるため、これらを分離したままにしておく必要がありますが、アプリケーションでは一緒に使用する必要があります。

In this case, you have an Invoice entity with a relationship to a non-existent object, an InvoiceSubjectInterface. The goal is to get the ResolveTargetEntityListener to replace any mention of the interface with a real object that implements that interface.

この場合、存在しないオブジェクトである InvoiceSubjectInterface との関係を持つ Invoice エンティティがあります。目標は、ResolveTargetEntityListener を取得して、インターフェイスに関する記述を、そのインターフェイスを実装する実際のオブジェクトに置き換えることです。

Set up

This article uses the following two basic entities (which are incomplete for brevity) to explain how to set up and use the ResolveTargetEntityListener.

この記事では、次の 2 つの基本エンティティ (簡潔にするために不完全です) を使用して、ResolveTargetEntityListener をセットアップして使用する方法を説明します。

A Customer entity:

顧客エンティティ:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// src/Entity/Customer.php
namespace App\Entity;

use App\Entity\CustomerInterface as BaseCustomer;
use App\Model\InvoiceSubjectInterface;
use Doctrine\ORM\Mapping as ORM;

#[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
}

An Invoice entity:

請求書エンティティ:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// src/Entity/Invoice.php
namespace App\Entity;

use App\Model\InvoiceSubjectInterface;
use Doctrine\ORM\Mapping as ORM;

/**
 * Represents an Invoice.
 */
#[ORM\Entity]
#[ORM\Table(name: 'invoice')]
class Invoice
{
    /**
     * @var InvoiceSubjectInterface
     */
    #[ORM\ManyToOne(targetEntity: InvoiceSubjectInterface::class)]
    protected $subject;
}

An InvoiceSubjectInterface:

InvoiceSubjectInterface:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// src/Model/InvoiceSubjectInterface.php
namespace App\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.

    public function getName(): string;
}

Next, you need to configure the listener, which tells the DoctrineBundle about the replacement:

次に、DoctrineBundle に置換について伝えるリスナーを設定する必要があります。
  • YAML
    YAML
  • XML
    XML
  • PHP
    PHP
1
2
3
4
5
6
7
# config/packages/doctrine.yaml
doctrine:
    # ...
    orm:
        # ...
        resolve_target_entities:
            App\Model\InvoiceSubjectInterface: App\Entity\Customer

Final Thoughts

With the ResolveTargetEntityListener, you are able to decouple your bundles, keeping them usable by themselves, but still being able to define relationships between different objects. By using this method, your bundles will end up being easier to maintain independently.

ResolveTargetEntityListener を使用すると、バンドルを分離して、それらを単独で使用できる状態に保ちながら、異なるオブジェクト間の関係を定義することができます。この方法を使用すると、バンドルを個別に保守しやすくなります。