Skip to content

Instantly share code, notes, and snippets.

@yano3nora
Last active May 10, 2018 15:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yano3nora/45aa77d9e854a04dfb77fcfc11447f8c to your computer and use it in GitHub Desktop.
Save yano3nora/45aa77d9e854a04dfb77fcfc11447f8c to your computer and use it in GitHub Desktop.
[cakephp: Paginator sample] Sample code of PaginatorComponent on CakePHP. #cakephp

TIPS & REFERENCES

CakePHP はページネートとソートについて、シンプルな実装以外は独自拡張が要る。結構だるい。

Manual

Refs


Sample codes

以下、未検証コード混在。案件で利用したものも。

検索/ページネート条件のセッション保持

ページネートや検索条件について index → edit → index みたいなフローで遷移した際に「さっきまでの検索/ページネート条件に戻りたい」みたいなケース。

/**
 * Index of Users. ( search / pagination )
 * @param  array $conditions
 * @return Users $Users
 */
public function index()
{
    $session    = $this->request->session();
    $conditions = $this->request->getQuery();
    if (empty($conditions)) {
        if (!empty($session->read('Users.index'))) {
            return $this->redirect([
                'action' => 'index',
                '?'      => $session->consume('Users.index'),
            ]);
        }
    }
    $session->write('Users.index', $conditions);
    // Expecting to receive something reset flag.
    if ($this->request->query('all')) {
        $session->delete('Users.index');
        return $this->redirect(['action' => 'index']);
    }
    try {
        $Users = $this->paginate($this->Users, $conditions);
        $this->set(compact('Users'));
    } catch (\Cake\Network\Exception\NotFoundException $e) {
        return $this->redirect(['action' => 'index']);
    }
}

自身プロパティや関連モデルに依らないソート

ステータスクラスとか使わず、複数のフラグ ( is_stopped とか ) の組み合わせで成り立つ「状態」によるソートをしなくちゃならんってケース。この場合ページングがさっぱり聞かなくなるので、ソート条件 ( $Query->order() ) やページング条件 ( $Query->limit() ) を自分でがっつり書き込んでから $this->paginate() に渡す必要がある。

  • Agents には .is_stoppedstopped_date があり、停止・停止予約状態がる
  • Agents には is_registered による、仮登録・本登録状態がある
  • Agents は上記4つの状態について、「本登録状態」をノーマルとして「仮→本→停予→停止」のようなライフサイクルを送る
  • Agents は物理削除されるケースがあるが、これは完全なサービスからの離脱・または生成ミスの訂正なため、ステータスとして管理しない
/**
 * AgentsController::index()
 * 
 * view に $this->Paginator->sort('Agents.is_registered', 'ステータス') みたいなソートリンクがある前提
 * sort に Agents.is_registered が投げられた時クエリを独自にカスタムして paginate へ渡す
 * 上記ケース以外では通常通りプレーンなクエリ $this->Agents->find() を渡す
 */
public function index() {
  $conditions = [ /* 何らかの抽出条件 */ ];
  $agents = $this->Agents->find()->where($conditions);
  if ($this->request->query('sort') == 'Agents.is_registered') {
    // クエリビルダーにより Select 句に Case 文をぶち込んでソート可能な仮想カラム status を生成
    $agents = $agents->select($this->Agents);
    $agents = $agents->select(['status' => $agents->newExpr()->addCase(
      [
        $agents->newExpr()->add([
          'is_registered'   => false,
          'is_stopped'      => false,
        ]),
        $agents->newExpr()->add([
          'is_registered'   => true,
          'is_stopped'      => false,
        ]),
        $agents->newExpr()->add([
          'stopped_date >'  => date('Y-m-d'),
          'is_stopped'      => true,
        ]),
        $agents->newExpr()->add([
          'stopped_date <=' => date('Y-m-d'),
          'is_stopped'      => true,
        ]),
      ],
      [0, 1, 2, 3],
      ['integer', 'integer', 'integer', 'integer']
    )]);
    // 仮想カラム status によるソートを行う
    $agents->order(['status' => $this->request->query('direction')]);
    // もし page がクエリストリングで投げられたら ( = ページネーションのリンクを踏んだら ) 
    // page の値から offset を計算して開始位置を調整 ( パフォーマンスを気にするなら BETWEEN とかのがいいのかな ) 
    if ($this->request->query('page')) {
      $offset = (PAGINATE_LIMIT * ((int)$this->request->query('page') - 1));
      $agents->offset($offset);
    }
  }
  // PaginatorHelper に渡すためのクエリに変換
  $agents = $this->paginate($agents, ['limit' => PAGINATE_LIMIT]);
  $this->set(compact('agents'));
}
<?php
/**
* Paginator
* @link https://book.cakephp.org/3.0/ja/controllers/components/pagination.html
* @link https://book.cakephp.org/3.0/ja/views/helpers/paginator.html
*/
// in AppController
public function initialize()
{
parent::initialize();
$this->loadComponent('Paginator');
}
// Controller method
public function index() {
$user = $this->Auth->user();
$query = $tihs->Articles->find()->where([
'Articles.user_id' => $user['id'], // フィルタリングはここでやんないとだめ
]);
$Articles = $this->paginate($query, [
'sortWhitelist' => ['Tags.name'], //関連エンティティのソート時はホワイトリストを設定
'contain' => ['Tags'],
'limit' => 10,
'order' => ['Articles.created' => 'desc']
]);
$this->set(compact('Articles'));
}
?>
<!-- View -->
<table class="mdl-data-table mdl-js-data-table mdl-shadow--2dp l-fitwidth t-font-size--small">
<thead>
<tr>
<th><?php echo $this->Paginator->sort('Articles.created', '投稿日') ?></th>
<th><?php echo $this->Paginator->sort('Articles.title', 'タイトル') ?></th>
<th><?php echo $this->Paginator->sort('Tags.name', 'タグ') ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($Articles ?? [] as $Article): ?>
<tr>
<td><?php echo h($Article->created->format('Y/m/d')) ?></td>
<td><?php echo h($Article->title) ?></td>
<td>
<?php echo h($Article->tag->name) ?>
</td>
</tr>
<?php endforeach ?>
</tbody>
</table>
<section class="c-pagination c-flex c-flex--nowrap p-container l-padding">
<?php echo $this->Paginator->prev() ?>
<?php echo $this->Paginator->numbers() ?>
<?php echo $this->Paginator->next() ?>
</section>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment