まずDoctrineの検索条件から。
Doctrine::getTable('JobeetJob')->createQuery('a')->execute();
とするとjobeet_jobテーブルの内容が全て取得されます。
getTableでテーブル名を指定し、createQueryでエイリアス名を指定します。
このエイリアス名は作成されるSQLにも出力を扱う際にも全く関係が無く、Doctrine内でカラムやテーブルを扱う際にのみ利用されます。
最後にexecuteで作成したクエリを実行し、取得します。
今回はWHERE句やLIMIT句等を使用していないため、jobeet_jobテーブルの内容が全件そのまま取得されます。
それでは今からクエリを書き換えていきますが、チュートリアルは丸ごと全部書き換えてしまっているのでいきなりハードルが高くてわかりにくいです。
とりあえずさっきのクエリにひとつ条件を書き足してみます。
Doctrine::getTable('JobeetJob')->createQuery('a')->where('a.id = ?',1)->execute();
whereは文字通りWHERE句を指定します。
第一引数に比較する内容、第二引数に具体的な値を記述します。
上記の場合'?'の部分が第二引数の1に相当します。
where('JobeetJob.id = ?',1)と書くとおかしな結果になります。
createQuery('a')で、以後JobeetJobを'a'として扱うと決めたので、その後最初のjobeet_jobテーブルを扱う際は'a'と書く必要があるのです。
上記の結果として、jobeet_job.id=1のカラムだけが取得されることになります。
今回は1ですが、ここにユーザ入力値を突っ込むことで自動的にSQLインジェクション対策になります。
固定値の場合であればプリペアドステートメントにする必要もないので、where('a.id = 1')と書くこともできます。
上記をチュートリアル形式に書き換えてみます。
$q = Doctrine_Query::create()->from('JobeetJob a')->where('a.id = ?',1);
$this->jobeet_job_list = $q->execute();
最初に呼び出すクラスがDoctrineクラスからDoctrine_Queryに変わっていますが、実行できることはあまり変わりません。
というか違いがよくわからん。
Doctrine_Query::fromではDoctrine::getTableとcreateQueryが行っていたテーブル名とエイリアス名の指定を一気に行います。
以後はwhereやexecuteを全く同じように書くことができます。
当然ですが$qを介さず、
$this->jobeet_job_list =Doctrine_Query::create()->from('JobeetJob j')->where('j.id = ?',1)->execute();
と一行で書くこともできます。
結局Doctrine_Query::create()とDoctrine::getTable()って何が違うんだ。
ようやくチュートリアルに追いついた。
30日以内に投稿された求人だけ拾ってみましょう。
apps/frontend/modules/job/actions/actions.class.php
1
2
3
4
|
$q = Doctrine_Query::create()
->from('JobeetJob j')
->where('j.created_at > ?', date('Y-m-d H:i:s', time() - 86400 * 30));
$this->jobeet_job_list = $q->execute();
|
単にwhereメソッドが変わっただけですね。
idのときは=だけでしたが、不等号や関数を記述することもできます。
しかし、30日以内とかなら
$q->where('j.created_at > NOW()-INTERVAL 30 DAY ');
のほうが早い気が。
実際に実行されたSQLはブラウザ右上のドラム缶みたいなマークで確認することができますので、見てみるとよいでしょう。
次にモデルを変更してみます。
前slugifyとかを書いたJobeetJob.class.phpです。
lib/model/doctrine/JobeetJob.class.php
1
2
3
4
5
6
7
|
public function save(Doctrine_Connection $conn = null){
if ($this->isNew() && !$this->getExpiresAt()){
$now = $this->getCreatedAt() ? strtotime($this->getCreatedAt()) : time();
$this->setExpiresAt(date('Y-m-d H:i:s', $now + 86400 * 30));
}
return parent::save($conn);
}
|
モデルクラスのsaveメソッドをオーバーライドすればセーブ時の動作を変化させることができるそうです。
そういった流れというか挙動は一体何処を調べればわかるのでしょうか?
あと、よくわからんのだが、シリアライズってこんなところで使う言葉なのか?
$this->isNew()は一度も保存されていないときにtrueを返すと思われます。
Doctrineの各モデルクラスはsfDoctrineRecordを継承しており、sfDoctrineRecord::isNew()に実体があります。
>(boolean) isNew ()
>Function require by symfony >= 1.2 admin generators
どういう意味だ。
こういうのの存在ってチュートリアルが無ければ気付きようが無い気がするんだがどうしてるんだろう?
全体としては、現在のオブジェクトが、一度もセーブしたことが無く、expires_atが定義されていない場合、expires_atを指定して親のsaveメソッドを呼ぶということになります。
2回目以降のセーブ、もしくは既にexpires_atが入っている場合はそのまま親のsaveメソッドが呼ばれます。
せっかくexpires_atを登録できるようにしたので、呼び出す場合もexpires_atを利用するようにしましょう。
apps/frontend/modules/job/actions/actions.class.php
1
2
3
4
|
$q = Doctrine_Query::create()
->from('JobeetJob j')
->where('j.expires_at > ?', date('Y-m-d H:i:s', time()));
$this->jobeet_job_list = $q->execute();
|
expires_atが現在より先のものだけ拾ってくるようになります。
これもやっぱり
$q->where('j.expires_at > NOW()');
のほうが早いんですがね。
このDQL変更によって、expires_atがNULLであるレコードは拾えなくなってしまいます。
新たなデータを例によってフィクスチャを利用して登録してみます。
しかし正直、どうもこの機能が便利とはとても思えないんですが。
データベースをいじって最初に投入したデータを変更していた場合、このチュートリアル通りにするとそこらへんの変更が全部消え去ってしまいます。
data/fixtures/jobs.ymlに、該当のYAMLのexpired_job:を追加します。
そしてコマンドを実行。
>php symfony doctrine:data-load
今回追加するこのexpired_job:にはexpires_atが指定されていないため、本来ならconfig/doctrine/schema.ymlの
JobeetJob:expires_at: { type: timestamp, notnull: true }
条件に引っかかってしまいます。
ところが上でJobeetJob.class.phpでsaveメソッドをオーバーライドしているため、expires_atに何も入れない場合、自動的にexpires_atを設定した上でセーブしてくれます。
expired_job.expires_atは、JobeetJob::save()メソッドに従いcreated_atの30日後に設定された上で保存されていました。
ついでに元あったデータは、直接DBをいじくって変更していたものがあったりした場合でも全削除されます。
このコマンドが効くのは開発用データベースだけではあるのですが、それでもテスト用データがたくさん詰まった状態なんかでうっかりコマンドを打ってしまうと大惨事。
現在のデータベースの内容をYAMLに書き出すコマンドは無いのか?
カスタムコンフィギュレーションは単にapp.ymlに設定を分離するというだけの話です。
apps/frontend/config/app.ymlにチュートリアルをコピペ。
この後コントローラからでもモデルからでもビューからでも、
sfConfig::get('app_active_days')
でapp.ymlに書かれている値を引っ張ってくることができるようになります。
JobeetJob::saveメソッドの該当部分を以下のように書き換えます。
$this->setExpiresAt(date('Y-m-d H:i:s', $now + 86400 * sfConfig::get('app_active_days')));
やっぱり掲載から60日は表示されるようにしたい、といったときに直接ロジックを書き換えることなく、設定ファイルを書き換えるだけで済むようになります。
最後にリファクタリング。
これまでDoctrine_Query::where('j.expires_at > NOW()')とかのロジックをコントローラに書いていました。
expires_atみたいなデータベースに直接関連する内容はコントローラに書かずに、全てモデルに押しやってしまいます。
コントローラを書き換えます。
apps/frontend/modules/job/actions/actions.class.php
1
2
3
|
public function executeIndex(sfWebRequest $request){
$this->jobeet_job_list = Doctrine::getTable('JobeetJob')->getActiveJobs();
}
|
JobeetJobTable::getActiveJobs()を書いたからには作らないといけません。
lib/model/doctrine/JobeetJobTable.class.php
1
2
3
4
5
|
public function getActiveJobs(){
$q = $this->createQuery('j')
->where('j.expires_at > ?', date('Y-m-d H:i:s', time()));
return $q->execute();
}
|
まあ見ての通り、これまでコントローラに書いてあった処理をJobeetJobTableモデルに持っていっただけです。
$thisになっていますが、別に
return Doctrine_Query::create()->from('JobeetJob j')
->where('j.expires_at > ?', date('Y-m-d H:i:s', time()))->execute();
とか書いても動きます。
コントローラでDoctrine::getTable('JobeetJob')を実行した場合、内部ではJobeetJobTableクラスが呼ばれています。
これまでのようにコントローラに検索ロジックを書いていた場合、複数の箇所で使用したくなったらその都度同じ処理を書かねばなりませんでした。
JobeetJobTableクラスにメソッドを記述することで、全てのプロジェクトからこのメソッドを使用できるようになります。
Symfonyの記事一覧