S2Daoを使ってページャを実現する機能です。
S2Daoで検索した結果に対して、開始位置と最大取得件数を指定して結果の一部のみを取得することができます。
これにより、Googleの検索結果のように、大量の検索結果をページ単位で表示することが可能になります。
S2Pagerを既存のプロジェクトで使用するには、S2Daoのdao.diconをS2Pager用に修正する必要があります。
S2Daoディレクトリ/src/s2dao.php5/dao-pager.diconを参考にして以下のようにdao.diconを修正します。
(dao-pager.diconをdao.diconにリネームする方法が簡単です。)
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
"http://www.seasar.org/dtd/components21.dtd">
<components namespace="dao">
<include path="%PDO_DICON%" />
<component class="S2Dao_BasicResultSetFactory" />
<component class="S2Dao_BasicStatementFactory" />
<component class="S2Dao_FieldAnnotationReaderFactory" />
<component class="S2Dao_DaoMetaDataFactoryImpl" />
<component name="interceptor" class="S2Dao_PagerS2DaoInterceptorWrapper">
<property name="useLimitOffsetQuery">false</property>
</component>
</components>
ページャ機能は次の手順で実現します。
-
S2Dao_PagerConditionインターフェイスをimplementsする検索条件DTOを作成します。
デフォルトの実装として、org.seasar.dao.pager.S2Dao_DefaultPagerConditionクラスが用意されています。
検索条件DTOはS2Pager用のプロパティ(offset,limit,count)と、検索条件(下記の例ではcategory)を保持します。
<?php
interface S2Dao_PagerCondition
{
const NONE_LIMIT = -1;
public function getCount();
public function setCount($count);
public function getLimit();
public function setLimit($limit);
public function setOffset($offset);
public function getOffset();
}
?>
class CategoryPagerCondition extends S2Dao_DefaultPagerCondition {
private $category;
public function getCategory() {
return $category;
}
public function setCategory($category) {
$this->category = $category;
}
}
-
検索条件DTOを引数に持つDaoの検索メソッドを作成します。
interface BookDao {
/**
* @return list
*/
public function findByCategoryPagerCondition(CategoryPagerCondition $dto);
}
-
検索条件DTOに開始位置(offset)と最大取得数(limit)をセットしてDaoのメソッドを呼び出します。
$dto = new CategoryPagerCondition();
$dto->setLimit(10);
$offset = $_REQUEST['offset'];
$dto->setOffset($offset);
$category = $_REQUEST['category'];
if ($category != null && length($category) != 0) {
$dto->setCategory($category);
}
$bookDao = $container->getComponent('BookDao');
$books = $bookDao->findByCategoryPagerCondition($dto);
var_dump($books);
通常、検索条件オブジェクトはsession変数に格納します。
S2Pagerでは検索条件オブジェクトのsessionへの格納などをサポートする
ユーティリティ的なクラスとしてS2Dao_PagerSupportクラスを用意しています。
S2Dao_PagerSupportクラスのコンストラクタで次の項目を指定します。
引数 |
意味
| 説明
|
第1引数 |
最大取得数 |
S2Dao_PagerConditionのlimitに使用されます。 |
第2引数 |
条件保持DTOのクラス名 |
セッション中に>条件保持DTOが存在しかった場合、ここで指定したクラス名の検索条件DTOが生成されます。 |
第3引数 |
属性名 |
セッションの属性名を指定します。ここで指定した名前で検索条件DTOがセッション中に格納されます。 |
$pager = new S2Dao_PagerSupport(10, 'CategoryPagerCondition', 'categoryPagerCondition');
セッション中の検索条件DTOの取得開始位置(offset)の更新は、次のコードで可能です。
$pager->updateOffset($_REQUEST['offset']);
セッション中の検索条件DTOを取得するには、次のようなコードになります。
$dto = $pager->getPagerCondition();
以上のS2Dao_PagerSupportの使い方をまとめると、次のようなコードになります。
<?php
$pager = new S2Dao_PagerSupport(10, 'CategoryPagerCondition', 'categoryPagerCondition');
$pager->updateOffset($_REQUEST['offset']);
$dto = $pager->getPagerCondition();
$category = $_REQUEST["category"];
if (isset($category)) {
$dto->setCategory($category);
}
$books = $dao->findByCategoryPagerCondition($dto);
?>
S2Dao_PagerConditionの情報を元にビューでリンクを生成するためのビューヘルパークラスとして、
org.seasar.dao.pager.S2Dao_PagerViewHelperクラスがあります。
S2Dao_PagerViewHelperクラスを使うとビューでページリンクを作成するのが楽になります。
PagerViewHelperは以下のメソッドを持っています。
<?php
class S2Dao_PagerViewHelper implements S2Dao_PagerCondition
{
const DEFAULT_DISPLAY_PAGE_MAX = 9;
private $condition;
private $displayPageMax;
public function __construct($condition, $displayPageMax = null)
{
$this->condition = $condition;
if (isset($displayPageMax)) {
$this->displayPageMax = $displayPageMax;
} else {
$this->displayPageMax = self::DEFAULT_DISPLAY_PAGE_MAX;
}
}
public function getCount(){ ... }
public function setCount($count){ ... }
public function getLimit()
{
return $this->condition->getLimit();
}
public function setLimit($limit)
{
$this->condition->setLimit($limit);
}
public function getOffset()
{
return $this->condition->getOffset();
}
public function setOffset($offset)
{
$this->condition->setOffset($offset);
}
public function isPrev()
{
return 0 < $this->condition->getOffset();
}
public function isNext()
{
$count = $this->condition->getCount();
$nextOffset = $this->condition->getOffset() + $this->condition->getLimit();
return 0 < $count && $nextOffset < $count;
}
public function getCurrentLastOffset()
{
$count = $this->condition->getCount();
$nextOffset = $this->getNextOffset($this->condition);
if ($nextOffset <= 0 || $this->condition->getCount() <= 0) {
return 0;
} else {
return $nextOffset < $count ? $nextOffset - 1 : $count - 1;
}
}
public function getNextOffset()
{
return $this->condition->getOffset() + $this->condition->getLimit();
}
public function getPrevOffset()
{
$prevOffset = $this->condition->getOffset() - $this->condition->getLimit();
return $prevOffset < 0 ? 0 : $prevOffset;
}
public function getPageIndex()
{
$limit = $this->condition->getLimit();
$offset = $this->condition->getOffset();
if ($limit == 0) {
return 1;
} else {
return floor($offset / $limit);
}
}
public function getPageCount()
{
return $this->getPageIndex() + 1;
}
public function getLastPageIndex()
{
$limit = $this->condition->getLimit();
$count = $this->condition->getCount();
if ($limit == 0) {
return 0;
} else {
return floor(($count - 1) / $limit);
}
}
public function getDisplayPageIndexBegin()
{
$lastPageIndex = $this->getLastPageIndex();
if ( $lastPageIndex < $this->displayPageMax ) {
return 0;
} else {
$currentPageIndex = $this->getPageIndex();
$displayPageIndexBegin = $currentPageIndex - (floor($this->displayPageMax / 2));
return $displayPageIndexBegin < 0 ? 0 : $displayPageIndexBegin;
}
}
public function getDisplayPageIndexEnd()
{
$lastPageIndex = $this->getLastPageIndex();
$displayPageIndexBegin = $this->getDisplayPageIndexBegin();
$displayPageRange = $lastPageIndex - $displayPageIndexBegin;
if ($displayPageRange < $this->displayPageMax) {
return $lastPageIndex;
} else {
return $displayPageIndexBegin + $this->displayPageMax - 1;
}
}
}
?>
以下はサンプルのページリンクの実装例です。
ページリンクの実装:pager.php
<?php
$container = S2ContainerFactory::create('resource/example.dicon.xml');
$dao = $container->getComponent('BooksDao');
$dto = new S2Dao_DefaultPagerCondition();
$dto->setOffset(3);
$dto->setLimit(5);
$books = $dao->getByPagerDtoList($dto);
$helper = new S2Dao_PagerViewHelper($dto);
?>
<html>
<body>
<?php
if ($helper->isPrev()) {
print( '<a href="pager.php?offset=' . ( $helper->getOffset() + 1 ). '">' );
print('前の' . $helper->getLimit() . '件');
print( '>' );
}
for ( $i = $helper->getDisplayPageIndexBegin(); $i <= $helper->getDisplayPageIndexEnd(); $i++ ) {
if ( $i == $helper->getPageIndex() ) {
print($i + 1);
} else {
print('<a href="pager.php?offset=' . $i * $helper->getLimit() . '">' . ( $i + 1 ) . '</a> ');
}
}
if ( $helper->isNext() ) {
print( '<a href="pager.php?offset=' . ( $helper->getOffset() - 1 ). '">' );
print( ' 次の' . $helper->getLimit() . '件' );
print( '>' );
}
?>
<table>
<tr>
<td>ID</td><td>TITLE</td><td>CONTENT</td>
</tr>
<?php
foreach ($books as $book) {
print( '<tr>' );
print( '<td>' . $book->getId() . '</td>' );
print( '<td>' . $book->getTitle() . '</td>' );
print( '<td>' . $book->getContent() . '</td>' );
print( '</tr>' );
}
?>
</table>
</body>
</html>
PosgreSQLやMySQLのように「limit offset」が使用できるDBMSでは、大量データ検索時のパフォーマンスが大幅に向上します。
以下の設定によりS2Dao_PagerS2DaoInterceptorWrapperのuseLimitOffsetQueryプロパティをtrueにすることで
「limit offset」を使用した取得が可能になります。
limitとoffsetを使用した高速取得の設定(dao-pager.dicon)
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
"http://www.seasar.org/dtd/components21.dtd">
<components namespace="dao">
<include path="%PDO_DICON%" />
<component class="S2Dao_BasicResultSetFactory" />
<component class="S2Dao_BasicStatementFactory" />
<component class="S2Dao_FieldAnnotationReaderFactory" />
<component class="S2Dao_DaoMetaDataFactoryImpl" />
<component name="interceptor" class="S2Dao_PagerS2DaoInterceptorWrapper">
<property name="useLimitOffsetQuery">true</property>
</component>
</components>
S2Dao.PHP5-1.1.3から、S2Pager内部で発行する全件数を取得するSQLで、元のSQLからorder by句を削除することで、さらに高速にページングできるようになりました。これは、全件数を取得するのに並び順は関係がないため、order by句を削除することでSQLのコストを小さくしています。
もしorder by句の削除を行いたくない場合や問題がある場合は、S2Dao_PagerS2DaoInterceptorWrapperの「chopOrderBy」プロパティをfalseにすることで、以前と同様にorder by句を削除せずに全件数を取得するSQLを発行することもできます。(デフォルトはtrueでorder by句が削除されます)
<component name="interceptor" class="S2Dao_PagerS2DaoInterceptorWrapper">
<property name="useLimitOffsetQuery">true</property>
<property name="isChopOrderBy">true</property>
</component>
|