10. Doctrine Internals explained

Object relational mapping is a complex topic and sufficiently understanding how Doctrine works internally helps you use its full power.

オブジェクト リレーショナル マッピングは複雑なトピックであり、Doctrine が内部でどのように機能するかを十分に理解することは、その機能を最大限に活用するのに役立ちます。

10.1. How Doctrine keeps track of Objects

Doctrine uses the Identity Map pattern to track objects. Whenever you fetch an object from the database, Doctrine will keep a reference to this object inside its UnitOfWork. The array holding all the entity references is two-levels deep and has the keys “root entity name” and “id”. Since Doctrine allows composite keys the id is a sorted, serialized version of all the key columns.

Doctrine は Identity Map パターンを使用してオブジェクトを追跡します。データベースからオブジェクトを取得するたびに、Doctrine はこのオブジェクトへの参照をその UnitOfWork 内に保持します。すべてのエンティティ参照を保持する配列は 2 レベルの深さで、キー「ルート エンティティ名」と「id」を持ちます。 Doctrine では複合キーが許可されているため、id はすべてのキー列のソートされ、シリアル化されたバージョンです。

This allows Doctrine room for optimizations. If you call the EntityManager and ask for an entity with a specific ID twice, it will return the same instance:

これにより、Doctrine に最適化の余地が生まれます。 EntityManager を呼び出して、特定の ID を持つエンティティを 2 回要求すると、同じインスタンスが返されます。

public function testIdentityMap(): void
{
    $objectA = $this->entityManager->find('EntityName', 1);
    $objectB = $this->entityManager->find('EntityName', 1);

    $this->assertSame($objectA, $objectB)
}

Only one SELECT query will be fired against the database here. In the second EntityManager#find() call Doctrine will check the identity map first and doesn’t need to make that database roundtrip.

ここでは、データベースに対して 1 つの SELECT クエリのみが実行されます。 2 番目の EntityManager#find() 呼び出しで、Doctrine は最初に ID マップをチェックし、そのデータベースのラウンドトリップを行う必要はありません。

Even if you get a proxy object first then fetch the object by the same id you will still end up with the same reference:

最初にプロキシ オブジェクトを取得してから、同じ ID でオブジェクトをフェッチしても、同じ参照になります。

public function testIdentityMapReference(): void
{
    $objectA = $this->entityManager->getReference('EntityName', 1);
    // check for proxyinterface
    $this->assertInstanceOf('Doctrine\Persistence\Proxy', $objectA);

    $objectB = $this->entityManager->find('EntityName', 1);

    $this->assertSame($objectA, $objectB)
}

The identity map being indexed by primary keys only allows shortcuts when you ask for objects by primary key. Assume you have the following persons table:

主キーによって索引付けされている ID マップは、主キーによってオブジェクトを要求する場合にのみショートカットを許可します。次の persontable があるとします。

id | name
-------------
1  | Benjamin
2  | Bud

Take the following example where two consecutive calls are made against a repository to fetch an entity by a set of criteria:

次の例では、一連の基準によってエンティティを取得するために、リポジトリに対して 2 つの連続した呼び出しが行われます。

public function testIdentityMapRepositoryFindBy()
{
    $repository = $this->entityManager->getRepository('Person');
    $objectA = $repository->findOneBy(array('name' => 'Benjamin'));
    $objectB = $repository->findOneBy(array('name' => 'Benjamin'));

    $this->assertSame($objectA, $objectB);
}

This query will still return the same references and $objectA and $objectB are indeed referencing the same object. However when checking your SQL logs you will realize that two queries have been executed against the database. Doctrine only knows objects by id, so a query for different criteria has to go to the database, even if it was executed just before.

このクエリは引き続き同じ参照を返し、$objectA と $objectBare は実際に同じオブジェクトを参照しています。ただし、SQL ログを確認すると、データベースに対して 2 つのクエリが実行されていることがわかります。 Doctrine はオブジェクトを id でしか認識していないため、異なる基準のクエリは、直前に実行された場合でもデータベースに移動する必要があります。

But instead of creating a second Person object Doctrine first gets the primary key from the row and check if it already has an object inside the UnitOfWork with that primary key. In our example it finds an object and decides to return this instead of creating a new one.

しかし、2 番目の Person オブジェクトを作成する代わりに、Doctrine は最初に行から主キーを取得し、その主キーを持つ UnitOfWork 内に既にオブジェクトがあるかどうかを確認します。この例では、オブジェクトを見つけて、新しいオブジェクトを作成する代わりに、this を返すことにしました。

The identity map has a second use-case. When you call EntityManager#flush Doctrine will ask the identity map for all objects that are currently managed. This means you don’t have to call EntityManager#persist over and over again to pass known objects to the EntityManager. This is a NO-OP for known entities, but leads to much code written that is confusing to other developers.

ID マップには 2 番目の使用例があります。 EntityManager#flushDoctrine を呼び出すと、現在管理されているすべてのオブジェクトの ID マップが要求されます。つまり、既知のオブジェクトを EntityManager に渡すために、EntityManager#persist を何度も呼び出す必要はありません。これは既知のエンティティに対する NO-OP ですが、他の開発者を混乱させる多くのコードが記述されることになります。

The following code WILL update your database with the changes made to the Person object, even if you did not call EntityManager#persist:

次のコードは、EntityManager#persist を呼び出さなくても、thePerson オブジェクトに加えられた変更でデータベースを更新します。

<?php
$user = $entityManager->find("Person", 1);
$user->setName("Guilherme");
$entityManager->flush();

10.2. How Doctrine Detects Changes

Doctrine is a data-mapper that tries to achieve persistence-ignorance (PI). This means you map php objects into a relational database that don’t necessarily know about the database at all. A natural question would now be, “how does Doctrine even detect objects have changed?”.

Doctrine は、persistence-ignorance (PI) を実現しようとするデータ マッパーです。これは、php オブジェクトをリレーショナル データベースにマップすることを意味します。 「Doctrine はオブジェクトが変更されたことをどのように検出するのでしょうか?」

For this Doctrine keeps a second map inside the UnitOfWork. Whenever you fetch an object from the database Doctrine will keep a copy of all the properties and associations inside the UnitOfWork. Because variables in the PHP language are subject to “copy-on-write” the memory usage of a PHP request that only reads objects from the database is the same as if Doctrine did not keep this variable copy. Only if you start changing variables PHP will create new variables internally that consume new memory.

この Doctrine は UnitOfWork 内に 2 番目のマップを保持します。データベースからオブジェクトを取得するたびに、Doctrine は UnitOfWork 内のすべてのプロパティと関連付けのコピーを保持します。 PHP 言語の変数は「コピー オン ライト」の対象となるため、データベースからオブジェクトを読み取るだけの PHP リクエストのメモリ使用量は、Doctrine がこの変数のコピーを保持しなかった場合と同じになります。変数の変更を開始した場合にのみ、PHP は内部で新しい変数を作成し、新しいメモリを消費します。

Now whenever you call EntityManager#flush Doctrine will iterate over the Identity Map and for each object compares the original property and association values with the values that are currently set on the object. If changes are detected then the object is queued for a SQL UPDATE operation. Only the fields that actually changed are updated.

EntityManager#flush を呼び出すたびに、Doctrine はアイデンティティ マップを繰り返し処理し、オブジェクトごとに元のプロパティと関連付けの値をオブジェクトに現在設定されている値と比較します。変更が検出された場合、オブジェクトは SQL UPDATE 操作のためにキューに入れられます。実際に変更されたフィールドのみが更新されます。

This process has an obvious performance impact. The larger the size of the UnitOfWork is, the longer this computation takes. There are several ways to optimize the performance of the Flush Operation:

このプロセスは明らかにパフォーマンスに影響を与えます。 UnitOfWork のサイズが大きいほど、この計算にかかる時間は長くなります。フラッシュ操作のパフォーマンスを最適化するには、いくつかの方法があります。

  • Mark entities as read only. These entities can only be inserted or removed, but are never updated. They are omitted in the changeset calculation.

    エンティティを読み取り専用としてマークします。これらのエンティティは挿入または削除のみ可能で、更新されることはありません。それらは変更セットの計算では省略されます。

  • Temporarily mark entities as read only. If you have a very large UnitOfWork but know that a large set of entities has not changed, just mark them as read only with $entityManager->getUnitOfWork()->markReadOnly($entity).

    エンティティを一時的に読み取り専用としてマークします。 UnitOfWork が非常に大きいが、多数のエンティティが変更されていないことがわかっている場合は、$entityManager->getUnitOfWork()->markReadOnly($entity) を使用してそれらを読み取り専用としてマークするだけです。

  • Flush only a single entity with $entityManager->flush($entity).

    $entityManager->flush($entity) を使用して単一のエンティティのみをフラッシュします。

  • Use Change Tracking Policies to use more explicit strategies of notifying the UnitOfWork what objects/properties changed.

    変更追跡ポリシーを使用して、どのオブジェクト/プロパティが変更されたかを UnitOfWork に通知する、より明示的な戦略を使用します。

Note

ノート

Flush only a single entity with $entityManager->flush($entity) is deprecated and will be removed in ORM 3.0. (Details)

$entityManager->flush($entity) を使用して単一のエンティティのみをフラッシュすることは非推奨であり、ORM 3.0 で削除されます。(詳細)

10.3. Query Internals

10.4. The different ORM Layers

Doctrine ships with a set of layers with different responsibilities. This section gives a short explanation of each layer.

Doctrine には、さまざまな責任を持つレイヤーのセットが同梱されています。このセクションでは、各レイヤーについて簡単に説明します。

10.4.1. Hydration

Responsible for creating a final result from a raw database statement and a result-set mapping object. The developer can choose which kind of result they wish to be hydrated. Default result-types include:

生のデータベース ステートメントと結果セット マッピング オブジェクトから最終的な結果を作成する責任があります。開発者は、水和したい結果の種類を選択できます。デフォルトの結果タイプには次のものがあります。

  • SQL to Entities

    SQL からエンティティへ

  • SQL to structured Arrays

    SQL から構造化配列へ

  • SQL to simple scalar result arrays

    SQL から単純なスカラー結果配列へ

  • SQL to a single result variable

    単一の結果変数への SQL

Hydration to entities and arrays is one of the most complex parts of Doctrine algorithm-wise. It can build results with for example:

エンティティと配列へのハイドレーションは、Doctrine アルゴリズムの最も複雑な部分の 1 つです。たとえば、次のように結果を作成できます。

  • Single table selects

    単一のテーブル選択

  • Joins with n:1 or 1:n cardinality, grouping belonging to the same parent.

    n:1 または 1:n カーディナリティで結合し、同じ親に属するグループ化。

  • Mixed results of objects and scalar values

    オブジェクトとスカラー値の混合結果

  • Hydration of results by a given scalar value as key.

    キーとして指定されたスカラー値による結果のハイドレーション。

10.4.2. Persisters

tbr

tbr

10.4.3. UnitOfWork

tbr

tbr

10.4.4. ResultSetMapping

tbr

tbr

10.4.5. DQL Parser

tbr

tbr

10.4.6. SQLWalker

tbr

tbr

10.4.7. EntityManager

tbr

tbr

10.4.8. ClassMetadataFactory

tbr

tbr

Table Of Contents

Previous topic

9. Events

9. イベント

Next topic

11. Association Updates: Owning Side and Inverse Side

11. 関連付けの更新: 所有側と逆側

This Page

Fork me on GitHub