昨日の続きです。
各章の説明
The size of a Unit of Work
UnitOfWorkのサイズは主に特定の時点での管理下のエンティティの数を指します。
(あんまり多くのエンティティを更新するトランザクションをはるなよってことかな)
The cost of flushing
flushのコストは主に以下の2つの要因に依存します。
- EntityManagerの現在のUnitOfWorkのサイズ
- 変更の追跡についての設定
UnitOfWorkのサイズは以下で取得できます。
<?php
$uowSize = $em->getUnitOfWork()->size();
UnitOfWorkのサイズはflushのパフォーマンスやメモリの消費量に影響を与えるため、開発中に見直してください。
Direct access to a Unit of Work
EntityManager#getUnitOfWork()を呼ぶことでUnitOfWorkに直接アクセスすることができます。
<?php
$uow = $em->getUnitOfWork();
※UnitOfWorkを直接操作することはお勧めしません。とのこと。
Entity State
エンティティの状態はこれまで言及してきたとおりNEW, MANAGED, REMOVED, DETACHEDの4つがあります。
エンティティの状態を知りたいときは以下のようなコードで知ることができます。
<?php
switch ($em->getUnitOfWork()->getEntityState($entity)) {
case UnitOfWork::STATE_MANAGED:
...
case UnitOfWork::STATE_REMOVED:
...
case UnitOfWork::STATE_DETACHED:
...
case UnitOfWork::STATE_NEW:
...
}
Querying
Doctrine 2は以下の紹介するようなクエリの実行方法を提供しています。
By Primary Key
EntityManager#find($entityName, $id)はPKによるエンティティの取得です。
指定されたエンティティまたはnullを返します。
ショートカットとして以下のように書くこともできます。
<?php
// $em instanceof EntityManager
$user = $em->getRepository('MyProject\Domain\User')->find($id);
By Simple Conditions
シンプルな条件ならfindByやfindOneByを使うことができます。
order byやlimitとoffsetをつけることも可能。
<?php
$tenUsers = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => 20), array('name' => 'ASC'), 10, 0);
配列を指定すればin句に。
<?php
$users = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => array(20, 30, 40)));
// translates roughly to: SELECT * FROM users WHERE age IN (20, 30, 40)
マジックメソッドを使ったやり方もできる。
<?php
// A single user by its nickname
$user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb'));
// A single user by its nickname (__call magic)
$user = $em->getRepository('MyProject\Domain\User')->findOneByNickname('romanb');
countも取れる。
<?php
// Check there is no user with nickname
$availableNickname = 0 === $em->getRepository('MyProject\Domain\User')->count(['nickname' => 'nonexistent']);
By Criteria
RepositoryはDoctrine\Common\Collections\Selectableインターフェースをimplements(実装)しています。それが何を意味するのかというと、Doctrine\Common\Collections\Criteriaオブジェクトを作成することができ、それをSelectableインターフェースのmatching($criteria)メソッドに渡すことができます。
By Eager Loading
エンティティ同士の関連をEAGERとしてマッピングすることもできます。
LAZYとは逆に、あるエンティティの取得時に同時にEAGERとして設定された関連のエンティティを取得し、アプリケーションですぐに利用できます。
By Lazy Loading
LAZYとして設定された関連については、関連のエンティティへのアクセス時にDBから取得しますが、アプリケーション的にはEAGERと同様すでに取得済みのように振る舞います。
By DQL
永続化されたエンティティに対する最もパワフルでフレキシブルなクエリ発行のやり方はDoctrine Query Languageです。DQLを使用すると、オブジェクトの言語で永続化されたエンティティを取得できます。DQLは、クラス、フィールド、継承、および関連付けを理解します。DQLは、使い慣れたSQLと構文的に非常に似ていますが、SQLではありません。
DQLクエリはDoctrine\ORM\Queryクラスのインスタンスで表されます。 EntityManager#createQuery($dql)を使用してクエリを作成します。
以下に簡単な例を示します。
<?php
// $em instanceof EntityManager
// All users with an age between 20 and 30 (inclusive).
$q = $em->createQuery("select u from MyDomain\Model\User u where u.age >= 20 and u.age <= 30");
$users = $q->getResult();
By Native Queries
DQLの代わりにネイティブなSQLを使用することもできます。
SQLの結果をResultSetMappingによりDoctrineのオブジェクトに変換できます。
Custom Repositories
EntityManager#getRepository($entityClass)を呼び出すと、Doctrine\ORM\EntityRepositoryが返されます。あなたはこの振る舞いをアノテーションやXMLの設定やyamlの設定で上書きすることができます。
大規模なアプリケーションでは多くのDQLを使うので、カスタムリポジトリはそれらをグルーピングするためのおすすめの方法です。
<?php
namespace MyDomain\Model;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass="MyDomain\Model\UserRepository")
*/
class User
{
}
class UserRepository extends EntityRepository
{
public function getAllAdminUsers()
{
return $this->em->createQuery('SELECT u FROM MyDomain\Model\User u WHERE u.status = "admin"')
->getResult();
}
}
という定義があったとるすと、以下のように呼び出すことができます。
<?php
// $em instanceof EntityManager
$admins = $em->getRepository('MyDomain\Model\User')->getAllAdminUsers();
おわりに
最後のほうがちょっと雑ですが、個人的にはDoctrineについての理解を助けるドキュメントを全部読めて達成感があります。
他に読みたいなと思っているのはこのあたりですかね。
Working with Associations
https://www.doctrine-project.org/projects/doctrine-orm/en/latest/reference/working-with-associations.html
関連まわりは上記の記事が詳しそうです。
関連まわりは結構ハマります。
Transactions and Concurrency
https://www.doctrine-project.org/projects/doctrine-orm/en/latest/reference/transactions-and-concurrency.html#transactions-and-concurrency
flushがトランザクション的に扱われているけれども、SQLレベルのトランザクションもあり、そのあたりどう使い分けるべきか・・・よくわかっていないので。
Best Practices
https://www.doctrine-project.org/projects/doctrine-orm/en/latest/reference/best-practices.html#best-practices
ベストプラクティスとは!?