読者です 読者をやめる 読者になる 読者になる

オープンソースこねこね

Webプログラミングなどについてあれこれ。

symfony, Doctrine1.4でページャの生成をDoctrine_Queryクラスに含めるための拡張

PHP

Doctrine1.4はsymfony1.4系のデフォルトORMです。symfonyにはORMにバンドルする形でページャ機能も最初から含まれていて便利ですが、一般的にページャの生成はこんな感じのコードになります。

(Jobeetから拝借)

// apps/frontend/modules/category/actions/actions.class.php
public function executeShow(sfWebRequest $request)
{
  $this->category = $this->getRoute()->getObject();
 
  $this->pager = new sfDoctrinePager(
    'JobeetJob',
    sfConfig::get('app_max_jobs_on_category')
  );
  $this->pager->setQuery($this->category->getActiveJobsQuery());
  $this->pager->setPage($request->getParameter('page', 1));
  $this->pager->init();
}

sfDoctrinePagerをnewして$this->category->getActiveJobsQuery()で取得したDoctrine_Queryをセットして、現在のページをセットして、initメソッド実行、などとつらつらと処理を書く必要があります。ちょっと煩雑です。

Doctrine_Queryにこの典型的な処理をまとめてしまうとページャ生成までをメソッドチェーンに含めることができて、もっとシンプルにコードを書くことができます。

手順

まず、Doctrine_Queryを継承した以下のようなクラスを作ります。

class myDoctrineQuery extends Doctrine_Query
{
  protected $_pagerClass = null;

  public function setPagerClass($class)
  {
    $this->_pagerClass = $class;
  }

  public function getPager($page = 1, $maxPerPage = 25)
  {
    $pager = new sfDoctrinePager($this->_pagerClass, $maxPerPage);
    $pager->setQuery($this);
    $pager->setPage($page);
    $pager->init();
    return $pager;
  }
}

次に、Doctrine_Tableを継承した以下のようなクラスを作ります。

class myDoctrineTable extends Doctrine_Table
{
  public function createQuery($alias = '')
  {
    $query = parent::createQuery($alias);
    $query->setPagerClass($this->_options['name']);
    return $query;
  }
}

これらの拡張クラスを使用するためにイベントリスナクラスを作って、モデル生成タスクの設定値を切り替えるように指定します。

class myDoctrineEventListener
{
  public static function listenToFilterModelBuilderOptionsEvent(sfEvent $event, $options)
  {
    // Doctrineのモデル生成タスクのオプションをカスタマイズする
    $options['baseTableClassName'] = 'myDoctrineTable';
    return $options;
  }

  public static function listenToConfigureEvent(sfEvent $event)
  {
    $manager = Doctrine_Manager::getInstance();
    // Queryクラスを標準のものから切り替える
    $manager->setAttribute(Doctrine::ATTR_QUERY_CLASS, 'myDoctrineQuery');
  }
}

前の手順で作成したイベントリスナをProjectConfigurationで利用するように指定します。

require_once dirname(__FILE__).'/../lib/vendor/symfony/lib/autoload/sfCoreAutoload.class.php';
sfCoreAutoload::register();

class ProjectConfiguration extends sfProjectConfiguration
{
  public function setup()
  {
    // Load Doctrine Plugin
    $this->enablePlugins('sfDoctrinePlugin');

    // Connect custom event
    $this->dispatcher->connect('doctrine.filter_model_builder_options', array('myDoctrineEventListener', 'listenToFilterModelBuilderOptionsEvent'));
  }

  public function configureDoctrine($manager)
  {
    $manager->setAttribute(Doctrine::ATTR_QUERY_CLASS, 'myDoctrineQuery');
  }
}

symfonyコマンドを実行してモデルを生成。生成されたテーブルクラスの親がmyDoctrineTableになっていることを確認します

#  php symfony doctrine:build-model

これで準備が出来ました。Doctrine_Queryの最初の生成をTableクラスのcreateQueryを使うようにすれば、上記のJobeetの例では以下のようにコードがかけます

// apps/frontend/modules/category/actions/actions.class.php
public function executeShow(sfWebRequest $request)
{
  $this->category = $this->getRoute()->getObject();
  $this->pager = $this->category->getActiveJobsQuery()->getPager($request->getParameter('page', 1), sfConfig::get('app_max_jobs_on_category'));
}

// lib/model/doctrine/JobeetCategory.class.php
public function getActiveJobsQuery()
{
  $q = JobeetJobTable::getInstance()->createQuery()
    ->where('j.category_id = ?', $this->getId());
 
  return Doctrine_Core::getTable('JobeetJob')->addActiveJobsQuery($q);
}

メインのロジックがたった2行になりましたよ。すっきり~。