Jobeetをやってみる 6日目
DoctrineとModelの使い方を学ぶ
Doctrineクエリオブジェト
Doctrine::create()でインスタンスを作成する
まずjobのリストを30日以上経過したら表示しないように変更する
<?php // apps/frontend/modules/job/actions/actions.class.php public function executeIndex(sfWebRequest $request) { $q = Doctrine_Query::create() ->from('JobeetJob j') ->where('j.created_at > ?', date('Y-m-d H:i:s', strtotime('-30 days'))); $this->jobeet_job_list = $q->execute(); }
Doctrineクエリオブジェクトで生成されたSQLのデバッグ
実行されたクエリは log/frontend_dev.log にロギングされる
Doctrineのログは「sfDoctrineLogger」のタグになる
Feb 25 18:45:53 symfony [info] {sfDoctrineLogger} executeQuery : SELECT j.id AS j__id, j.category_id AS j__category_id, j.type AS j__type, j.company AS j__company, j.logo AS j__logo, j.url AS j__url, j.position AS j__position, j.location AS j__location, j.description AS j__description, j.how_to_apply AS j__how_to_apply, j.token AS j__token, j.is_public AS j__is_public, j.is_activated AS j__is_activated, j.email AS j__email, j.expires_at AS j__expires_at, j.created_at AS j__created_at, j.updated_at AS j__updated_at FROM jobeet_job j WHERE j.created_at > ? - (2009-01-25 06:45:53 )
見やすいように適当に改行してるけど、実際は1行で書いてある。
オブジェクトのSerialization
このままだと作成してからの時間でjobが消えてしまうので、有効期限を設定できるようにする。
<?php // lib/model/doctrine/JobeetJob.class.php class JobeetJob extends BaseJobeetJob { 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', strtotime('-30 days', $now))); return parent::save($conn); } }
そしてリスト表示もexpires_atを見るように変更する
<?php public function executeIndex(sfWebRequest $request) { $q = Doctrine_Query::create() ->from('JobeetJob j') ->where('j.expires_at > ?', date('Y-m-d H:i:s')); $this->jobeet_job_list = $q->execute(); }
有効期限が過ぎたデータを作る
# data/fixtures/jobs.yml JobeetJob: # other jobs expired_job: JobeetCategory: programming company: Sensio Labs position: Web Developer location: Paris, France description: Lorem ipsum dolor sit amet, consectetur adipisicing elit. how_to_apply: Send your resume to lorem.ipsum [at] dolor.sit is_public: true is_activated: true expires_at: '2005-12-01 00:00:00' token: job_expired email: job@example.com
確認の前に
追加前のデータはJobeetの作成時に合わせてexpires_atが設定されているので、画面を確認する前に変更しておく
変更しないで確認すると、全て有効期限切れとなり、なにも表示されなくなる
# data/fixtures/jobs.yml JobeetJob: job_sensio_labs: expires_at: '2009-10-10' job_extreme_sensio: expires_at: '2009-10-01'
app内での設定
とりあえず有効期限初期値がマジックナンバーになっているので、設定値化する
# apps/frontend/config/app.yml all: active_days: 30
この設定値の取得方法は
<?php sfConfig::get('app_active_days');
有効期限のデフォルト値を設定しているところを、この設定値を利用するように書き換える
<?php // lib/model/doctrine/JobeetJob.class.php 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', strtotime('-'.sfConfig::get('app_active_days').' days', $now))); } return parent::save($conn); }
リファクタリング
Doctrine_QueryはAction(Controlレイヤ)で利用するべきではないので、Modelレイヤに処理を移動させる
<?php // lib/model/doctrine/JobeetJobTable.class.php class JobeetJobTable extends Doctrine_Table { public function getActiveJobs() { $q = $this->createQuery('j') ->where('j.expires_at > ?', date('Y-m-d H:i:s')); return $q->execute(); } }
そしてActionの処理をモデルから呼び出すように変更
<?php // apps/frontend/modules/job/actions/actions.class.php public function executeIndex(sfWebRequest $request) { $this->jobeet_job_list = Doctrine::getTable('JobeetJob')->getActiveJobs(); }
ついでに有効期限が切れそうな順でソートする*1
<?php // lib/model/doctrine/JobeetJobTable.class.php class JobeetJobTable extends Doctrine_Table { public function getActiveJobs() { $q = $this->createQuery('j') ->where('j.expires_at > ?', date('Y-m-d H:i:s')) ->orderBy('j.expires_at ASC'); return $q->execute(); } }
カテゴリ毎にjobを表示する
<?php // lib/model/doctrine/JobeetCategoryTable.class.php class JobeetCategoryTable extends Doctrine_Table { public function getWithJobs() { $q = $this->createQuery('c') ->leftJoin('c.JobeetJobs j') ->where('j.expires_at > ?', date('Y-m-d H:i:s')); return $q->execute(); } }
<?php // apps/frontend/modules/job/actions/actions.class.php public function executeIndex(sfWebRequest $request) { $this->categories = Doctrine::getTable('JobeetCategory')->getWithJobs(); }
<?php /* apps/frontend/modules/job/indexSuccess.php */ ?> <?php use_stylesheet('jobs.css') ?> <div id="jobs"> <?php foreach ($categories as $category): ?> <div class="category_<?php echo Jobeet::slugify($category->getName()) ?>"> <div class="category"> <div class="feed"> <a href="">Feed</a> </div> <h1><?php echo $category ?></h1> </div> <table class="jobs"> <?php foreach ($category->getActiveJobs() as $i => $job): ?> <tr class="<?php echo fmod($i, 2) ? 'even' : 'odd' ?>"> <td class="location"> <?php echo $job->getLocation() ?> </td> <td class="position"> <?php echo link_to($job->getPosition(), 'job_show_user', $job) ?> </td> <td class="company"> <?php echo $job->getCompany() ?> </td> </tr> <?php endforeach; ?> </table> </div> <?php endforeach; ?> </div>
<?php // lib/model/doctrine/JobeetCategory.class.php public function getActiveJobs() { $q = Doctrine_Query::create() ->from('JobeetJob j') ->where('j.category_id = ?', $this->getId()); return Doctrine::getTable('JobeetJob')->getActiveJobs($q); }
JobeetJobTable::getActiveJobs()にDoctrine_Queryオブジェクトを渡すと、有効期限を参照する条件だけを加えるように変更する
<?php // lib/model/doctrine/JobeetJobTable.class.php public function getActiveJobs(Doctrine_Query $q = null) { if (is_null($q)) { $q = Doctrine_Query::create() ->from('JobeetJob j'); } $q->andWhere('j.expires_at > ?', date('Y-m-d H:i:s')) ->addOrderBy('j.expires_at ASC'); return $q->execute(); }
1ページに表示するjob数を設定する
データの取得数を制限するので、JobeetCategory::getActiveJobs()にLIMITを付けるように変更する
<?php // lib/model/doctrine/JobeetCategory.class.php public function getActiveJobs($max = 10) { $q = Doctrine_Query::create() ->from('JobeetJob j') ->where('j.category_id = ?', $this->getId()) ->limit($max); return Doctrine::getTable('JobeetJob')->getActiveJobs($q); }
実際にテンプレートで使用するときに、表示件数を設定するので、マジックナンバーにならないように設定値化する
# apps/frontend/config/app.yml all: active_days: 30 max_job_on_homepage: 5
テンプレートに適用
<?php /* apps/frontend/modules/job/templates/indexSuccess.php at 14 */ ?> <?php foreach ($category->getActiveJobs(sfConfig::get('app_max_jobs_on_homepage') as $i => $job): ?>
FixturesでPHPコードを利用する
表示個数の制限のデバッグのために、それぞれのカテゴリで11以上のデータが必要となる。
その為にはjobs.ymlにデータを記述しなくてはならないが、そんなに多くのデータを作成するのは骨が折れる
symfonyでは、YAMLファイル内でPHPコードが利用できるので、ループでその悩みを解決する
※制限として、「文末で使用する場合は "\n" を出力しないといけない」というのがある。
# data/fixtures/jobs.yml <?php for ($i = 100; $i <= 130; $i++): ?> job_<?php echo $i ?>: JobeetCategory: programming company: Company <?php echo "{$i}\n" ?> position: Web Developer location: Paris, France description: Lorem ipsum dolor sit amet, consectetur adipisicing elit. how_to_apply: | Send your resume to lorem.ipsum [at] company_<?php echo $i ?>.sit is_public: true is_activated: true expires_at: '2009-10-31 00:00:00' token: job_<?php echo "{$i}\n" ?> email: job@example.com <?php endfor; ?>
有効期限切れのjobの扱い
現状だと有効期限が切れたjobも見れてしまう
/frontend_dev.php/job/sensio-labs/paris-france/{$id}/web-developer-expired
※ serialが初期化された状態でdoctrine:data-loadをした場合、$idは恐らく3か4になる
見ることができないように、404ページへと強制的に遷移させたい
その条件処理をルーティングで実現する
# apps/frontend/config/routing.yml job_show_user: url: /job/:company_slug/:location_slug/:id/:position_slug class: sfDoctrineRoute options: model: JobeetJob type: object method_for_query: retrieveActiveJob param: { module: job, action: show } requirements: id: \d+ sf_method: [get]
このメソッドはTableオブジェクトに実装する必要がある
<?php // lib/model/doctrine/JobeetJobTable.class.php class JobeetJobTable extends Doctrine_Table { public function retrieveActiveJob(Doctrine_Query $q) { $q->andWhere('a.expires_at > ?', date('Y-m-d H:i:s')); return $q->fetchOne(); } }