スキーマファイルとデータフィクスチャ
クエリをテストするためにいくつかのスキーマとデータフィクスチャを最初に定義する必要があります。
スキーマファイル
User:
actAs: [Timestampable]
columns:
username:
type: string(255)
password:
type: string(255)
last_login:
type: timestamp
relations:
Friends:
class: User
refClass: UserFriend
local: user_id1
foreign: user_id2
Groups:
class: Group
refClass: UserGroup
foreignAlias: Users
Permissions:
class: Permission
refClass: UserPermission
foreignAlias: Users
Group:
tableName: groups
columns:
name: string(255)
relations:
Permissions:
class: Permission
refClass: GroupPermission
foreignAlias: Groups
Permission:
columns:
name: string(255)
Phonenumber:
columns:
user_id: integer
phonenumber: string(55)
relations:
User:
foreignAlias: Phonenumbers
onDelete: CASCADE
Profile:
columns:
user_id: integer
first_name: string(255)
last_name: string(255)
email_address: string(255)
relations:
User:
foreignType: one
onDelete: CASCADE
UserFriend:
columns:
user_id1:
type: integer
primary: true
user_id2:
type: integer
primary: true
relations:
User1:
class: User
local: user_id1
foreignAlias: UserFriends
onDelete: CASCADE
User2:
class: User
local: user_id2
foreignAlias: UserFriends
onDelete: CASCADE
UserGroup:
columns:
user_id:
type: integer
primary: true
group_id:
type: integer
primary: true
relations:
User:
foreignAlias: UserGroups
onDelete: CASCADE
Group:
foreignAlias: UserGroups
onDelete: CASCADE
UserPermission:
columns:
user_id:
type: integer
primary: true
permission_id:
type: integer
primary: true
relations:
User:
foreignAlias: UserPermissions
onDelete: CASCADE
Permission:
foreignAlias: UserPermissions
onDelete: CASCADE
GroupPermission:
columns:
group_id:
type: integer
primary: true
permission_id:
type: integer
primary: true
relations:
Group:
foreignAlias: GroupPermissions
onDelete: CASCADE
Permission:
foreignAlias: GroupPermissions
onDelete: CASCADE
BlogPost:
actAs:
Timestampable:
Sluggable:
fields: [title]
columns:
user_id: integer
title: string(255)
body: clob
relations:
Author:
class: User
foreignAlias: BlogPosts
onDelete: CASCADE
Tags:
class: Tag
refClass: BlogPostTag
foreignAlias: BlogPosts
Comments:
class: Comment
refClass: BlogPostComment
foreignAlias: BlogPosts
Tag:
columns:
name: string(255)
Comment:
columns:
title: string(255)
body: clob
Page:
actAs:
Timestampable:
Sluggable:
fields: [title]
columns:
title: string(255)
body: clob
BlogPostTag:
columns:
blog_post_id:
type: integer
primary: true
tag_id:
type: integer
primary: true
relations:
BlogPost:
foreignAlias: BlogPostTags
onDelete: CASCADE
Tag:
foreignAlias: BlogPostTags
onDelete: CASCADE
BlogPostComment:
columns:
blog_post_id:
type: integer
primary: true
comment_id:
type: integer
primary: true
relations:
BlogPost:
foreignAlias: BlogPostComments
onDelete: CASCADE
Comment:
foreignAlias: BlogPostComments
onDelete: CASCADE
データフィクスチャ
User:
jwage:
username: jwage
password: changeme
Profile:
first_name: Jonathan
last_name: Wage
email_address: jonwage@gmail.com
Groups: [Administrator]
Friends: [fabpot, joeblow]
Phonenumbers:
Phonenumber_1:
phonenumber: 6155139185
fabpot:
username: fabpot
password: changeme
Profile:
first_name: Fabien
last_name: Potencier
email_address: fabien.potencier@symfony-project.com
Groups: [ContentEditor]
Friends: [jwage]
joeblow:
username: joeblow
password: changeme
Profile:
first_name: Joe
last_name: Blow
email_address: jowblow@gmail.com
Groups: [Registered]
Friends: [jwage, fabpot]
Group:
Administrator:
name: Administrator
Permissions: [EditPages, EditBlog, EditUsers, EditPages, Frontend]
Blogger:
name: Blogger
Permissions: [EditBlog, Frontend]
Moderator:
name: Moderator
Permissions: [EditUsers, EditComments, Frontend]
ContentEditor:
name: Content Editor
Permissions: [EditPages, EditBlog, Frontend]
Registered:
name: Registered
Permissions: [Frontend]
Permission:
EditPages:
name: Edit Pages
EditBlog:
name: Edit Blog
EditUsers:
name: Edit Users
EditPages:
name: Edit Pages
EditComments:
name: Edit Comments
Frontend:
name: Frontend
BlogPost:
BlogPost_1:
Author: jwage
title: Sample Blog Post
body: This is a sample blog post
Tags: [symfony, doctrine, php, mvc]
Comments:
Comment_1:
title: This is a bad blog post
body: Yes this is indeed a horrible blog post
Comment_2:
title: I think this is awesome
body: This is an awesome blog post, what are you talking about?!?!?!
Tag:
symfony:
name: symfony
php:
name: PHP
doctrine:
name: Doctrine
mvc:
name: MVC
Page:
home:
title: Home
body: This is the content of the home page
about:
title: About
body: This is the content of the about page
faq:
title: F.A.Q.
body: This is the content of the frequently asked questions page
SELECTクエリ
DBMS関数
最初にクエリでDBMS関数を使う方法を示します。 たとえばblogのすべての投稿を読み取る際にそれぞれのblogの投稿のコメント数をカウントしたい場合は次のように書きます。
$q = Doctrine_Query::create() ->select('p.*, COUNT(c.id) as num_comments') ->from('BlogPost p') ->leftJoin('p.Comments c') ->groupBy('p.id'); $results = $q->execute(); echo $results[0]['num_comments'];
任意の組み合わせの関数を使うことが可能で望む深さだけ入れ子にすることができます。
複数のJoin
Doctrineで複数のテーブルからデータを読み取るのは簡単です。 この例ではユーザーに割り当てられたグループも含めてすべてのパーミッションを読み取ることができます。
$q = Doctrine_Query::create() ->from('User u') ->leftJoin('u.Permissions p') ->leftJoin('u.Groups g') ->leftJoin('g.Permissions p2') ->where('u.id = ?', 1); $user = $q->fetchOne();
ユーザーが持つPermissionのDoctrine_Collectionをビルドできます。
$permissions = new Doctrine_Collection('Permission'); foreach ($user['Groups'] as $group) { foreach ($group['Permissions'] as $permission) { $permissions[] = $permission; } } foreach ($user['Permissions'] as $permission) { $permissions[] = $permission; }
blogのアプリケーションにおいて、関連したAuthor、CommentsとTagsを持つBlogPostを1つのクエリで読み取ることは共通のニーズです。
Doctrineでは次のように簡単にできます。
$q = Doctrine_Query::create() ->from('BlogPost p') ->leftJoin('p.Author a') ->leftJoin('p.Comments c') ->leftJoin('p.Tags t') ->where('p.id = ?', 1);
サブクエリ
どのPermissionレコードを検索するのかを知っているサブクエリを利用して同じDoctrine_CollectionのPermissionを直接検索することができます。
$userId = 1; $q = Doctrine_Query::create() ->from('Permission p'); $q2 = $q->createSubquery() ->select('p2.permission_id') ->from('UserPermission p2') ->where('p2.user_id = ?'); $q3 = $q->createSubquery() ->select('p3.id') ->from('Permission p3') ->leftJoin('p3.GroupPermissions gp') ->leftJoin('gp.Group g') ->leftJoin('g.Users u') ->where('u.id = ?'); $q->where('p.id IN (' . $q2->getDql() . ')') ->orWhere('p.id IN (' . $q3->getDql() . ')'); $permissions = $q->execute(array($userId, $userId));
LEFT JOINの省略記法
Doctrineのとても便利な機能の1つは省略記法でJOINを指定できることです。
これによってクエリが占めるコードの行数を大いに減らすことができます。
JOINを指定するためにfrom()の部分でモデルを変更することができます。
leftJoin()を利用して同じこともできます。
$q = Doctrine_Query::create() ->from('User u, u.Profile p, u.Groups g');
上記のコードは下記のコードと等しいです:
$q = Doctrine_Query::create() ->from('User u') ->leftJoin('u.Profile p') ->leftJoin('u.Groups g');
DELETEとUPDATEクエリ
Doctrine_Queryはupdate()もしくはdelete()メソッドを利用してUPDATEとDELETEクエリを指定するために使います。用例は次の通りです。
DELETEクエリ
この用例ではユーザーの名前でユーザーを削除します。
Doctrine_Query::create() ->delete() ->from('User u') ->where('u.username = ?', 'jwage') ->execute();
UPDATEクエリ
この用例では、ユーザーのパスワードを更新します。
Doctrine_Query::create() ->update('User u') ->set('u.password', '?', 'newpassword') ->where('u.username = ?', 'jwage') ->execute();
set()メソッドは3つの引数を受け取ります。
1番目は設定したフィールドの名前で、2番目はPDOにそのまま渡される部分で3番目はパラメーター/値です。
次の用例ではDBMS関数でタイムスタンプフィールドを設定します。
PDOにNOW()をそのまま渡したいので3番目の引数は使いません。
Doctrine_Query::create() ->update('User u') ->set('u.last_login', 'NOW()') ->where('u.username = ?', 'jwage') ->execute();
DQLのupdateとdeleteを使う利点は望むことを実現するために1つのクエリだけしか必要としないことです。 オブジェクトを使う場合は最初にオブジェクトの読み取りを行わなければならず、更新もしくは削除は2つの個別のクエリを意味します。
手書きのDQL
SQLマニアのあなたを忘れていませんよ。
オプションとしてDQLクエリを手書きして実行するためにDoctrine_Queryインスタンスで解析できます。
$dql = "FROM User u, u.Phonenumbers p"; $q = Doctrine_Query::create()->parseQuery($dql);
もしくはDoctrine_Queryのquery()メソッドを使ってこれらを実行することもできます。
$dql = "FROM User u, u.Phonenumbers p"; $q = Doctrine_Query::create()->query($dql);
クエリを実行する
上記のすべての用例ではクエリの作り方が示されましたが、これらを実行するにはどうしたらよいでしょうか? Doctrineはクエリを実行する方法とデータをハイドレートする方法をそれぞれ少しずつ提供します。 Doctrineはデータをオブジェクト、PHP配列としてハイドレートします。 これらはオブジェクトを利用するよりもはるかに速いです。もしくはハイドレーションプロセスを一度に飛ばすこともできます。
配列のハイドレーション
配列のハイドレーションを実行する例は次のとおりです。
$results = $q->execute($params, Doctrine::HYDRATE_ARRAY);
fetchArray()と呼ばれるコンビニエンスメソッドが存在します。
$results = $q->fetchArray($params);
レコードのハイドレーション
$results = $q->execute($params, Doctrine::HYDRATE_RECORD);
レコードのハイドレーションはデフォルトなので2番目の引数は省略できます。
ハイドレーションを使わない
ハイドレーションプロセスを完全にスキップしてPDOによる結果を返すことができます。 データの列とカラムがそれぞれ1つづつしかないような場合のみに役立ちます。 データは数値のキーを持つ配列として返されるので他の事例ではあまり便利ではありません。
$results = $q->execute($params, Doctrine::HYDRATE_NONE);
1つのレコードを取得する
制限を自動的に追加して複数ではなく単独の結果を返すためにはfetchOne()コンビニエンスメソッドを利用できます。
$result = $q->fetchOne($params, Doctrine::HYDRATE_ARRAY);
This work is licensed under the Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 Unported License license.