• このエントリーをはてなブックマークに追加
  • このエントリーをはてなブックマークに追加

こんにちは。開発部の原田です。

PHPではデータベースのデータを操作するためのモジュールなどが用意されていますが、その他にも“Doctrineを使う”という選択肢があります。

Doctrineとは?

ORMライブラリの1つで、平たく言えば「SQLを書かずオブジェクト指向でデータベースの操作ができるもの」と思って頂ければと思います。

Doctrineではデータベースに保存するセッターメソッドや、逆にデータを取得するゲッターメソッドがありますが、「これとこれとこれの条件に合致したデータが欲しい!」となるとプログラム側での条件分岐や繰り返しが必要になってきます。

そういう場合は、データベースからデータを取得する時点で細かい指定ができるQuery Builderを使うことで楽に取得できるようになります。SQLのように「WHERE」で条件を指定したり、「ORDER BY」でソートしたり…ということが可能です。便利!

でもQuery Builderで調べるとまだまだ英語のページが多く出てきます。私も、何度Googleの翻訳に英文を投げたことか…。翻訳された文章と、記載されたソースを確認しながら使い方を覚えていきました。

前置きはこのくらいにして、今回はそのQuery Builderの使い方を簡単にですがご紹介したいと思います。

Query Builderでデータを取得しよう!

例えば、とあるショップの特定の会員の名前・メールアドレス・会員ランクを取得する場合、SQLでは下記のようになります。

SELECT
  name, mailaddress, rank
FROM
  member
WHERE
  shop_no=45 AND member_no=103;

これをQuery Builderで書いた場合はこうなります。
(色々と書き方はありますが、今回は条件の指定のところでExprクラスを使います。この辺りは好みかと)

$repo = $em->getRepository(‘¥Entities¥Member’); 
$expr = $em->createQueryBuilder()->expr();

$qb = $repo->createQueryBuilder(‘m’)
           ->select('m.name, m.mailaddress, m.rank')
           ->add('where', $expr->andX(
               $expr->eq('m.shop_no', ':shop_no'),
               $expr->eq('m.member_no', ':member_no')
             ))
           ->setParameters(array(
               'shop_no' => $shop_no,
               'member_no' => $member_no
             ))
           ->getQuery()
           ->getResult();

※$emはEntityManagerのインスタンス(クラスを元に作成したオブジェクトの実態)です。

1. テーブルの指定とExprクラスのインスタンス化

$repo = $em->getRepository(‘¥Entities¥Member’); 
$expr = $em->createQueryBuilder()->expr();

1行目がSQLで言う FROM の箇所のようなもので、取得したいテーブルを指定しています。ここのパスはこのテーブルを定義したYAMLファイルの1行目付近に書いたパスになります。

2行目は条件の指定で使うExprクラスのインスタンス化(インスタンスを生成すること)をしており、Exprクラスを使わない場合は必要ありません。

2. createQueryBuilder を呼んでくる

$qb = $repo->createQueryBuilder(‘m’)

(’m’)は、getRepository()で指定したテーブルのエイリアスになります。例だと「memberテーブル=m」という扱いになります。

ここのエイリアスは必ず1文字でないとダメということではなく、好きな名称を付けられます。

ただし、「as」や「select」といった既にSQLのコマンドとして存在するものはエイリアスとして設定できません。クエリの実行時にエラーが出てしまいます。

私がQuery Builderの使い方を調べていた時は大抵1文字で設定されていたので、それが一般的なのかな?と感じています。

3. 取得するカラムを指定

->select('m.name, m.mailaddress, m.rank')

「テーブルのエイリアス.カラム名」で指定します。

取得するデータのカラムを指定しない場合、この行はいりません。「->select()」なしだと、SQLでいう「SELECT * FROM 〜」になります。

4. 条件指定

->add('where', $expr->andX(
    $expr->eq('m.shop_no', ':shop_no'),
    $expr->eq('o.member_no', ':member_no')
))

where は、あのSQLでよく使う WHERE です。ここで最初の方でインスタンス化したExprクラスが出てきます。

「->andX()」はあの「WHERE ○○ AND ○○」のアンドで、このメソッドの中に入れることで条件をアンドで並べることができます。…はい、ご想像の通り「OR」は「orX()」です!使い方は andX() と同じです。

今回はイコールなので「->eq()」を使用します。「->eq(‘テーブルのエイリアス.カラム名’, ‘比較対象’)」という書き方です。条件を増やす場合は2行目のように末尾にコンマを付けて増やしていきます。

Exprクラスで定義されているメソッドは他にも多数ありますが、ここでは割愛させていただきます。その他のメソッドについては下記のリンク先をご覧頂ければと!

15. The QueryBuilder — Doctrine 2 ORM 2 documentation(※外部サイト)

5. パラメータをセットする

->setParameters(array(
    'shop_no' => $shop_no,
    'member_no' => $member_no
))

上の「4. 条件指定」で出てきた「:shop_no」ってなんぞや?と思われた方、いらっしゃると思います。私も初めて見た時はなんだこれとなりました。これは上記の箇所で設定したものです。

「->setParameters(配列)」で比較対象のパラメータ(値)をセットします。今回は複数値があるので setParameters() を使用していますが、値が1つだけの場合は下記を使用します。

->setParameter('shop_no', $shop_no)

setParameters()なら配列のキー、setParameter()なら1つ目の引数として渡したものと変数の値が紐付けられます。上記の例だと第一引数が「shop_no」なので、その値を使いたい時は「:shop_no」となります。

これ、ちょっと別の書き方もありまして、キーや第一引数のところを数字にすることもできます。

->setParameters(array(
    1 => $shop_no,
    2 => $member_no
))

上記のパターンの書き方にした場合「4. 条件指定」にあった「:shop_no」の箇所は「?1」となります。同じように、$member_noを使いたい時は「?2」となります。

6. 結果を取得する

->getQuery()
->getResult();

やっと最後まで来ました。「->getQuery()」でQuery オブジェクトが返ってきて、「->getResult()」で結果を取得します。

「->getResult()」を使うと結果をオブジェクトで取得します。でも今回の例のように取得するカラムを指定していると結果が配列で返ってくるので少々ややこしかったりします。

結果の取得には複数メソッドが用意されており、1つの結果のみ期待している場合は「->getSingleResult()」を使用します。

ただ、この getSingleResult() は結果がない場合や1つより多い結果が返ってくるとエラーになるので注意が必要です。その時の対処方法については、リンク先の「Caution」を参考にして頂ければと思います。

データベースと Doctrine (“The Model”) | Symfony2日本語ドキュメント(※外部サイト)

他にも「->getArrayResult() 」があり、これは関数名のとおり結果は配列で返ってきます。ここでご紹介したメソッド以外もありますが、こちらもここでは割愛させて頂きます。

終わりに

いかがでしたでしょうか?

SQLをご存知の方はQuery Builderの方が取得するデータをイメージしやすくていいかもしれません。

でもそもそものプログラムの処理内容や意図によっては、速度やメモリへの負荷の観点でゲッター・セッターメソッドの方が良かったり…ということもあるので、その時その時の使い分けが必要かなと個人的に思っています。

それでは、ここまでご覧くださりありがとうございました!