5. Association Mapping

This chapter explains mapping associations between objects.

この章では、オブジェクト間のマッピングの関連付けについて説明します。

Instead of working with foreign keys in your code, you will always work with references to objects instead and Doctrine will convert those references to foreign keys internally.

コード内で外部キーを操作する代わりに、常にオブジェクトへの参照を操作し、Doctrine はこれらの参照を内部的に外部キーに変換します。

  • A reference to a single object is represented by a foreign key.

    単一のオブジェクトへの参照は、外部キーによって表されます。

  • A collection of objects is represented by many foreign keys pointing to the object holding the collection

    オブジェクトのコレクションは、コレクションを保持しているオブジェクトを指す多くの外部キーによって表されます

This chapter is split into three different sections.

この章は 3 つのセクションに分かれています。

  • A list of all the possible association mapping use-cases is given.

    すべての可能なアソシエーション マッピングの使用例のリストが示されています。

  • Mapping Defaults are explained that simplify the use-case examples.

    ユースケースの例を簡素化するマッピングのデフォルトについて説明します。

  • Collections are introduced that contain entities in associations.

    関連付けにエンティティを含むコレクションが導入されました。

One tip for working with relations is to read the relation from left to right, where the left word refers to the current Entity. For example:

リレーションを操作するための 1 つのヒントは、リレーションを左から右に読むことです。左の単語は現在のエンティティを指します。例えば:

  • OneToMany - One instance of the current Entity has Many instances (references) to the referred Entity.

    OneToMany - 現在のエンティティの 1 つのインスタンスには、参照されたエンティティへの多数のインスタンス (参照) があります。

  • ManyToOne - Many instances of the current Entity refer to One instance of the referred Entity.

    ManyToOne - 現在のエンティティの多数のインスタンスが、参照されたエンティティの 1 つのインスタンスを参照します。

  • OneToOne - One instance of the current Entity refers to One instance of the referred Entity.

    OneToOne - 現在のエンティティの 1 つのインスタンスは、参照されたエンティティの 1 つのインスタンスを参照します。

See below for all the possible relations.

考えられるすべての関係については、以下を参照してください。

An association is considered to be unidirectional if only one side of the association has a property referring to the other side.

アソシエーションの一方の側だけがもう一方の側を参照するプロパティを持っている場合、そのアソシエーションは単方向であると見なされます。

To gain a full understanding of associations you should also read about owning and inverse sides of associations

アソシエーションを完全に理解するには、アソシエーションの所有と逆側についても読む必要があります。

5.1. Many-To-One, Unidirectional

A many-to-one association is the most common association between objects. Example: Many Users have One Address:

多対一の関連付けは、オブジェクト間の最も一般的な関連付けです。例: 多くのユーザーが 1 つのアドレスを持っている:

Note

ノート

The above #[JoinColumn] is optional as it would default to address_id and id anyways. You can omit it and let it use the defaults. Likewise, inside the #[ManyToOne] attribute you can omit the targetEntity argument and it will default to Address.

上記の #[JoinColumn] はオプションで、デフォルトで address_id と id になります。これを省略して、デフォルトを使用できます。同様に、#[ManyToOne] 属性内では、targetEntity 引数を省略でき、デフォルトで Address になります。

Generated MySQL Schema:

生成された MySQL スキーマ:

CREATE TABLE User (
    id INT AUTO_INCREMENT NOT NULL,
    address_id INT DEFAULT NULL,
    PRIMARY KEY(id)
) ENGINE = InnoDB;

CREATE TABLE Address (
    id INT AUTO_INCREMENT NOT NULL,
    PRIMARY KEY(id)
) ENGINE = InnoDB;

ALTER TABLE User ADD FOREIGN KEY (address_id) REFERENCES Address(id);

5.2. One-To-One, Unidirectional

Here is an example of a one-to-one association with a Product entity that references one Shipment entity.

以下は、1 つの Shipment エンティティを参照する Product エンティティとの 1 対 1 の関連付けの例です。

Note that the #[JoinColumn] is not really necessary in this example, as the defaults would be the same.

デフォルトは同じであるため、この例では #[JoinColumn] は実際には必要ないことに注意してください。

Generated MySQL Schema:

生成された MySQL スキーマ:

CREATE TABLE Product (
    id INT AUTO_INCREMENT NOT NULL,
    shipment_id INT DEFAULT NULL,
    UNIQUE INDEX UNIQ_6FBC94267FE4B2B (shipment_id),
    PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE Shipment (
    id INT AUTO_INCREMENT NOT NULL,
    PRIMARY KEY(id)
) ENGINE = InnoDB;
ALTER TABLE Product ADD FOREIGN KEY (shipment_id) REFERENCES Shipment(id);

5.3. One-To-One, Bidirectional

Here is a one-to-one relationship between a Customer and a Cart. The Cart has a reference back to the Customer so it is bidirectional.

以下は、Customer と aCart の間の 1 対 1 の関係です。カートには顧客への参照があるため、双方向です。

Here we see the mappedBy and inversedBy attributes for the first time. They are used to tell Doctrine which property on the other side refers to the object.

ここで、mappedBy および inversedBy 属性が初めて表示されます。これらは、反対側のどのプロパティがオブジェクトを参照しているかを Doctrine に伝えるために使用されます。

Note that the @JoinColumn is not really necessary in this example, as the defaults would be the same.

デフォルトは同じであるため、この例では @JoinColumn は実際には必要ないことに注意してください。

Generated MySQL Schema:

生成された MySQL スキーマ:

CREATE TABLE Cart (
    id INT AUTO_INCREMENT NOT NULL,
    customer_id INT DEFAULT NULL,
    UNIQUE INDEX UNIQ_BA388B79395C3F3 (customer_id),
    PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE Customer (
    id INT AUTO_INCREMENT NOT NULL,
    PRIMARY KEY(id)
) ENGINE = InnoDB;
ALTER TABLE Cart ADD FOREIGN KEY (customer_id) REFERENCES Customer(id);

We had a choice of sides on which to place the inversedBy attribute. Because it is on the Cart, that is the owning side of the relation, and thus holds the foreign key.

inversedBy 属性を配置する側を選択できました。これはカート上にあるため、それが関係の所有側であり、したがって外部キーを保持します。

5.4. One-To-One, Self-referencing

You can define a self-referencing one-to-one relationships like below.

以下のような自己参照の 1 対 1 の関係を定義できます。

<?php
#[Entity]
class Student
{
    // ...

    /** One Student has One Mentor. */
    #[OneToOne(targetEntity: Student::class)]
    #[JoinColumn(name: 'mentor_id', referencedColumnName: 'id')]
    private Student|null $mentor = null;

    // ...
}

Note that the @JoinColumn is not really necessary in this example, as the defaults would be the same.

デフォルトは同じであるため、この例では @JoinColumn は実際には必要ないことに注意してください。

With the generated MySQL Schema:

生成された MySQL スキーマで:

CREATE TABLE Student (
    id INT AUTO_INCREMENT NOT NULL,
    mentor_id INT DEFAULT NULL,
    PRIMARY KEY(id)
) ENGINE = InnoDB;
ALTER TABLE Student ADD FOREIGN KEY (mentor_id) REFERENCES Student(id);

5.5. One-To-Many, Bidirectional

A one-to-many association has to be bidirectional, unless you are using a join table. This is because the “many” side in a one-to-many association holds the foreign key, making it the owning side. Doctrine needs the “many” side defined in order to understand the association.

結合テーブルを使用していない限り、1 対多の関連付けは双方向でなければなりません。これは、1 対多の関連付けの「多」側が外部キーを保持し、所有側になるためです。教義は、関連付けを理解するために定義された「多」側を必要とします。

This bidirectional mapping requires the mappedBy attribute on the “one” side and the inversedBy attribute on the “many” side.

この双方向マッピングには、「一」側に mappingBy 属性が必要であり、「多」側に inversedBy 属性が必要です。

This means there is no difference between a bidirectional one-to-many and a bidirectional many-to-one.

これは、双方向の 1 対多と双方向の多対 1 の間に違いがないことを意味します。

Note that the @JoinColumn is not really necessary in this example, as the defaults would be the same.

デフォルトは同じであるため、この例では @JoinColumn は実際には必要ないことに注意してください。

Generated MySQL Schema:

生成された MySQL スキーマ:

CREATE TABLE Product (
    id INT AUTO_INCREMENT NOT NULL,
    PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE Feature (
    id INT AUTO_INCREMENT NOT NULL,
    product_id INT DEFAULT NULL,
    PRIMARY KEY(id)
) ENGINE = InnoDB;
ALTER TABLE Feature ADD FOREIGN KEY (product_id) REFERENCES Product(id);

5.6. One-To-Many, Unidirectional with Join Table

A unidirectional one-to-many association can be mapped through a join table. From Doctrine’s point of view, it is simply mapped as a unidirectional many-to-many whereby a unique constraint on one of the join columns enforces the one-to-many cardinality.

単方向の 1 対多の関連付けは、結合テーブルを使用してマップできます。 Doctrine の観点からは、単方向の多対多として単純にマッピングされ、結合列の 1 つに対する一意の制約が 1 対多のカーディナリティを強制します。

The following example sets up such a unidirectional one-to-many association:

次の例では、このような単方向の 1 対多の関連付けを設定しています。

Generates the following MySQL Schema:

次の MySQL スキーマを生成します。

CREATE TABLE User (
    id INT AUTO_INCREMENT NOT NULL,
    PRIMARY KEY(id)
) ENGINE = InnoDB;

CREATE TABLE users_phonenumbers (
    user_id INT NOT NULL,
    phonenumber_id INT NOT NULL,
    UNIQUE INDEX users_phonenumbers_phonenumber_id_uniq (phonenumber_id),
    PRIMARY KEY(user_id, phonenumber_id)
) ENGINE = InnoDB;

CREATE TABLE Phonenumber (
    id INT AUTO_INCREMENT NOT NULL,
    PRIMARY KEY(id)
) ENGINE = InnoDB;

ALTER TABLE users_phonenumbers ADD FOREIGN KEY (user_id) REFERENCES User(id);
ALTER TABLE users_phonenumbers ADD FOREIGN KEY (phonenumber_id) REFERENCES Phonenumber(id);

5.7. One-To-Many, Self-referencing

You can also setup a one-to-many association that is self-referencing. In this example we setup a hierarchy of Category objects by creating a self referencing relationship. This effectively models a hierarchy of categories and from the database perspective is known as an adjacency list approach.

自己参照する 1 対多の関連付けを設定することもできます。この例では、自己参照関係を作成することによって、Category オブジェクトの階層をセットアップします。これは、カテゴリの階層を効果的にモデル化し、データベースの観点から、隣接リスト アプローチとして知られています。

Note that the @JoinColumn is not really necessary in this example, as the defaults would be the same.

デフォルトは同じであるため、この例では @JoinColumn は実際には必要ないことに注意してください。

Generated MySQL Schema:

生成された MySQL スキーマ:

CREATE TABLE Category (
    id INT AUTO_INCREMENT NOT NULL,
    parent_id INT DEFAULT NULL,
    PRIMARY KEY(id)
) ENGINE = InnoDB;
ALTER TABLE Category ADD FOREIGN KEY (parent_id) REFERENCES Category(id);

5.8. Many-To-Many, Unidirectional

Real many-to-many associations are less common. The following example shows a unidirectional association between User and Group entities:

実際の多対多の関連付けはあまり一般的ではありません。次の例は、User と Groupentities の間の一方向の関連付けを示しています。

Generated MySQL Schema:

生成された MySQL スキーマ:

CREATE TABLE User (
    id INT AUTO_INCREMENT NOT NULL,
    PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE users_groups (
    user_id INT NOT NULL,
    group_id INT NOT NULL,
    PRIMARY KEY(user_id, group_id)
) ENGINE = InnoDB;
CREATE TABLE Group (
    id INT AUTO_INCREMENT NOT NULL,
    PRIMARY KEY(id)
) ENGINE = InnoDB;
ALTER TABLE users_groups ADD FOREIGN KEY (user_id) REFERENCES User(id);
ALTER TABLE users_groups ADD FOREIGN KEY (group_id) REFERENCES Group(id);

Note

ノート

Why are many-to-many associations less common? Because frequently you want to associate additional attributes with an association, in which case you introduce an association class. Consequently, the direct many-to-many association disappears and is replaced by one-to-many/many-to-one associations between the 3 participating classes.

多対多の関連付けがあまり一般的でないのはなぜですか?追加の属性を関連付けに関連付けたい場合がよくあるため、関連付けクラスを導入します。その結果、直接の多対多関連付けがなくなり、3 つの参加クラス間の 1 対多/多対 1 関連付けに置き換えられます。

5.9. Many-To-Many, Bidirectional

Here is a similar many-to-many relationship as above except this one is bidirectional.

これは双方向であることを除いて、上記と同様の多対多の関係です。

The MySQL schema is exactly the same as for the Many-To-Many uni-directional case above.

MySQL スキーマは、上記の多対多一方向の場合とまったく同じです。

5.9.1. Owning and Inverse Side on a ManyToMany Association

For Many-To-Many associations you can chose which entity is the owning and which the inverse side. There is a very simple semantic rule to decide which side is more suitable to be the owning side from a developers perspective. You only have to ask yourself which entity is responsible for the connection management, and pick that as the owning side.

多対多の関連付けの場合、所有するエンティティとその逆側のエンティティを選択できます。開発者の観点から、どちらの側が所有側になるのにより適しているかを決定するための非常に単純なセマンティック ルールがあります。どのエンティティが接続管理を担当しているかを自問し、それを所有側として選択するだけで済みます。

Take an example of two entities Article and Tag. Whenever you want to connect an Article to a Tag and vice-versa, it is mostly the Article that is responsible for this relation. Whenever you add a new article, you want to connect it with existing or new tags. Your “Create Article” form will probably support this notion and allow specifying the tags directly. This is why you should pick the Article as owning side, as it makes the code more understandable:

Article と Tag という 2 つのエンティティの例を見てみましょう。記事をタグに、またはその逆に接続したいときはいつでも、ほとんどの場合、この関係を担当するのは記事です。新しい記事を追加するときはいつでも、それを既存または新しいタグに接続したいと思うでしょう。 「記事の作成」フォームはおそらくこの概念をサポートしており、タグを直接指定できます。これが、コードをより理解しやすくするため、記事を所有側として選択する必要がある理由です。

<?php
class Article
{
    private Collection $tags;

    public function addTag(Tag $tag): void
    {
        $tag->addArticle($this); // synchronously updating inverse side
        $this->tags[] = $tag;
    }
}

class Tag
{
    private Collection $articles;

    public function addArticle(Article $article): void
    {
        $this->articles[] = $article;
    }
}

This allows to group the tag adding on the Article side of the association:

これにより、関連付けの記事側に追加するタグをグループ化できます。

<?php
$article = new Article();
$article->addTag($tagA);
$article->addTag($tagB);

5.10. Many-To-Many, Self-referencing

You can even have a self-referencing many-to-many association. A common scenario is where a User has friends and the target entity of that relationship is a User so it is self referencing. In this example it is bidirectional so User has a field named $friendsWithMe and $myFriends.

自己参照する多対多の関連付けを作成することもできます。一般的なシナリオは、User に友人がいて、その関係のターゲット エンティティが User であるため、自己参照している場合です。この例では双方向であるため、ユーザーには $friendsWithMe および $myFriends という名前のフィールドがあります。

<?php
#[Entity]
class User
{
    // ...

    /**
     * Many Users have Many Users.
     * @var Collection<int, User>
     */
    #[ManyToMany(targetEntity: User::class, mappedBy: 'myFriends')]
    private Collection $friendsWithMe;

    /**
     * Many Users have many Users.
     * @var Collection<int, User>
     */
    #[JoinTable(name: 'friends')]
    #[JoinColumn(name: 'user_id', referencedColumnName: 'id')]
    #[InverseJoinColumn(name: 'friend_user_id', referencedColumnName: 'id')]
    #[ManyToMany(targetEntity: 'User', inversedBy: 'friendsWithMe')]
    private Collection $myFriends;

    public function __construct() {
        $this->friendsWithMe = new ArrayCollection();
        $this->myFriends = new ArrayCollection();
    }

    // ...
}

Generated MySQL Schema:

生成された MySQL スキーマ:

CREATE TABLE User (
    id INT AUTO_INCREMENT NOT NULL,
    PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE friends (
    user_id INT NOT NULL,
    friend_user_id INT NOT NULL,
    PRIMARY KEY(user_id, friend_user_id)
) ENGINE = InnoDB;
ALTER TABLE friends ADD FOREIGN KEY (user_id) REFERENCES User(id);
ALTER TABLE friends ADD FOREIGN KEY (friend_user_id) REFERENCES User(id);

5.11. Mapping Defaults

The @JoinColumn and @JoinTable definitions are usually optional and have sensible default values. The defaults for a join column in a one-to-one/many-to-one association is as follows:

@JoinColumn および @JoinTable の定義は、通常はオプションであり、適切なデフォルト値があります。 1 対 1/多対 1 関連付けの結合列のデフォルトは次のとおりです。

name: "<fieldname>_id"
referencedColumnName: "id"

As an example, consider this mapping:

例として、次のマッピングを検討してください。

This is essentially the same as the following, more verbose, mapping:

これは本質的に、次のより詳細なマッピングと同じです。

The @JoinTable definition used for many-to-many mappings has similar defaults. As an example, consider this mapping:

多対多マッピングに使用される @JoinTable 定義には、同様のデフォルトがあります。例として、次のマッピングを検討してください。

This is essentially the same as the following, more verbose, mapping:

これは本質的に、次のより詳細なマッピングと同じです。

In that case, the name of the join table defaults to a combination of the simple, unqualified class names of the participating classes, separated by an underscore character. The names of the join columns default to the simple, unqualified class name of the targeted class followed by “_id”. The referencedColumnName always defaults to “id”, just as in one-to-one or many-to-one mappings.

その場合、結合テーブルの名前はデフォルトで、参加するクラスの単純な非修飾クラス名をアンダースコア文字で区切って組み合わせたものになります。結合列の名前は、デフォルトで、対象クラスの単純な非修飾クラス名の後に「_id」が続きます。 referencedColumnName は、1 対 1 または多対 1 のマッピングと同様に、常にデフォルトで「id」になります。

Additionally, when using typed properties with Doctrine 2.9 or newer you can skip targetEntity in ManyToOne and OneToOne associations as they will be set based on type. Also nullable attribute on JoinColumn will be inherited from PHP type. So that:

さらに、Doctrine 2.9 以降で型指定されたプロパティを使用する場合、型に基づいて設定されるため、ManyToOne および OneToOne 関連付けで targetEntity をスキップできます。 JoinColumn の nullable 属性も PHP 型から継承されます。となることによって:

Is essentially the same as following:

基本的に次と同じです。

If you accept these defaults, you can reduce the mapping code to a minimum.

これらのデフォルトを受け入れる場合は、マッピング コードを最小限に減らすことができます。

5.12. Collections

Unfortunately, PHP arrays, while being great for many things, are missing features that make them suitable for lazy loading in the context of an ORM. This is why in all the examples of many-valued associations in this manual we will make use of a Collection interface and its default implementation ArrayCollection that are both defined in the Doctrine\Common\Collections namespace. A collection implements the PHP interfaces ArrayAccess, Traversable and Countable.

残念ながら、PHP 配列は多くのことに優れていますが、ORM のコンテキストでの遅延読み込みに適した機能がありません。これが、このマニュアルの多値関連のすべての例で Collection インターフェースを使用する理由です。およびそのデフォルトの実装 ArrayCollection はどちらも Doctrine\Common\Collections 名前空間で定義されています。コレクションは、PHP インターフェイスの ArrayAccess、Traversable、および Countable を実装します。

Note

ノート

The Collection interface and ArrayCollection class, like everything else in the Doctrine namespace, are neither part of the ORM, nor the DBAL, it is a plain PHP class that has no outside dependencies apart from dependencies on PHP itself (and the SPL). Therefore using this class in your model and elsewhere does not introduce a coupling to the ORM.

Collection インターフェースと ArrayCollection クラスは、Doctrine 名前空間の他のすべてのものと同様に、ORM の一部でも DBAL の一部でもありません。これは、PHP 自体 (および SPL) への依存関係以外の外部依存関係を持たない単純な PHP クラスです。したがって、これを使用します。モデルや他の場所のクラスは、ORM への結合を導入しません。

5.13. Initializing Collections

You should always initialize the collections of your @OneToMany and @ManyToMany associations in the constructor of your entities:

エンティティのコンストラクターで @OneToMany および @ManyToMany 関連付けのコレクションを常に初期化する必要があります。

<?php
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;

#[Entity]
class User
{
    /** Many Users have Many Groups. */
    #[ManyToMany(targetEntity: Group::class)]
    private Collection $groups;

    public function __construct()
    {
        $this->groups = new ArrayCollection();
    }

    public function getGroups(): Collection
    {
        return $this->groups;
    }
}

The following code will then work even if the Entity hasn’t been associated with an EntityManager yet:

エンティティがまだ EntityManager に関連付けられていない場合でも、次のコードは機能します。

<?php
$group = new Group();
$user = new User();
$user->getGroups()->add($group);

Table Of Contents

Previous topic

4. Basic Mapping

4. 基本的なマッピング

Next topic

6. Inheritance Mapping

6. 継承マッピング

This Page

Fork me on GitHub