======== はじめに ======== 大きなアプリケーションにとってパフォーマンスはとても重要な側面です。Doctrineはオブジェクトリレーショナルマッピングと同様に大きなデータベース抽象化レイヤーを提供する抽象化ライブラリです。これがポータビリティと開発のしやすさを提供する一方でパフォーマンスの観点から欠点になるのは避けられません。この章では読者がDoctrineのベストなパフォーマンスを得られるようにするためのお手伝いを試みます。 ========== コンパイル ========== Doctrineはとても大きなフレームワークなのでそれぞれのリクエストごとにたくさんのファイルがインクルードされます。これは大きなオーバーヘッドをもたらします。実際これらのファイルオペレーションはデータベースに複数のクエリを送信するのと同じくらい時間がかかります。開発環境ではファイルごとにクラスを明確に分離する方法はうまくゆきますが、プロジェクトが商用ディストリビューションになるとき動作速度がファイルごとのクラス分離の原則よりも優先されます。 Doctrineはこの問題を解決するために``compile()``と呼ばれるメソッドを提供します。compileメソッドは最もよく使われるDoctrineコンポーネントの単独のファイルを作成します。デフォルトではこのファイルは``Doctrine.compiled.php``という名前でDoctrineのrootに作成されます。 コンパイルは複数のファイル(最悪の場合数十のファイル)の代わりにコンパイルされたファイルを含む最も使われるDoctrineのランタイムコンポーネントの単独ファイルを作成するための手段で桁違いのパフォーマンスを改善できます。失敗ケースでは、``Doctrine_Exception``が詳細なエラーを投げます。 Doctrineのコンパイルを処理する``compile.php``という名前のコンパイルスクリプトを作りましょう: // compile.php require\_once('/path/to/doctrine/lib/Doctrine.php'); spl\_autoload\_register(array('Doctrine', 'autoload')); Doctrine\_Core::compile('Doctrine.compiled.php'); ``compile.php``を実行すると``Doctrine.compiled.php``ファイルが``doctrine_test``フォルダのrootに生成されます: $ php compile.php 使用しているデータベースドライバのみをコンパイルしたいのであれば``compile()``に2番目の引数としてドライバの配列を渡すことができます。この例ではMySQLのみを使用しているのであればDoctrineに``mysql``ドライバのみをコンパイルするように伝えましょう: // compile.php // ... Doctrine\_Core::compile('Doctrine.compiled.php', array('mysql')); コンパイルされたDoctrineを含めるために``bootstrap.php``スクリプトを変更できます: // bootstrap.php // ... require\_once('Doctrine.compiled.php'); // ... ====================== コンサーバティブな取得 ====================== おそらく最も重要なルールはコンサーバティブなモードで実際に必要なデータのみを取得することです。ささいなことに聞こえるかもしれませんができることに対する無精や知識の欠如は必要のないオーバーヘッドにつながることがよくあります。 例を見てみましょう: $record = :code:`table->find(`\ id); このようにコードを書くのはどのくらいの頻度でしょうか?便利ですが望むものではありません。上記の例はデータベースからレコードのすべてのカラムを引き出し新しく作成されたオブジェクトにこのデータを投入します。これは不必要なネットワークトラフィックだけでなくDoctrineデータを決して使われないオブジェクトに投入しなければならないことも意味します。 次のようなクエリが理想的ではない理由は読者のみなさんはみんなご存知だと思います: SELECT \* FROM my\_table 上記のコードはどのアプリケーションでも悪いものでDoctrineを使う際にも当てはまります。オブジェクトに必要のないデータを投入するのは時間の無駄使いになるのでDoctrineを使うとより悪くなります。 このカテゴリに所属する別の重要なルールは: **本当に必要なときだけオブジェクトを取得する**ことです。Doctrineはオブジェクトグラフの代わりに"配列グラフ"を取得する機能を持ちます。最初は奇妙なことに聞こえるかもしれません。ではなぜオブジェクトリレーショナルマッパーを使うのかもう一度考えてみましょう。PHPは優れたOOPのために多くの機能で強化された手続き型の言語です。それでも配列はPHPで使うことのできる最も効率的なデータ構造です。複雑なビジネスロジックを実現する際にオブジェクトは最も価値があります。データをオブジェクト構造にラッピングしても恩恵がない場合はリソースの無駄使いです。記事用の関連データを持つすべてのコメントを取得する次のコードを見てみましょう。後で表示するためにこれらをビューに渡します: $q = Doctrine\_Query::create() ->select('b.title, b.author, b.created\_at') ->addSelect('COUNT(t.id) as num\_comments') ->from('BlogPost b') ->leftJoin('b.Comments c') ->where('b.id = ?') ->orderBy('b.created\_at DESC'); $blogPosts = $q->execute(array(1)); 最新のblog投稿をレンダリングするビューもしくはテンプレートを想像してください: .. raw:: html
  • - Posted on by . () .. raw:: html
  • ビューの中で配列の代わりにオブジェクトを使う利点を想像できますか?ビューの中でビジネスロジックを実行しているわけではないですよね?1つのパラメータによってたくさんの不必要な処理をしなくて済みます: // ... $blogPosts = $q->execute(array(1), Doctrine\_Core::HYDRATE\_ARRAY); 望むのであれば``setHydrationMethod()``メソッドを使うこともできます: // ... $q->setHydrationMode(Doctrine\_Core::HYDRATE\_ARRAY); $blogPosts = $q->execute(array(1)); 上記のコードはデータをオブジェクトではなくはるかに負荷が低い配列にハイドレイトします。 **NOTE** 配列のハイドレーションに関して1つの素晴らしいことはオブジェクトで``ArrayAccess``を使用する場合配列のハイドレーションを使用するようにクエリを切り替えばコードはまったく同じように動作します。例えば最新のblog投稿をレンダリングするために書いた上記のコードはその背後のクエリを配列のハイドレーションに切り替えるときに動作します。 ときに、オブジェクトや配列ではなくPDOから直接出力したいことがあります。これを行うには、ハイドレーションモードを **``Doctrine\_Core::HYDRATE_NONE``**に切り替えます。例は次の通りです: $q = Doctrine\_Query::create() ->select('SUM(d.amount)') ->from('Donation d'); $results = $q->execute(array(), Doctrine\_Core::HYDRATE\_NONE); 結果を出力するとDQLクエリに依存する配列形式の値が見つかります: print\_r($results); この例では結果は次のコードでアクセスできます: $total = $results[0][1]; ============================ クラスファイルをバンドルする ============================ Doctrineもしくは他の大きなOOライブラリもしくはフレームワークを使うとき通常のHTTPリクエストでインクルードされるファイルの数は秘女に大きくなります。1つのリクエストで50-100のファイルがインクルードされるのも珍しいことではありません。これはたくさんのディスクオペレーションにつながるのでパフォーマンスに大きな影響を及ぼします。これは一般的に開発環境では問題ありませんが、本番環境には適していません。この問題に対処するための推奨方法はもっともよく使われるライブラリのクラスを本番環境用に1つのファイルにまとめ、不要なホワイトスペース、改行とコメントを剥ぎ取ります。この方法によってバイトコードキャッシュ無しでも大きなパフォーマンスの改善ができます(次のセクションをご覧ください。このようなバンドルを作成するベストな方法は自動化ビルド処理、例えばPhingによる方法です。 ================================ バイトコードキャッシュを使用する ================================ APCのようなバイトコードキャッシュは実行に先駆けてPHPによって生成されます。このことはファイルの解析とバイトコードの作成は毎回のリクエストごとではなく一度だけ行われることを意味します。これはとりわけ大きなライブラリ/フレームワークを利用しているときに役立ちます。本番環境用のファイルを利用することで大きなパフォーマンスの改善ができます。キャッシュを最適化するための設定オプションがたくさんあるのでバイトコードを最大限活用するにはマニュアルページを調べる必要があります。 ====================== オブジェクトを開放する ====================== バージョン5.2.5に関して、PHPは循環参照を持つオブジェクトグラフのガーベッジコレクションを行うことができません。例えば親が子への参照を持ち子が親に参照を持つ場合です。多くのDoctrineオブジェクトモデルがこのようなリレーションを持つので、オブジェクトがスコープの外側にゆくときでさえPHPはメモリーを解放しません。 大抵のPHPアプリケーションに関して、この問題はほとんど関係しません。PHPスクリプトの実行時間が短い傾向にあるからです。長い実行時間のスクリプト、例えば、バルクデータインポーターやエクスポーターなどの実行時間の長いスクリプトは循環参照のチェーンを破棄しない限りメモリを使い果たしてしまうことがあります。Doctrineは``Doctrine\_Record``、``Doctrine\_Collection``、と``Doctrine_Query``で``free()``メソッドを提供します。このメソッドはこれらのオブジェクト上の循環参照を削除します。ガベージコレクション用にこれらを開放します。使い方は次の通りです: 大量のレコードを挿入するときにオブジェクトを解放します: for ($i = 0; $i < 1000; $i++) { $object = createBigObject(); $object->save(); $object->free(true); } 同じ方法でクエリオブジェクトを解放することもできます: for ($i = 0; $i < 1000; $i++) { $q = Doctrine\_Query::create() ->from('User u'); $results = $q->fetchArray(); $q->free(); } もしくはループの中のそれぞれのクエリに対して同じクエリオブジェクトを使う場合よりベターです: $q = Doctrine\_Query::create() ->from('User u'); for ($i = 0; $i < 1000; $i++) { $results = $q->fetchArray(); $q->free(); } ============== 他のティップス ============== **DQLパーサーを手助けする** DQLを使用する際に可能な方法は2つあります。最初の方法はプレーンなDQLクエリを書きこれらを``Doctrine\_Connection::query($dql)``に渡すことです。2番目の方法は``Doctrine\_Query``オブジェクトと流れるようなインターフェイスを使うことです。とてもシンプルなクエリ以外は後者の方が望ましいです。これは``Doctrine_Query``オブジェクトとそのメソッドを利用することでDQLパーサーの負担が少し減るからです。これは解析する必要のあるクエリの量を減らすのでそれゆえ速くなります。 **効率的なリレーションのハンドリング** 2つのコンポーネントの間のリレーションを追加したい場合次のようなことを行うべきでは**ありません**: **NOTE** 次の例は``Role``と``User``の多対多のリレーションを想定します。 $role = new Role(); $role->name = 'New Role Name'; $user->Roles[] = $newRole; **CAUTION** 上記のコードはロールがまだロードされていない場合データベースからすべてのロールをロードします!1つの新しいリンクを追加するためだけに! 代わりに次の方法が推奨されます: $userRole = new UserRole(); $userRole->role\_id = $role\_id; $userRole->user\_id = $user\_id; $userRole->save(); ====== まとめ ====== Doctrineのパフォーマンスを改善するメソッドはたくさん存在します。この章で説明されたメソッドを検討することを多いに推奨します。 Doctrineで使われている[doc technology テクノロジー]を学ぶために次の章に移動します。