Doctrine Query Language (DQL)は複雑なオブジェクト読み取りを手助けするためのObject Query Languageです。リレーショナルデータを効率的に読み取るときに(例えばユーザーと電話番号を取得するとき)DQL(もしくは生のSQL)を使うことを常に考えるべきです。
この章ではDoctrine Query Languageの使い方の例をたくさん実演します。これらすべての例ではDefining Modelsの章で定義したスキーマを使うことを想定します。またテスト用に1つの追加モデルを定義します。
// models/Account.php
class Account extends Doctrine_Record
{
public function setTableDefinition()
{
$this->hasColumn('name', 'string', 255);
$this->hasColumn('amount', 'decimal');
}
}
YAMLフォーマットでの同じ例は次の通りです。YAML Schema Filesの章でYAMLの詳細を読むことができます:
---
# schema.yml
# ...
Account:
columns:
name: string(255)
amount: decimal
生のSQLを使う場合と比較すると、DQLは次の恩恵があります:
DQLの力が十分でなければ、オブジェクト投入に対してRawSql APIを使うことを考えるべきです。
既に次の構文に慣れている方もいらっしゃるでしょうx:
次のコードは決して使わないでください。 これはオブジェクト投入用に多くのSQLクエリを使います。
// test.php
// ...
$users = Doctrine_Core::getTable('User')->findAll();
foreach($users as $user) {
echo $user->username . " has phonenumbers: \n";
foreach($user->Phonenumbers as $phonenumber) {
echo $phonenumber->phonenumber . "\n";
}
}
上記と同じ内容ですがオブジェクト投入のために1つのSQLクエリのみを使うより効率的な実装です。
// test.php
// ...
$q = Doctrine_Query::create()
->from('User u')
->leftJoin('u.Phonenumbers p');
echo $q->getSqlQuery();
上記のクエリによって生成されるSQLを見てみましょう:
SELECT
u.id AS u__id,
u.is_active AS u__is_active,
u.is_super_admin AS u__is_super_admin,
u.first_name AS u__first_name,
u.last_name AS u__last_name,
u.username AS u__username,
u.password AS u__password,
u.type AS u__type,
u.created_at AS u__created_at,
u.updated_at AS u__updated_at,
p.id AS p__id,
p.user_id AS p__user_id,
p.phonenumber AS p__phonenumber
FROM user u
LEFT JOIN phonenumber p ON u.id = p.user_id
クエリを実行してデータで遊んでみましょう:
// test.php
// ...
$users = $q->execute();
foreach($users as $user) {
echo $user->username . " has phonenumbers: \n";
foreach($user->Phonenumbers as $phonenumber) {
echo $phonenumber->phonenumber . "\n";
}
}
DQLの文字列で二重引用符(")を使うのは非推奨です。これはMySQLの標準では使えますがDQLにおいて識別子と混同される可能性があります。代わりに値に対してプリペアードステートメントを使うことが推奨されます。これによって適切にエスケープされます。
SELECT文の構文:
SELECT
[ALL | DISTINCT]
<select_expr>,
...
[
FROM <components>
[
WHERE <where_condition>]
[
GROUP BY <groupby_expr>
[ASC | DESC],
... ]
[
HAVING <where_condition>]
[
ORDER BY <orderby_expr>
[ASC | DESC],
...]
[
LIMIT <row_count>
OFF
SET <offset>}]
SELECT文は1つもしくは複数のコンポーネントからデータを読み取るために使われます。
それぞれのselect_exprは読み取りたいカラムもしくは集約関数の値を示します。
すべてのSELECT文で少なくとも1つのselect_exprがなければなりません。
最初にサンプルのAccountレコードをinsertします:
// test.php
// ...
$account = new Account();
$account->name = 'test 1';
$account->amount = '100.00';
$account->save();
$account = new Account();
$account->name = 'test 2';
$account->amount = '200.00';
$account->save();
test.phpを実行します:
$ php test.php
次のサンプルクエリでデータのselectをテストできます:
// test.php
// ...
$q = Doctrine_Query::create()
->select('a.name')
->from('Account a');
echo $q->getSqlQuery();
上記のクエリによって生成されたSQLを見てみましょう:
SELECT
a.id AS a__id,
a.name AS a__name
FROM account a
// test.php
// ...
$accounts = $q->execute();
print_r($accounts->toArray());
上記の例では次の出力が生み出されます:
$ php test.php
Array
(
[0] => Array
(
[id] => 1
[name] => test 1
[amount] =>
)
[1] => Array
(
[id] => 2
[name] => test 2
[amount] =>
)
)
アスタリスクは任意のコンポーネントからすべてのカラムをselectするために使われます。アスタリスクを使うときでも実行されるSQLクエリは実際にはそれを使いません(Doctrineはアスタリスクを適切なカラムの名前に変換することで、データベースでのパフォーマンスの向上につながります)。
// test.php
// ...
$q = Doctrine_Query::create()
->select('a.*')
->from('Account a');
echo $q->getSqlQuery();
最後のクエリの例から生成されたSQLとすぐ前に生成されたクエリで生成されたSQLを比較します:
SELECT
a.id AS a__id,
a.name AS a__name,
a.amount AS a__amount
FROM account a
アスタリスクはAccountモデルに存在する実際のすべてのカラム名に置き換えられることに留意してください。
クエリを実行して結果を検査してみましょう:
// test.php
// ...
$accounts = $q->execute();
print_r($accounts->toArray());
上記の例は次の出力を生み出します:
$ php test.php
Array
(
[0] => Array
(
[id] => 1
[name] => test 1
[amount] => 100.00
)
[1] => Array
(
[id] => 2
[name] => test 2
[amount] => 200.00
)
)
FROM句はレコードから読み取るコンポーネントを示します。
// test.php
// ...
$q = Doctrine_Query::create()
->select('u.username, p.*')
->from('User u')
->leftJoin('u.Phonenumbers p')
echo $q->getSqlQuery();
getSql()への上記の呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id,
u.username AS u__username,
p.id AS p__id,
p.user_id AS p__user_id,
p.phonenumber AS p__phonenumber
FROM user u
LEFT JOIN phonenumber p ON u.id = p.user_id
WHERE句は、選択されるためにレコードが満たさなければならない条件を示します。where_conditionは選択されるそれぞれの列に対してtrueに表示する式です。WHERE句が存在しない場合ステートメントはすべての列を選択します。
// test.php
// ...
$q = Doctrine_Query::create()
->select('a.name')
->from('Account a')
->where('a.amount > 2000');
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
a.id AS a__id,
a.name AS a__name
FROM account a
WHERE a.amount > 2000
WHERE句において、集約(要約)関数を除いて、DQLがサポートする任意の関数と演算子を使うことができます。HAVING句は集約関数で結果を絞るために使うことができます:
// test.php
// ...
$q = Doctrine_Query::create()
->select('u.username')
->from('User u')
->leftJoin('u.Phonenumbers p')
->having('COUNT(p.id) > 3');
echo $q->getSqlQuery();
getSql()を呼び出すと次のSQLクエリが出力されます:
SELECT
u.id AS u__id,
u.username AS u__username
FROM user u
LEFT JOIN phonenumber p ON u.id = p.user_id
HAVING COUNT(p.id) > 3
ORDER BY句は結果のソートに使われます。
// test.php
// ...
$q = Doctrine_Query::create()
->select('u.username')
->from('User u')
->orderBy('u.username');
echo $q->getSqlQuery();
上記のgetSql()を呼び出すと次のSQLクエリが出力されます:
SELECT
u.id AS u__id,
u.username AS u__username
FROM user u
ORDER BY u.username
LIMITとOFFSET句はレコードの数をrow_countに効率的に制限するために使われます。
// test.php
// ...
$q = Doctrine_Query::create()
->select('u.username')
->from('User u')
->limit(20);
echo $q->getSqlQuery();
上記のgetSql()を呼び出すと次のSQLクエリが出力されます:
SELECT
u.id AS u__id,
u.username AS u__username
FROM user u
LIMIT 20
集約値用のSELECT構文:
// test.php
// ...
$q = Doctrine_Query::create()
->select('u.id, COUNT(t.id) AS num_threads')
->from('User u, u.Threads t')
->where('u.id = ?', 1)
->groupBy('u.id');
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id,
COUNT(f.id) AS f__0
FROM user u
LEFT JOIN forum__thread f ON u.id = f.user_id
WHERE u.id = ?
GROUP BY u.id
クエリを実行して結果をインスペクトします:
// test.php
// ...
$users = $q->execute();
次のコードでnum_threadsのデータに簡単にアクセスできます:
// test.php
// ...
echo $users->num_threads . ' threads found';
UPDATE文の構文:
UPDATE <component_name>
SET <col_name1> = <expr1> ,
<col_name2> = <expr2>
WHERE <where_condition>
ORDER BY <order_by>
LIMIT <record_count>
// test.php
// ...
$q = Doctrine_Query::create()
->update('Account')
->set('amount', 'amount + 200')
->where('id > 200');
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
UPDATE account
SET amount = amount + 200
WHERE id > 200
更新の実行はシンプルです。次のクエリを実行するだけです:
// test.php
// ...
$rows = $q->execute();
echo $rows;
DELETE
FROM <component_name>
WHERE <where_condition>
ORDER BY <order_by>
LIMIT <record_count>
// test.php
// ...
$q = Doctrine_Query::create()
->delete('Account a')
->where('a.id > 3');
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
DELETE
FROM account
WHERE id > 3
DELETEクエリの実行は次の通りです:
// test.php
// ...
$rows = $q->execute();
echo $rows;
DQLのUPDATEとDELETEクエリを実行すると影響を受けた列の数が返されます。
構文:
FROM <component_reference> [[LEFT | INNER] JOIN <component_reference>] ...
FROM句はレコードを読み取るコンポーネントを示します。複数のコンポーネントを名付けると、joinを実行することになります。指定されたそれぞれのテーブルに対して、オプションとしてエイリアスを指定できます。
次のDQLクエリを考えます:
// test.php
// ...
$q = Doctrine_Query::create()
->select('u.id')
->from('User u');
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id
FROM user u
Userはクラス(コンポーネント)の名前でuはエイリアスです。常に短いエイリアスを使うべきです。大抵の場合これらによってクエリははるかに短くなるのと例えばキャッシュを利用するときに短いエイリアスが使われていればクエリのキャッシュされたフォームの取るスペースが少なくなるからです。
DQL JOINの構文:
JOIN <component_reference1>] [ON | WITH] <join_condition1> [INDEXBY] <map_condition1>, [[LEFT | INNER] JOIN <component_reference2>] [ON | WITH] <join_condition2> [INDEXBY] <map_condition2>, ... [[LEFT | INNER] JOIN <component_referenceN>] [ON | WITH] <join_conditionN> [INDEXBY] <map_conditionN>
DQLはINNER JOINとLEFT JOINをサポートします。それぞれのjoinされたコンポーネントに対して、オプションとしてエイリアスを指定できます。
デフォルトのjoinの形式はLEFT JOINです。このjoinはLEFT JOIN句もしくはシンプルな','のどちらかを使うことで示せます。なので次のクエリは等しいです:
// test.php
// ...
$q = Doctrine_Query::create()
->select('u.id, p.id')
->from('User u')
->leftJoin('u.Phonenumbers p');
$q = Doctrine_Query::create()
->select('u.id, p.id')
->from('User u, u.Phonenumbers p');
echo $q->getSqlQuery();
推奨される形式は前者です。より冗長で読みやすく何が行われているのか理解しやすいからです。
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id,
p.id AS p__id
FROM user u
LEFT JOIN phonenumber p ON u.id = p.user_id
JOINの条件が自動的に追加されることに注意してください。DoctrineはUserとPhonenumberは関連していることを知っているのであなたに代わって追加できるからです。
INNER JOINは共通集合を生み出します(すなわち、最初のコンポーネントのありとあらゆるレコードが2番目のコンポーネントのありとあらゆるレコードにjoinされます)。ですので例えば電話番号を1つかそれ以上持つすべてのユーザーを効率的に取得したい場合、基本的にINNER JOINが使われます。
デフォルトではDQLは主キーのjoin条件を自動追加します:
// test.php
// ...
$q = Doctrine_Query::create()
->select('u.id, p.id')
->from('User u')
->leftJoin('u.Phonenumbers p');
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id,
p.id AS p__id
FROM User u
LEFT JOIN Phonenumbers p ON u.id = p.user_id
このビヘイビアをオーバーライドして独自のカスタムjoin条件を追加したい場合ONキーワードで実現できます。次のDQLクエリを考えてみましょう:
// test.php
// ...
$q = Doctrine_Query::create()
->select('u.id, p.id')
->from('User u')
->leftJoin('u.Phonenumbers p ON u.id = 2');
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id,
p.id AS p__id
FROM User u
LEFT JOIN Phonenumbers p ON u.id = 2
通常追加されるON条件が現れず代わりにユーザーが指定した条件が使われていることに注目してください。
大体の場合最初のjoin条件をオーバーライドする必要はありません。むしろカスタム条件を追加したいことがあります。これはWITHキーワードで実現できます。
// test.php
// ...
$q = Doctrine_Query::create()
->select('u.id, p.id')
->from('User u')
->leftJoin('u.Phonenumbers p WITH u.id = 2');
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id,
p.id AS p__id
FROM User u
LEFT JOIN Phonenumbers p ON u.id = p.user_id
AND u.id = 2
ON条件が完全には置き換えられていないことに注意してください。代わりに指定する条件が自動条件に追加されます。
Doctrine_Query APIはJOINを追加するための2つのコンビニエンスメソッドを提供します。これらはinnerJoin()とleftJoin()と呼ばれ、これらの使い方は次のようにとても直感的です:
// test.php
// ...
$q = Doctrine_Query::create()
->select('u.id')
->from('User u')
->leftJoin('u.Groups g')
->innerJoin('u.Phonenumbers p WITH u.id > 3')
->leftJoin('u.Email e');
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id
FROM user u
LEFT JOIN user_group u2 ON u.id = u2.user_id
LEFT JOIN groups g ON g.id = u2.group_id
INNER JOIN phonenumber p ON u.id = p.user_id
AND u.id > 3
LEFT JOIN email e ON u.id = e.user_id
INDEXBYキーワードはコレクション/配列のキーなどの特定のカラムをマッピングする方法を提供します。デフォルトではDoctrineは数値のインデックス付きの配列/コレクションに複数の要素のインデックスを作成します。マッピングはゼロから始まります。このビヘイビアをオーバーライドするには下記で示されるようにINDEXBYキーワードを使う必要があります:
// test.php
// ...
$q = Doctrine_Query::create()
->from('User u INDEXBY u.username');
$users = $q->execute();
INDEXBYキーワードは生成されるSQLを変えません。コレクションのそれぞれのレコードのキーとして指定されたカラムでデータをハイドレイトするためにDoctrine_Queryによって内部で使われます。
これで$usersコレクションのユーザーは自身の名前を通してアクセスできます:
// test.php
// ...
echo $user['jack daniels']->id;
INDEXBYキーワードは任意のJOINに適用できます。これは任意のコンポーネントがそれぞれ独自のインデックス作成のビヘイビアを持つことができることを意味します。次のコードにおいてUsersとGroupsの両方に対して異なるインデックス作成機能を使用しています。
// test.php
// ...
$q = Doctrine_Query::create()
->from('User u INDEXBY u.username')
->innerJoin('u.Groups g INDEXBY g.name');
$users = $q->execute();
drinkers clubの作成日を出力してみましょう。
// test.php
// ...
echo $users['jack daniels']->Groups['drinkers club']->createdAt;
構文:
WHERE <where_condition>
Doctrine_Queryオブジェクトを使用する複雑なwhere条件を構築するためにaddWhere()、andWhere()、orWhere()、whereIn()、andWhereIn()、orWhereIn()、whereNotIn(), andWhereNotIn()、orWhereNotIn()メソッドを使うことができます。
すべてのアクティブな登録ユーザーもしくは管理者を読み取る例は次の通りです:
// test.php
// ...
$q = Doctrine_Query::create()
->select('u.id')
->from('User u')
->where('u.type = ?', 'registered')
->andWhere('u.is_active = ?', 1)
->orWhere('u.is_super_admin = ?', 1);
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id
FROM user u
WHERE u.type = ?
AND u.is_active = ?
OR u.is_super_admin = ?
文字列
文字列リテラルはシングルクォートで囲まれます; 例: 'literal'。シングルクォートを含む文字列リテラルは2つのシングルクォートで表現されます; 例: 'literal''s'。
// test.php
// ...
$q = Doctrine_Query::create()
->select('u.id, u.username')
->from('User u')
->where('u.username = ?', 'Vincent');
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id,
u.username AS u__username
FROM user u
WHERE u.username = ?
where()メソッドにusernameの値をパラメータとして渡したので生成SQLに含まれません。クエリを実行するときにPDOが置き換え処理をします。Doctrine_Queryインスタンス上でパラメータをチェックするにはgetParams()メソッドを使うことができます。
整数
整数リテラルはPHPの整数リテラル構文の使用をサポートします。
// test.php
// ...
$q = Doctrine_Query::create()
->select('a.id')
->from('User u')
->where('u.id = 4');
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id
FROM user u
WHERE u.id = 4
浮動小数
浮動小数はPHPの浮動小数リテラルの構文の使用をサポートします。
// test.php
// ...
$q = Doctrine_Query::create()
->select('a.id')
->from('Account a')
->where('a.amount = 432.123');
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
a.id AS a__id
FROM account a
WHERE a.amount = 432.123
論理値
論理値リテラルはtrueとfalseです。
// test.php
// ...
$q = Doctrine_Query::create()
->select('a.id')
->from('User u')
->where('u.is_super_admin = true');
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id
FROM user u
WHERE u.is_super_admin = 1
列挙値
列挙値は文字リテラルと同じ方法で動作します。
// test.php
// ...
$q = Doctrine_Query::create()
->select('a.id')
->from('User u')
->where("u.type = 'admin'");
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id
FROM user u
WHERE u.type = 'admin'
予め定義され予約済みのリテラルは大文字と小文字を区別しますが、これらを大文字で書くのが良い標準です。
位置パラメータの使用の例は次の通りです:
単独の位置パラメータ:
// test.php
// ...
$q = Doctrine_Query::create()
->select('u.id')
->from('User u')
->where('u.username = ?', array('Arnold'));
echo $q->getSqlQuery();
位置パラメータ用に渡されたパラメータが1つの値しか格納しないとき1つの値を含む配列の代わりに単独のスカラー値を渡すことができます。
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id
FROM user u
WHERE u.username = ?
複数の位置パラメータ:
// test.php
// ...
$q = Doctrine_Query::create()
->from('User u')
->where('u.id > ? AND u.username LIKE ?', array(50, 'A%'));
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id
FROM user u
WHERE (u.id > ?
AND u.username LIKE ?)
名前付きパラメータの使い方の例は次の通りです:
単独の名前付きパラメータ:
// test.php
// ...
$q = Doctrine_Query::create()
->select('u.id')
->from('User u')
->where('u.username = :name', array(':name' => 'Arnold'));
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id
FROM user u
WHERE u.username = :name
LIKEステートメントを伴う名前付きパラメータ:
// test.php
// ...
$q = Doctrine_Query::create()
->select('u.id')
->from('User u')
->where('u.id > :id', array(':id' => 50))
->andWhere('u.username LIKE :name', array(':name' => 'A%'));
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id
FROM user u
WHERE u.id > :id
AND u.username LIKE :name
演算子の一覧は優先順位が低い順です。
| 演算子 | 説明 |
|---|---|
| . | ナビゲーション演算子 |
| 算術演算子: | |
| +, - | 単項式 |
| *, / | 乗法と除法 |
| +, - | 加法と減法 |
| =, >, >=, <, <=, <> (not equal), | 比較演算子 |
| [NOT] LIKE, [NOT] IN, IS [NOT] NULL, IS [NOT] EMPTY | |
| 論理演算子: | |
| NOT | |
| AND | |
| OR |
構文:
<operand> IN (<subquery>|<value list>)
サブクエリの結果からオペランドが見つかるもしくは指定しされたカンマで区切られた値リストにある場合INの条件式はtrueを返します。サブクエリの結果が空の場合INの式は常にfalseです。
値リストが使われているときそのリストには少なくとも1つの要素がなければなりません。
INに対してサブクエリを使う例は次の通りです:
// test.php
// ...
$q = Doctrine_Query::create()
->from('User u')
->where('u.id IN (SELECT u.id FROM User u INNER JOIN u.Groups g WHERE g.id = ?)', 1);
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id
FROM user u
WHERE u.id IN (SELECT
u2.id AS u2__id
FROM user u2
INNER JOIN user_group u3 ON u2.id = u3.user_id
INNER JOIN groups g ON g.id = u3.group_id
WHERE g.id = ?)
整数のリストを使うだけの例は次の通りです:
// test.php
// ...
$q = Doctrine_Query::create()
->select('u.id')
->from('User u')
->whereIn('u.id', array(1, 3, 4, 5));
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id
FROM user u
WHERE u.id IN (?,
?,
?,
?)
構文:
string_expression [NOT] LIKE pattern_value [ESCAPE escape_character]
string_expressionは文字列の値でなければなりません。pattern_valueは文字列リテラルもしくは文字列の値を持つ入力パラメータです。アンダースコア(_)は任意の単独の文字を表し、パーセント(%)の文字は文字のシーケンス(空のシーケンスを含む)を表し、そして他のすべての文字はそれら自身を表します。オプションのescape_characterは単独文字の文字列リテラルもしくは文字の値を持つ入力パラメータ(すなわちcharもしくはCharacter)でpattern_valueで特別な意味を持つアンダースコアとパーセントの文字をエスケープします。
例:
string_expressionもしくはpattern_valueの値はNULLもしくはunknownで、LIKE式の値はunknownです。escape_characterが指定されNULLである場合、LIKE式の値はunknownです。
'@gmail.com'で終わるEメールを持つユーザーを見つける:
// test.php
// ...
$q = Doctrine_Query::create()
->select('u.id')
->from('User u')
->leftJoin('u.Email e')
->where('e.address LIKE ?', '%@gmail.com');
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id
FROM user u
LEFT JOIN email e ON u.id = e.user_id
WHERE e.address LIKE ?
'A'で始まる名前を持つすべてのユーザーを見つける:
// test.php
// ...
$q = Doctrine_Query::create()
->select('u.id')
->from('User u')
->where('u.username LIKE ?', 'A%');
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id
FROM user u
WHERE u.username LIKE ?
構文:
<operand> [NOT ]EXISTS (<subquery>)
EXISTS演算子はサブクエリが1つもしくは複数の列を返す場合はTRUEを返しそうでなければFALSEを返します。
NOT EXISTS演算子はサブクエリが0を返す場合TRUEを返しそうでなければFALSEを返します。
次の例ではReaderLogモデルを追加する必要があります。
// models/ReaderLog.php
class ReaderLog extends Doctrine_Record
{
public function setTableDefinition()
{
$this->hasColumn('article_id', 'integer', null, array(
'primary' => true
)
);
$this->hasColumn('user_id', 'integer', null, array(
'primary' => true
)
);
}
}
YAMLフォーマットでの同じは次の通りです。YAML Schema Filesの章でYAMLの詳細を読むことができます:
---
# schema.yml
# ...
ReaderLog:
columns:
article_id:
type: integer
primary: true
user_id:
type: integer
primary: true
ReaderLogモデルを追加した後でgenerate.phpスクリプトを実行することをお忘れなく!
$ php generate.php
これでテストを実行できます!最初に、読者を持つすべての記事を見つけます:
// test.php
// ...
$q = Doctrine_Query::create()
->select('a.id')
->from('Article a')
->where('EXISTS (SELECT r.id FROM ReaderLog r WHERE r.article_id = a.id)');
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
a.id AS a__id
FROM article a
WHERE EXISTS (SELECT
r.id AS r__id
FROM reader_log r
WHERE r.article_id = a.id)
読者を持たないすべての記事を見つけます:
// test.php
// ...
$q = Doctrine_Query::create()
->select('a.id')
->from('Article a')
->where('NOT EXISTS (SELECT r.id FROM ReaderLog r WHERE r.article_id = a.id));
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
a.id AS a__id
FROM article a
WHERE NOT EXISTS (SELECT
r.id AS r__id
FROM reader_log r
WHERE r.article_id = a.id)
構文:
operand comparison_operator ANY (subquery)
operand comparison_operator SOME (subquery)
operand comparison_operator ALL (subquery)
サブクエリの結果のすべての値に対して比較演算子がtrueである場合もしくはサブクエリの結果が空の場合、ALLの条件式はtrueを返します。すべての条件式のALLは少なくとも1つの列に対して比較の結果がfalseである場合はfalseで、trueもしくはfalseのどちらでもない場合はunknownです。
$q = Doctrine_Query::create()
->from('C')
->where('C.col1 < ALL (FROM C2(col1))');
サブクエリの結果の値に対して比較演算子がtrueの場合条件式のANYはtrueを返します。サブクエリの結果が空の場合もしくはサブクエリの結果のすべての値に対して比較式がfalseの場合、ANY条件式はfalseで、trueでもfalseでもなければunknownです。
$q = Doctrine_Query::create()
->from('C')
->where('C.col1 > ANY (FROM C2(col1))');
SOMEキーワードはANY用のエイリアスです。
$q = Doctrine_Query::create()
->from('C')
->where('C.col1 > SOME (FROM C2(col1))');
ALLもしくはANY条件式で使うことができる比較演算子は=、<、<=、>、>=、<>です。サブクエリの結果は条件式で同じ型を持たなければなりません。
NOT INは<> ALL用のエイリアスです。これら2つのステートメントは等しいです:
FROM C
WHERE C.col1 <> ALL (
FROM C2(col1));
FROM C
WHERE C.col1 NOT IN (
FROM C2(col1));
$q = Doctrine_Query::create()
->from('C')
->where('C.col1 <> ALL (FROM C2(col1))');
$q = Doctrine_Query::create()
->from('C')
->where('C.col1 NOT IN (FROM C2(col1))');
サブクエリは通常のSELECTクエリが含むことができる任意のキーワードもしくは句を含むことができます。
サブクエリの利点です:
idが1であるグループに所属しないすべてのユーザーを見つける例は次の通りです:
// test.php
// ...
$q = Doctrine_Query::create()
->select('u.id')
->from('User u')
->where('u.id NOT IN (SELECT u2.id FROM User u2 INNER JOIN u2.Groups g WHERE g.id = ?)', 1);
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id
FROM user u
WHERE u.id NOT IN (SELECT
u2.id AS u2__id
FROM user u2
INNER JOIN user_group u3 ON u2.id = u3.user_id
INNER JOIN groups g ON g.id = u3.group_id
WHERE g.id = ?)
グループに所属していないすべてのユーザーを見つける例は次の通りです。
// test.php
// ...
$q = Doctrine_Query::create()
->select('u.id')
->from('User u')
->where('u.id NOT IN (SELECT u2.id FROM User u2 INNER JOIN u2.Groups g)');
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id
FROM user u
WHERE u.id NOT IN (SELECT
u2.id AS u2__id
FROM user u2
INNER JOIN user_group u3 ON u2.id = u3.user_id
INNER JOIN groups g ON g.id = u3.group_id)
CONCAT関数は引数を連結した文字列を返します。上記の例においてユーザーのfirst_nameとlast_nameを連結してnameという値にマッピングします。
// test.php
// ...
$q = Doctrine_Query::create()
->select('CONCAT(u.first_name, u.last_name) AS name')
->from('User u');
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id,
CONCAT(u.first_name,
u.last_name) AS u__0
FROM user u
これでクエリを実行してマッピングされた関数値を取得できます:
$users = $q->execute();
foreach($users as $user) {
// 'name'は$userのプロパティではなく、
// マッピングされた関数値である
echo $user->name;
}
SUBSTRING関数の2番目と3番目の引数は開始位置と返される部分文字列の長さを表します。これらの引数は整数です。文字列の最初の位置は1によって表されます。SUBSTRING関数は文字列を返します。
// test.php
// ...
$q = Doctrine_Query::create()
->select('u.username')
->from('User u')
->where("SUBSTRING(u.username, 0, 1) = 'z'");
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id,
u.username AS u__username
FROM user u
WHERE SUBSTRING(u.username
FROM 0 FOR 1) = 'z'
SQLは使用しているDBMSに対して適切なSUBSTRING構文で生成されることに注目してください!
TRIM関数は文字列から指定された文字をトリムします。トリムされる文字が指定されていない場合、スペース(もしくは空白)が想定されます。オプションのtrim_characterは単独文字の文字列リテラルもしくは文字の値を持つ入力パラメータです(すなわちcharもしくはCharacter)[30]。トリムの仕様が提供されていない場合、BOTHが想定されます。TRIM関数はトリムされた文字列を返します。
// test.php
// ...
$q = Doctrine_Query::create()
->select('u.username')
->from('User u')
->where('TRIM(u.username) = ?', 'Someone');
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id,
u.username AS u__username
FROM user u
WHERE TRIM(u.username) = ?
LOWERとUPPER関数はそれぞれ文字列を小文字と大文字に変換します。これらは文字列を返します。
// test.php
// ...
$q = Doctrine_Query::create();
->select('u.username')
->from('User u')
->where("LOWER(u.username) = 'jon wage'");
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id,
u.username AS u__username
FROM user u
WHERE LOWER(u.username) = 'someone'
LOCATE関数は文字列の範囲内で任意の文字列の位置を返します。検索は指定された位置で始められます。文字列が整数として見つかった位置で、これは最初の位置を返します。最初の引数は検索される文字列です; 2番目の引数は検索文字列です; 3番目のオプション引数は検索が始まる文字列の位置を表す整数です(デフォルトでは、検索文字列の始め)。文字列の最初の位置は1によって表現されます。文字列が見つからない場合、0が返されます。
LENGTH関数は文字の文字列の長さを整数として返します。
利用可能なDQLの算術関数です:
ABS(simple_arithmetic_expression)
SQRT(simple_arithmetic_expression)
MOD(simple_arithmetic_expression, simple_arithmetic_expression)
DoctrineではFROM、SELECTとWHERE文でDQLのサブクエリを使うことができます。下記のコードではDoctrineが提供する異なる型のすべてのサブクエリの例が見つかります。
指定されたグループに所属しないすべてのユーザーを見つける。
// test.php
// ...
$q = Doctrine_Query::create()
->select('u.id')
->from('User u')
->where('u.id NOT IN (SELECT u.id FROM User u INNER JOIN u.Groups g WHERE g.id = ?)', 1);
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id
FROM user u
WHERE u.id NOT IN (SELECT
u2.id AS u2__id
FROM user u2
INNER JOIN user_group u3 ON u2.id = u3.user_id
INNER JOIN groups g ON g.id = u3.group_id
WHERE g.id = ?)
サブクエリでユーザーの電話番号を読み取りユーザー情報の結果セットに格納します。
// test.php
// ...
$q = Doctrine_Query::create()
->select('u.id')
->addSelect('(SELECT p.phonenumber FROM Phonenumber p WHERE p.user_id = u.id LIMIT 1) as phonenumber')
->from('User u');
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id,
(SELECT
p.phonenumber AS p__phonenumber
FROM phonenumber p
WHERE p.user_id = u.id
LIMIT 1) AS u__0
FROM user u
DQLのGROUP BY構文:
GROUP BY groupby_item {,
groupby_item}*
DQL HAVINGの構文:
HAVING conditional_expression
GROUP BYとHAVING句は集約関数を扱うために使われます。次の集約関数がDQLで利用可能です: COUNT、MAX、MIN、AVG、SUM
アルファベット順で最初のユーザーを名前で選択する。
// test.php
// ...
$q = Doctrine_Query::create()
->select('MIN(a.amount)')
->from('Account a');
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
MIN(a.amount) AS a__0
FROM account a
すべてのアカウントの合計数を選択する。
// test.php
// ...
$q = Doctrine_Query::create()
->select('SUM(a.amount)')
->from('Account a');
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
SUM(a.amount) AS a__0
FROM account a
GROUP BY句を含まないステートメントで集約関数を使うと、すべての列でグルーピングすることになります。下記の例ではすべてのユーザーと彼らが持つ電話番号の合計数を取得します。
// test.php
// ...
$q = Doctrine_Query::create()
->select('u.username')
->addSelect('COUNT(p.id) as num_phonenumbers')
->from('User u')
->leftJoin('u.Phonenumbers p')
->groupBy('u.id');
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id,
u.username AS u__username,
COUNT(p.id) AS p__0
FROM user u
LEFT JOIN phonenumber p ON u.id = p.user_id
GROUP BY u.id
HAVING句は集約値を使用する結果を狭めるために使われます。次の例では少なくとも2つの電話番号を持つすべてのユーザーを取得します。
// test.php
// ...
$q = Doctrine_Query::create()
->select('u.username')
->addSelect('COUNT(p.id) as num_phonenumbers')
->from('User u')
->leftJoin('u.Phonenumbers p')
->groupBy('u.id')
->having('num_phonenumbers >= 2');
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id,
u.username AS u__username,
COUNT(p.id) AS p__0
FROM user u
LEFT JOIN phonenumber p ON u.id = p.user_id
GROUP BY u.id
HAVING p__0 >= 2
次のコードで電話番号の数にアクセスできます:
// test.php
// ...
$users = $q->execute();
foreach($users as $user) {
echo $user->name . ' has ' . $user->num_phonenumbers . ' phonenumbers';
}
レコードのコレクションはORDER BY句を使用してデータベースレベルで効率的にソートできます。
構文:
, ...]
例:
// test.php
// ...
$q = Doctrine_Query::create()
->select('u.username')
->from('User u')
->leftJoin('u.Phonenumbers p')
->orderBy('u.username, p.phonenumber');
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id,
u.username AS u__username
FROM user u
LEFT JOIN phonenumber p ON u.id = p.user_id
ORDER BY u.username,
p.phonenumber
逆順でソートするためにソートするORDER BY句のカラム名にDESC(降順)キーワードを追加できます。デフォルトは昇順です; これはASCキーワードを使用して明示的に指定できます。
// test.php
// ...
$q = Doctrine_Query::create()
->select('u.username')
->from('User u')
->leftJoin('u.Email e')
->orderBy('e.address DESC, u.id ASC');
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id,
u.username AS u__username
FROM user u
LEFT JOIN email e ON u.id = e.user_id
ORDER BY e.address DESC,
u.id ASC
次の例ではすべてのユーザーを取得しユーザーが持つ電話番号の数でユーザーをソートします。
// test.php
// ...
$q = Doctrine_Query::create()
->select('u.username, COUNT(p.id) count')
->from('User u')
->innerJoin('u.Phonenumbers p')
->orderby('count');
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id,
u.username AS u__username,
COUNT(p.id) AS p__0
FROM user u
INNER JOIN phonenumber p ON u.id = p.user_id
ORDER BY p__0
次の例ではランダムな投稿を取得するためにORDER BY句でランダム機能を使います。
// test.php
// ...
$q = Doctrine_Query::create()
->select('t.id, RANDOM() AS rand')
->from('Forum_Thread t')
->orderby('rand')
->limit(1);
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
f.id AS f__id,
RAND() AS f__0
FROM forum__thread f
ORDER BY f__0
LIMIT 1
おそらく最も複雑機能であるDQLパーサーはLIMIT句パーサーです。DQL LIMIT句パーサーはLIMITデータベースポータビリティを考慮するだけでなく複雑なクエリ分析とサブクエリを使用することで列の代わりにレコードの数を制限できる機能を持ちます。
最初の20ユーザーと関連する電話番号を読み取ります:
// test.php
// ...
$q = Doctrine_Query::create()
->select('u.username, p.phonenumber')
->from('User u')
->leftJoin('u.Phonenumbers p')
->limit(20);
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id,
u.username AS u__username,
p.id AS p__id,
p.phonenumber AS p__phonenumber
FROM user u
LEFT JOIN phonenumber p ON u.id = p.user_id
DQLのLIMIT句はサポートされるすべてのデータベース上でポータブルです。次の事実に対して特別な注意を払う必要があります:
limit-subquery-algorithmはDQLパーサーが内部で使用するアルゴリズムです。1対多/多対多のリレーショナルデータは同時に取得されているときに内部で使用されます。SQLの結果セットの列の代わりにレコードの数を制限するためにこの種の特別なアルゴリズムはLIMIT句に必要です。
このビヘイビアは設定システムを使用してオーバーライドできます(グローバル、接続もしくはテーブルレベル):
$table->setAttribute(Doctrine_Core::ATTR_QUERY_LIMIT, Doctrine_Core::LIMIT_ROWS);
$table->setAttribute(Doctrine_Core::ATTR_QUERY_LIMIT, Doctrine_Core::LIMIT_RECORDS); // リバート
次の例ではユーザーと電話番号がありこれらのリレーションは1対多です。最初の20ユーザーを取得しすべての関連する電話番号を取得することを考えてみましょう。
クエリの最後でシンプルなドライバ固有のLIMIT 20を追加すれば正しい結果が返されるとお考えの方がいらっしゃるかもしれません。これは間違っています。1から20までの任意のユーザーを20の電話番号を持つ最初のユーザーとして取得しレコードセットが20の列で構成されることがあるからです。
DQLはサブクエリと複雑だが効率的なサブクエリの解析でこの問題に打ち勝ちます。次の例では最初の20人のユーザーとそのすべての電話番号を効果的な1つのクエリで取得しようとしています。DQLパーサーがサブクエリでもカラム集約継承を使うほど賢くまたエイリアスの衝突を回避するサブクエリのテーブルに異なるテーブルを使うほど賢いことに注目してください。
// test.php
// ...
$q = Doctrine_Query::create()
->select('u.id, u.username, p.*')
->from('User u')
->leftJoin('u.Phonenumbers p')
->limit(20);
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id,
u.username AS u__username,
p.id AS p__id,
p.user_id AS p__user_id,
p.phonenumber AS p__phonenumber
FROM user u
LEFT JOIN phonenumber p ON u.id = p.user_id
次の例では最初の20人のユーザーとすべての電話番号かつ実際に電話番号を持つユーザーのみを1つの効率的なクエリで取得します。これはINNER JOINを使います。サブクエリでINNER JOINを使うほどDQLパーサーが賢いことに注目してください:
// test.php
// ...
$q = Doctrine_Query::create()
->select('u.id, u.username, p.*')
->from('User u')
->innerJoin('u.Phonenumbers p')
->limit(20);
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id,
u.username AS u__username,
p.id AS p__id,
p.user_id AS p__user_id,
p.phonenumber AS p__phonenumber
FROM user u
INNER JOIN phonenumber p ON u.id = p.user_id
変化する可能性があるモデルを扱うが、クエリを簡単に更新できるようにする必要があるとき、クエリを定義する簡単な方法を見つける必要があります。例えば1つのフィールドを変更して何も壊れていないことを確認するためにアプリケーションのすべてのクエリを追跡する必要がある状況を想像してください。
名前付きクエリはこの状況を解決する素晴らしく効率的な方法です。これによってDoctrine_Queriesを作成しこれらを書き直すこと無く再利用できるようになります。
名前付きクエリのサポートはDoctrine_Query_Registryのサポートの上で構築されます。Doctrine_Query_Registryはクエリを登録して名前をつけるためのクラスです。これはアプリケーションクエリの編成を手助けしこれに沿ってとても便利な機能を提供します。
レジストリオブジェクトのadd()メソッドを使用してこのクエリは追加されます。これは2つのパラメータ、クエリの名前と実際のDQLクエリを受け取ります。
// test.php
// ...
$r = Doctrine_Manager::getInstance()->getQueryRegistry();
$r->add('User/all', 'FROM User u');
$userTable = Doctrine_Core::getTable('User');
// すべてのユーザーを見つける
$users = $userTable->find('all');
このサポートを簡略化するために、Doctrine_TableはDoctrine_Query_Registryへのアクセサをサポートします。
trueとして定義されたgenerateTableClassesオプションでモデルをビルドするとき、それぞれのレコードクラスはDoctrine_Tableを継承する*Tableクラスも生成します。
それから、名前付きクエリを含めるためにconstruct()メソッドを実装できます:
class UserTable extends Doctrine_Table
{
public function construct()
{
// DQL文字列を使用して定義されたNamed Query
$this->addNamedQuery('get.by.id', 'SELECT u.username FROM User u WHERE u.id = ?');
// Doctrine_Queryオブジェクトを使用して定義された名前付きのクエリ
$this->addNamedQuery(
'get.by.similar.usernames', Doctrine_Query::create()
->select('u.id, u.username')
->from('User u')
->where('LOWER(u.username) LIKE LOWER(?)')
);
}
}
Doctrine_TableのサブクラスであるMyFooTableクラスにリーチするには、次のようにできます:
$userTable = Doctrine_Core::getTable('User');
名前付きクエリにアクセスするには(常にDoctrine_Queryインスタンスを返す):
$q = $userTable->createNamedQuery('get.by.id');
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id,
u.username AS u__username
FROM user u
WHERE u.id = ?
名前付きクエリを実行するには2つの方法があります。1つめの方法は通常のインスタンスとしてDoctrine_Queryを読み取り通常通りに実行します:
// test.php
// ...
$users = Doctrine_Core::getTable('User')
->createNamedQuery('get.by.similar.usernames')
->execute(array('%jon%wage%'));
次のようにも実行を簡略化できます:
// test.php
// ...
$users = Doctrine_Core::getTable('User')
->find('get.by.similar.usernames', array('%jon%wage%'));
find()メソッドはハイドレーションモード用の3番目の引数を受け取ります。
それで十分でなければ、DoctrineはDoctrine_Query_Registryを利用しオブジェクト間の名前付きクエリにクロスアクセスできるようにする名前空間クエリを使います。Articleレコードの*Tableクラスのインスタンスがあることを想定します。Userレコードの"get.by.id" 名前付きクエリを呼び出したいとします。名前付きクエリにアクセスするには、次のように行わなければなりません:
// test.php
// ...
$articleTable = Doctrine_Core::getTable('Article');
$users = $articleTable->find('User/get.by.id', array(1, 2, 3));
QL_statement ::= select_statement | update_statement | delete_statement
select_statement ::= select_clause from_clause [where_clause] [groupby_clause]
[having_clause] [orderby_clause]
update_statement ::= update_clause [where_clause]
delete_statement ::= delete_clause [where_clause]
from_clause ::=
FROM identification_variable_declaration
{, {identification_variable_declaration | collection_member_declaration}}*
identification_variable_declaration ::= range_variable_declaration { join | fetch_join }*
range_variable_declaration ::= abstract_schema_name [AS ] identification_variable
join ::= join_spec join_association_path_expression [AS ] identification_variable
fetch_join ::= join_specFETCH join_association_path_expression
association_path_expression ::=
collection_valued_path_expression | single_valued_association_path_expression
join_spec::= [LEFT [OUTER ] |INNER ]JOIN
join_association_path_expression ::= join_collection_valued_path_expression |
join_single_valued_association_path_expression
join_collection_valued_path_expression::=
identification_variable.collection_valued_association_field
join_single_valued_association_path_expression::=
identification_variable.single_valued_association_field
collection_member_declaration ::=
IN ( collection_valued_path_expression) [AS ] identification_variable
single_valued_path_expression ::=
state_field_path_expression | single_valued_association_path_expression
state_field_path_expression ::=
{identification_variable | single_valued_association_path_expression}.state_field
single_valued_association_path_expression ::=
identification_variable.{single_valued_association_field.}* single_valued_association_field
collection_valued_path_expression ::=
identification_variable.{single_valued_association_field.}*collection_valued_association_field
state_field ::= {embedded_class_state_field.}*simple_state_field
update_clause ::=UPDATE abstract_schema_name [[AS ] identification_variable]
SET update_item {, update_item}*
update_item ::= [identification_variable.]{state_field | single_valued_association_field} =
new_value
new_value ::=
simple_arithmetic_expression |
string_primary |
datetime_primary |
boolean_primary |
enum_primary
simple_entity_expression |
NULL
delete_clause ::=DELETE FROM abstract_schema_name [[AS ] identification_variable]
select_clause ::=SELECT [DISTINCT ] select_expression {, select_expression}*
select_expression ::=
single_valued_path_expression |
aggregate_expression |
identification_variable |
OBJECT( identification_variable) |
constructor_expression
constructor_expression ::=
NEW constructor_name( constructor_item {, constructor_item}*)
constructor_item ::= single_valued_path_expression | aggregate_expression
aggregate_expression ::=
{AVG |MAX |MIN |SUM }( [DISTINCT ] state_field_path_expression) |
COUNT ( [DISTINCT ] identification_variable | state_field_path_expression |
single_valued_association_path_expression)
where_clause ::=WHERE conditional_expression
groupby_clause ::=GROUP BY groupby_item {, groupby_item}*
groupby_item ::= single_valued_path_expression | identification_variable
having_clause ::=HAVING conditional_expression
orderby_clause ::=ORDER BY orderby_item {, orderby_item}*
orderby_item ::= state_field_path_expression [ASC |DESC ]
subquery ::= simple_select_clause subquery_from_clause [where_clause]
[groupby_clause] [having_clause]
subquery_from_clause ::=
FROM subselect_identification_variable_declaration
{, subselect_identification_variable_declaration}*
subselect_identification_variable_declaration ::=
identification_variable_declaration |
association_path_expression [AS ] identification_variable |
collection_member_declaration
simple_select_clause ::=SELECT [DISTINCT ] simple_select_expression
simple_select_expression::=
single_valued_path_expression |
aggregate_expression |
identification_variable
conditional_expression ::= conditional_term | conditional_expressionOR conditional_term
conditional_term ::= conditional_factor | conditional_termAND conditional_factor
conditional_factor ::= [NOT ] conditional_primary
conditional_primary ::= simple_cond_expression |( conditional_expression)
simple_cond_expression ::=
comparison_expression |
between_expression |
like_expression |
in_expression |
null_comparison_expression |
empty_collection_comparison_expression |
collection_member_expression |
exists_expression
between_expression ::=
arithmetic_expression [NOT ]BETWEEN
arithmetic_expressionAND arithmetic_expression |
string_expression [NOT ]BETWEEN string_expressionAND string_expression |
datetime_expression [NOT ]BETWEEN
datetime_expressionAND datetime_expression
in_expression ::=
state_field_path_expression [NOT ]IN ( in_item {, in_item}* | subquery)
in_item ::= literal | input_parameter
like_expression ::=
string_expression [NOT ]LIKE pattern_value [ESCAPE escape_character]
null_comparison_expression ::=
{single_valued_path_expression | input_parameter}IS [NOT ] NULL
empty_collection_comparison_expression ::=
collection_valued_path_expressionIS [NOT] EMPTY
collection_member_expression ::= entity_expression
[NOT ]MEMBER [OF ] collection_valued_path_expression
exists_expression::= [NOT ]EXISTS (subquery)
all_or_any_expression ::= {ALL |ANY |SOME } (subquery)
comparison_expression ::=
string_expression comparison_operator {string_expression | all_or_any_expression} |
boolean_expression {= |<> } {boolean_expression | all_or_any_expression} |
enum_expression {= |<> } {enum_expression | all_or_any_expression} |
datetime_expression comparison_operator
{datetime_expression | all_or_any_expression} |
entity_expression {= |<> } {entity_expression | all_or_any_expression} |
arithmetic_expression comparison_operator
{arithmetic_expression | all_or_any_expression}
comparison_operator ::== |> |>= |< |<= |<>
arithmetic_expression ::= simple_arithmetic_expression | (subquery)
simple_arithmetic_expression ::=
arithmetic_term | simple_arithmetic_expression {+ |- } arithmetic_term
arithmetic_term ::= arithmetic_factor | arithmetic_term {* |/ } arithmetic_factor
arithmetic_factor ::= [{+ |- }] arithmetic_primary
arithmetic_primary ::=
state_field_path_expression |
numeric_literal |
(simple_arithmetic_expression) |
input_parameter |
functions_returning_numerics |
aggregate_expression
string_expression ::= string_primary | (subquery)
string_primary ::=
state_field_path_expression |
string_literal |
input_parameter |
functions_returning_strings |
aggregate_expression
datetime_expression ::= datetime_primary | (subquery)
datetime_primary ::=
state_field_path_expression |
input_parameter |
functions_returning_datetime |
aggregate_expression
boolean_expression ::= boolean_primary | (subquery)
boolean_primary ::=
state_field_path_expression |
boolean_literal |
input_parameter |
enum_expression ::= enum_primary | (subquery)
enum_primary ::=
state_field_path_expression |
enum_literal |
input_parameter |
entity_expression ::=
single_valued_association_path_expression | simple_entity_expression
simple_entity_expression ::=
identification_variable |
input_parameter
functions_returning_numerics::=
LENGTH( string_primary) |
LOCATE( string_primary, string_primary[, simple_arithmetic_expression]) |
ABS( simple_arithmetic_expression) |
SQRT( simple_arithmetic_expression) |
MOD( simple_arithmetic_expression, simple_arithmetic_expression) |
SIZE( collection_valued_path_expression)
functions_returning_datetime ::=
CURRENT_DATE |
CURRENT_TIME |
CURRENT_TIMESTAMP
functions_returning_strings ::=
CONCAT( string_primary, string_primary) |
SUBSTRING( string_primary,
simple_arithmetic_expression, simple_arithmetic_expression)|
TRIM( [[trim_specification] [trim_character]FROM ] string_primary) |
LOWER( string_primary) |
UPPER( string_primary)
trim_specification ::=LEADING | TRAILING | BOTH
Doctrineはモデルに存在する任意のカラムでレコードを見つけることを可能にするDoctrineモデル用のマジックファインダー(magic finder)を提供します。ユーザーの名前でユーザーを見つけたり、グループの名前でグループを見つけるために役立ちます。通常これはDoctrine_Queryインスタンスを書き再利用できるようにこれをどこかに保存することが必要です。このようなシンプルな状況にはもはや必要ありません。
ファインダーメソッドの基本パターンは次の通りです: findBy%s($value)もしくはfindOneBy%s($value)です。%sはカラム名もしくはリレーションのエイリアスです。カラムの名前の場合探す値を提供しなければなりません。リレーションのエイリアスを指定する場合、見つけるリレーションクラスのインスタンスを渡すか、実際の主キーの値を渡すことができます。
最初に扱うUserTableインスタンスを読み取りましょう:
// test.php
// ...
$userTable = Doctrine_Core::getTable('User');
find()メソッドを利用して主キーでUserレコードを簡単に見つけられます:
// test.php
// ...
$user = $userTable->find(1);
ユーザー名で1人のユーザーを見つけたい場合は次のようにマジックファインダーを使うことができます:
// test.php
// ...
$user = $userTable->findOneByUsername('jonwage');
レコード間のリレーションを利用してレコードでユーザーを見つけることができます。Userは複数のPhonenumbersを持つのでfindBy**()メソッドにUserインスタンスを渡すことでこれらのPhonenumberを見つけることができます:
// test.php
// ...
$phonenumberTable = Doctrine::getTable('Phonenumber');
$phonenumbers = $phonenumberTable->findByUser($user);
マジックファインダーはもう少し複雑な検索を可能にします。複数のプロパティによってレコードを検索するためにメソッド名でAndとOrキーワードを使うことができます。
$user = $userTable->findOneByUsernameAndPassword('jonwage', md5('changeme'));
条件を混ぜることもできます。
$users = $userTable->findByIsAdminAndIsModeratorOrIsSuperAdmin(true, true, true);
Doctrine_Queryオブジェクトはクエリの問題をデバッグするための手助けになる機能を少々提供します:
ときにDoctrine_Queryオブジェクトに対して完全なSQL文字列を見たいことがあります:
// test.php
// ...
$q = Doctrine_Query::create()
->select('u.id')
->from('User u')
->orderBy('u.username');
echo $q->getSqlQuery();
getSql()への呼び出しは次のSQLクエリを出力します:
SELECT
u.id AS u__id
FROM user u
ORDER BY u.username
上記のDoctrine_Query::getSql()メソッドによって返されるSQLはトークンをパラメータに置き換えません。これはPDOのジョブでクエリを実行するとき置き換えが実行されるPDOにパラメータを渡します。Doctrine_Query::getParams()メソッドでパラメータの配列を読み取ることができます。
Doctrine_Queryインスタンス用のパラメータの配列を取得します:
// test.php
// ...
print_r($q->getParams());
Doctrine Query Languageはこれまでのところ最も高度で役に立つDoctrineの機能です。これによってRDBMSのリレーションからとても複雑なデータを簡単にかつ効率的に選択できます!
これでDoctrineの主要なコンポーネントの使い方を見たのでComponent Overviewの章に移りすべてを鳥の目で見ることにします。