![]() |
|
The symfony and Doctrine book6章 - データを扱う |
|
You are currently reading "The symfony and Doctrine book" which is licensed under the GFDL license.

DoctrineにおいてRDBMSから複雑な結果を読み取りそれらをリレーション構造を表す配列もしくはオブジェクトのデータ構造にハイドレイト(hydrate)できます。 これはDoctrine Query Languageによって行われます。 これは可能な限り最小のクエリですべてのデータを検索するための最良の方法です。 利便性のために単一のテーブルに取り組むとき、シンプルなfinderメソッドを提供します。 このメソッドはこれらのクエリを動的に構築して実行します。
データを読み取りそれらをビルドするための完全なDoctrine_Query APIを提供するためにDoctrineはDQLを使います。
活用例と同様にメソッドの完全な一覧は下記のとおりです。
共通のAPI
| 関数名 | SQL | append | 説明 |
|---|---|---|---|
| where('u.username = ?', 'jwage') | u.username = ? | No | WHEREを設定して既存のWHERE条件をオーバーライドする |
| andWhere('u.username = ?', 'jwage') | AND u.username = ? | Yes | ANDでappendされるWHERE条件を追加する |
| whereIn('u.id', array(1, 2, 3)) | AND u.id IN (?, ?, ?) | Yes | appendされるAND IN WHERE 条件を追加する |
| andWhereIn('u.id', array(1, 2, 3)) | ^ | Yes | whereIn()用のコンビニエンス/プロキシメソッド |
| orWhereIn('u.id', array(1, 2, 3)) | OR u.id IN (?, ?, ?) | Yes | appendされるOR IN WHERE条件を追加する |
| whereNotIn('u.id', array(1, 2, 3)) | AND u.id NOT IN (?, ?, ?) | Yes | appendされるAND NOT IN WHERE条件を追加する |
| andWhereNotIn('u.id', array(1, 2, 3)) | ^ | Yes | whereNotIn()用のコンビニエンス/プロキシメソッド |
| orWhereNotIn('u.id', array(1, 2, 3)) | OR u.id NOT IN (?, ?, ?) | Yes | appendされるOR NOT IN WHEREの条件を追加する |
| orWhere('u.username = ?', 'jwage') | OR u.username = ? | Yes | OR、WHERE条件を追加する |
| groupBy('u.id') | GROUP BY u.id, u.username | No | GROUP BYを設定して既存のGROUP BYをオーバーライドする |
| addGroupBy('u.username') | GROUP BY u.username | Yes | appendされるGROUP BYを追加する that is appended |
| having('num_phonenumbers > 0') | HAVING num_phonenumbers > 0 |
No | HAVINGを設定して既存のHAVINGをオーバーライドする |
| addHaving('u.username = ?', 'jwage') | HAVING u.username = ? | Yes | 追加されるHAVINGを追加する |
選択のAPI
| 関数名 | 説明 |
|---|---|
| distinct($flag = true) | フラグをdistinct selectに設定する |
| select('u.id, u.username, COUNT(p.id) as num_phonenumbers') | SELECTを設定して既存のSELECTをオーバーライドする |
| addSelect('u.email_address') | appendされるselectを追加する |
| from('User u, u.Phonenumber p') | FROMを設定して既存のFROMとjoinをオーバーライドする |
| leftJoin('u.Phonenumber p') | FROMにappendされるLEFT JOINを追加する |
| innerJoin('u.Profile p') | FROMにappendされるINNER JOINを追加する |
| addFrom('u.Phonenumber p') | FROMにappendされるFROM joinを追加する |
| orderBy('u.username') | ORDER BYを設定して既存のORDER BYをオーバーライドする |
| addOrderBy('u.is_active = ?', 1) | appendされるORDER BYを追加する |
| limit(20) | 結果セットを制限するレコードの数を設定する |
| offset(5) | レコードの制限をオフセットする番号を設定する |
更新API
| 関数名 | 説明 |
|---|---|
| forUpdate($flag = true) | FOR UPDATEを使うためにクエリを変更する |
| update('User u') | UPDATEするモデルの名前を指定する |
| set('u.username', '?', 'jwage') | UPDATEクエリのために新しい値を設定する。最初の引数は修正するデータで、2番目はDQLの文字列に直接加える式(?もしくはDBMS関数)、で3番目は新しい値。 |
削除API
| 関数名 | 説明 |
|---|---|
| delete() | 削除するクエリを変更する |
Doctrine_Tableインスタンスから新しいクエリを作成します。
$q = Doctrine::getTable('User')->createQuery('u') ->where('u.username = ?', 'jwage');
新しいクエリを手動で作成する。
$q = Doctrine_Query::create() ->from('User u') ->where('u.username = ?', 'jwage');
上記の2つのクエリは理想的ですが、わかりやすくするために最初のコードは2番目のコードを内部で行っています。
Doctrineで結果セットを読み取る方法はいくつかのクエリの例を見ればわかります。
算出されるカラム
カラムを算出するためにDBMSの関数を使うとき、これらは式に最初に関わるコンポーネント/モデルにハイドレイトされます。
下記の例において、クエリで最初に遭遇するコンポーネントなので、モデルはPhonenumberリレーションにハイドレイトされます。
$q = Doctrine_Query::create() ->select('u.*, COUNT(DISTINCT p.id) AS num_phonenumbers') ->from('User u') ->leftJoin('u.Phonenumbers p') ->groupBy('u.id'); $users = $q->fetchArray(); echo $users[0]['num_phonenumbers'];
ユーザーとユーザーが所属するグループを読み取る
$q = Doctrine_Query::create() ->from('User u') ->leftJoin('u.Groups g'); $users = $q->fetchArray(); foreach ($users[0]['Groups'] as $group) { echo $group['name']; }
1つのパラメーターの値を持つシンプルなWHERE
$q = Doctrine_Query::create() ->from('User u') ->where('u.username = ?', 'jwage'); $users = $q->fetchArray();
複数のパラメーターの値を持つ複数のWHERE
$q = Doctrine_Query::create() ->from('User u') ->where('u.is_active = ? AND u.is_online = ?', array(1, 1)); $users = $q->fetchArray(); // 既存のwhere部分に追加するためにオプションとしてandWhere()も使用できる $q = Doctrine_Query::create() ->from('User u') ->where('u.is_active = ?', 1) ->andWhere('u.is_online = ?', 1); $users = $q->fetchArray();
whereIn()コンビニエンスメソッドを使う
$q = Doctrine_Query::create() ->from('User u') ->whereIn('u.id', array(1, 2, 3)); $users = $q->fetchArray(); // 上記と同じ $q = Doctrine_Query::create() ->from('User u') ->where('u.id IN (1, 2, 3)'); $users = $q->fetchArray();
WHEREでDBMS関数を使う
$userEncryptedKey = 'a157a558ac00449c92294c7fab684ae0'; $q = Doctrine_Query::create() ->from('User u') ->where("MD5(CONCAT(u.username, 'secret_user_key')) = ?", $userEncryptedKey); $user = $q->fetchOne(); $q = Doctrine_Query::create() ->from('User u') ->where('LOWER(u.username) = LOWER(?)', 'jwage'); $user = $q->fetchOne();
集約関数を使用して結果セットを制限する
// 電話番号を複数持つユーザー $q = Doctrine_Query::create() ->select('u.*, COUNT(DISTINCT p.id) AS num_phonenumbers') ->from('User u') ->leftJoin('u.Phonenumbers p') ->having('num_phonenumbers > 1') ->groupBy('u.id'); $users = $q->fetchArray();
WITHを使用して最初の電話番号だけJOINする
$q = Doctrine_Query::create() ->from('User u') ->leftJoin('u.Phonenumbers p WITH p.primary_num = ?', true); $users = $q->fetchArray();
ONを使用してJOIN条件をオーバーライドする
$q = Doctrine_Query::create() ->from('User u') ->leftJoin('u.Phonenumbers p ON u.id = p.user_id AND p.primary_num = ?', true); $users = $q->fetchArray();
最適化用に特定のカラムを選択する
$q = Doctrine_Query::create() ->select('u.username, p.phone') ->from('User u') ->leftJoin('u.Phonenumbers p'); $users = $q->fetchArray();
すべてのカラムを選択するためにワイルドカードを使う
// すべてのUserカラムを選択するが電話番号のカラムのみ $q = Doctrine_Query::create() ->select('u.*, p.phone') ->from('User u') ->leftJoin('u.Phonenumbers p'); $users = $q->fetchArray();
シンプルなWHEREでDQLのdeleteを実行する
// user id = 5に対して電話番号を削除する $deleted = Doctrine_Query::create() ->delete() ->from('Phonenumber') ->andWhere('user_id = 5') ->execute();
カラム用にシンプルなDQLのupdateを実行する
// 有効にするためにuser id = 1をセットする Doctrine_Query::create() ->update('User u') ->set('u.is_active', '?', true) ->where('u.id = ?', 1) ->execute();
DBMS関数のDQLのupdateを実行する
// すべてのユーザー名を小文字にする Doctrine_Query::create() ->update('User u') ->set('u.username', 'LOWER(u.username)') ->execute();
レコードを検索するためにMySQLのLIKEを使う
$q = Doctrine_Query::create() ->from('User u') ->where('u.username LIKE ?', '%jwage%'); $users = $q->fetchArray();
レコードエントリのキーが割り当てるカラムの名前であるデータをハイドレイトするにはINDEXBYキーワードを使う
$q = Doctrine_Query::create() ->from('User u INDEXBY u.username'); $users = $q->fetchArray(); print_r($users['jwage']); // usernameがjwageであるユーザーを表示する
位置と名前つきパラメーターを使う
// 位置パラメーター $q = Doctrine_Query::create() ->from('User u') ->where('u.username = ?', array('Arnold')); $users = $q->fetchArray(); // 名前つきパラメーター $q = Doctrine_Query::create() ->from('User u') ->where('u.username = :username', array(':username' => 'Arnold')); $users = $q->fetchArray();
WHEREでサブクエリを使う
// 名前がGroup 2であるグループに存在しないユーザーを見つける $q = Doctrine_Query::create() ->from('User u') ->where('u.id NOT IN (SELECT u.id FROM User u2 INNER JOIN u2.Groups g WHERE g.name = ?)', 'Group 2'); $users = $q->fetchArray(); // 下記の2つの例のようにこれをサブクエリ無しで実現できる // これは上記と同じ $q = Doctrine_Query::create() ->from('User u') ->innerJoin('u.Groups g WITH g.name != ?', 'Group 2') $users = $q->fetchArray(); // もしくはこれ $q = Doctrine_Query::create() ->from('User u') ->leftJoin('u.Groups g') ->where('g.name != ?', 'Group 2'); $users = $q->fetchArray();
Doctrineでクエリを実行してデータを読み取る方法はいろいろあります。下記はクエリを実行できるすべての異なる方法の一覧です。
$q = Doctrine_Query::create() ->from('User u'); // 配列のハイドレーション $users = $q->fetchArray(); // ハイドレイトされた配列として結果を取得する $users = $q->execute(array(), Doctrine::HYDRATE_ARRAY); // 上記と同じ $users = $q->setHydrationMode(Doctrine::HYDRATE_ARRAY)->execute(); // これも同じ // ハイドレーション無し $users = $q->execute(array(), Doctrine::HYDRATE_NONE); // プレーンなPDOとハイドレーション無しでクエリを実行する $users = $q->setHydrationMode(Doctrine::HYDRATE_NONE)->execute(); // これは上記と同じ // 1つを取得する $user = $q->fetchOne(); // すべてを取得してコレクションから最初のものを得る $user = $q->execute()->getFirst();
Doctrineはシンプルで魔法の力を持つfinderメソッドを提供します。 このメソッドはバックグラウンドでDoctrine_Queryオブジェクトを自動的に作成します。 下記のものはこれらのメソッドを活用するいくつかの例です。
finderマジックメソッド
1つのフィールドの値によってレコードを見つけるためにfindBy*()とfindOneBy*()マジックメソッドを活用できます。
$user = Doctrine::getTable('User')->findOneByUsername('jwage'); $users = Doctrine::getTable('User')->findByIsActive(1);
識別子によるfind
Doctrine_Table::find()メソッドは主キーでレコードを見つけるためにあります。サロゲートもしくは複合主キーを持つ両方のモデルに対して機能します。
$user = Doctrine::getTable('User')->find(1); $userGroup = Doctrine::getTable('UserGroup')->find(array(1, 2));
DoctrineによってDQLのupdateとdeleteクエリを直接実行することでデータを変更する、もしくはオブジェクトを取得しプロパティを変更して保存することができます。 下記のものは両方の戦略の例です。
Doctrineはオブジェクトのプロパティを変更する方法を3つ提供し、sfDoctrinePluginは4番目の方法を実装します。 これらのアクセス方法のスタイルにはオブジェクト、配列、と関数とPropelがあります。
$user = new User(); $user->username = 'jwage'; // オブジェクト $user['username'] = 'jwage'; // 配列 $user->set('username', 'jwage'); // 関数 $user->setUsername('jwage'); // Propelスタイル $user->save();
class User extends BaseUser { public function setPassword($password) { return $this->_set('password', md5($password)); } public function getUsername() { return 'PREFIX_' . $this->_get('username'); } } $user = new User(); $user->username = 'jwage'; $user->password = 'changeme'; // setPassword()を起動する echo $user->username; // getUsername()を起動してPREFIX_jwageを返す
DoctrineでPHPオブジェクトを活用すればオブジェクトグラフでデータを操作することは簡単です。
User hasOne Profile
$user = new User(); $user->username = 'jwage'; $user->password = 'changeme'; $user->Profile->name = 'Jonathan H. Wage'; $user->Profile->about = 'His name is Jonathan'; $user->save();
User hasMany Phonenumber as Phonenumbers
$user = new User(); $user->username = 'jwage'; $user->password = 'changeme'; $user->Phonenumbers[]->phonenumber = '6155139185'; $user->Phonenumbers[]->phonenumber = '1234567890'; $phonenumber = $user->Phonenumbers[2]; $phonenumber->phonenumber = '0987654321';
BlogPost hasMany Tag as Tags
$blogPost = new BlogPost(); $blogPost->title = 'Test blog post'; $blogPost->body = 'This is the content of the test blog post'; $tag = Doctrine::getTable('Tag')->findOneByName('doctrine'); if ( ! $tag) { $tag = new Tag; $tag->name = 'doctrine'; } $blogPost->Tags[] = $tag;
上記のコードは醜いので、lib/model/doctrine/TagTable.class.phpに設置されているTagTable子クラスにそのログインを抽出します.
class TagTable extends Doctrine_Table { public function findOneByName($name) { $tag = $this->findOneBy('name', $name); if ( ! $tag) { $tag = new Tag(); $tag->name = $name; } return $tag; } }
最初の例は次のように簡略化できます。
$blogPost = new BlogPost(); $blogPost->title = 'Test blog post'; $blogPost->body = 'This is the content of the test blog post'; $tag = Doctrine::getTable('Tag')->findOneByName('doctrine'); $blogPost->Tags[] = $tag;
別の方法はlib/model/doctrine/Tag.class.phpに設置されている生成されたTagクラスのsetName()という名前の関数を作ることによるTagのnameミューテーターです。
class Tag extends BaseTag { public function setName($name) { $tag = Doctrine::getTable('Tag')->findOneByName($name); if ($tag) { $this->assignIdentifier($tag->identifier()); } else { $this->_set('name', $name); } } }
コードはよりシンプルになり、重複タグはデータベースに挿入されていないことが保証されます。
$blogPost = new BlogPost(); $blogPost->title = 'Test blog post'; $blogPost->body = 'This is the content of the test blog post'; $blogPost->Tags[]->name = 'doctrine';
データを削除する方法は2つあります。
最初にオブジェクトを取得してDoctrine_Record::delete()メソッドを呼び出すかDQLのdeleteクエリを単独で実行します。
$user = Doctrine::getTable('User')->find(1); $user->delete();
単独のDQL deleteクエリが発行されます。 使うクエリは1つだけなので上述の方法よりも効率的です。 上述の例はオブジェクトを読み取り削除しなければなりません。
$deleted = Doctrine_Query::create() ->delete() ->from('User u') ->where('u.id = ?', 1) ->execute();
Doctrineでデータを扱う方法の詳細はマニュアルをご覧ください。
If you find a typo or an error, please register and open a ticket.
If you need support or have a technical question, please post to the official user mailing-list.