slimでknp-componentsのpaginatorを使う(データベースアクセス版)

前回の記事では最初から全データを用意してページ捲りを実装してました。
ただ、データベースに対象データが存在する場合は全件データを取得するのは現実的ではありません。
データベースからデータの一部を取得する形でページ捲りを実装する方法を記します。

1. データの投入
aからzまでのデータが存在するalphabetテーブルを用意します。

mysql> create table alphabet( str varchar(1), sort int);
mysql> insert into alphabet(str, sort) values('a', 1);
mysql> insert into alphabet(str, sort) values('b', 2);
~省略~
mysql> select * from alphabet;
+------+------+
| str  | sort |
+------+------+
| a    |    1 |
| b    |    2 |
| c    |    3 |
| d    |    4 |
| e    |    5 |
| f    |    6 |
| g    |    7 |
~省略~

2. DoctrineのORMを使う場合の実装例
DoctrineのORM等を利用する場合はpaginateの第1引数にDoctrineのQueryを指定すれば完了です。

$ vi src/Taka512/Controllers/ContentsController.php
~省略~
    public function pageTest($page) {
        $paginator = new Paginator();
        $pagination = $paginator->paginate(
            $this->em->createQuery('SELECT a FROM Entity\Alphabet a'), $page, 5);
        $this->app->render('page_test.html.twig', array('pagination' => $pagination));
    }

3. PDOを使う場合の実装例
knp-componentsのページ捲りはSymfony2のEventDispatcherのcomponentsを使用して実装してます。
DoctrineのORMを使用している場合は既存のSubscriberを使用してページ捲り用の処理を行いますが、
PDOの場合は自分でSubscriberを実装する必要があります。

Subscriberの作成
Subscriberではcountでデータ件数とlimitでデータを取得してEventオブジェクトに設定します。

$ mkdir -p src/Taka512/Events/Subscriber
$ vi src/Taka512/Events/Subscriber/PdoQuerySubscriber.php
<?php
namespace Taka512\Events\Subscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Knp\Component\Pager\Event\ItemsEvent;
use Taka512\Models\PdoQuery;

class PdoQuerySubscriber implements EventSubscriberInterface
{
    public function items(ItemsEvent $event)
    {
        if ($event->target instanceof PdoQuery) {

            $sth = $event->target->dbh->prepare('SELECT COUNT(*) AS count FROM alphabet');
            $sth->execute();
            while($row = $sth->fetch(\PDO::FETCH_ASSOC)){
                $event->count = $row['count'];
            }

            $event->items = array();
            if ($event->count) {
                $sth = $event->target->dbh->prepare('SELECT * FROM alphabet ORDER BY SORT LIMIT ? OFFSET ?');
                $sth->execute(array($event->getLimit(),$event->getOffset()));
                while($row = $sth->fetch(\PDO::FETCH_ASSOC)){
                    $event->items[] = $row;
                }
            }
            $event->stopPropagation();
        }
    }

    public static function getSubscribedEvents()
    {
        return array(
            'knp_pager.items' => array('items', 2)
        );
    }
}

Subscriberに渡すデータを保持するクラスを作成します。
本来はここで検索条件などを保持してPdoQuerySubscriberで組み立てますが今回はdbhのみ保持します。

$ mkdir -p src/Taka512/Models
$ vi src/Taka512/Models/PdoQuery.php
<?php
namespace Taka512\Models;

class PdoQuery
{
    public $dbh;
    public function __construct($dbh)
    {
        $this->dbh   = $dbh;
    }
}

Controllerの変更
Controllerでは明示的にsubscriberを指定し、作成したPdoQueryクラスを指定すると
PdoQuerySubscriberのitemsが動作してpaginationデータがセットされるようになります。

$ vi src/Taka512/Controllers/ContentsController.php
~省略~
    public function pageTest($page) {
        $paginator = new Paginator();
        $paginator->subscribe(new PdoQuerySubscriber());
        $query = new PdoQuery($this->dbh);
        $pagination = $paginator->paginate($query, $page, 5);

        $this->app->render('page_test.html.twig', array('pagination' => $pagination));
    }

こんな感じでデータベースを利用したページ捲りが実装できます。