PHPでページネーション

毎回アプリケーションを作るたびに実装しているので面倒になったのでメモ

```php:BaseTrait.php
<?php

namespace App\Model;

trait BaseTrait
{

    private static $instance;

    private function __construct()
    {
        // new from private only
    }

    public static function getInstance()
    {
        if ( ! self::$instance)
        {
            self::$instance = new static();
        }

        return self::$instance;
    }
}
```

```php:Page.php
<?php

namespace App\Model;

class Page
{

    use BaseTrait;

    private $page_unit;
    private $default_page;
    private $page_link;

    private function __construct()
    {
        $this->setPageUnit(3);// １ページに表示する件数
        $this->setDefaultPage(1);// 最初のページ
        $this->setPageLink(5);// ページネーションのリンクを表示する件数(中央をアクティブにしたいので奇数)
    }

    public function setPageUnit(int $number)
    {
        $this->page_unit = $number < 1 ? 1 : $number;
    }

    public function setDefaultPage(int $number)
    {
        $this->default_page = $number < 1 ? 1 : $number;
    }

    public function setPageLink(int $number)
    {
        $this->page_link = $number < 1 ? 1 : $number;
    }

    /**
     * ページネーションの表示に必要な情報の取得
     * これで取得したoffsetとlimitで表示するアイテムのリストは別途取得すること
     *
     * @param int $page ページ位置（クエリーストリングから渡ってくる想定）
     * @param int $item_count 全アイテム数
     * @return array
     */
    public function getPageData(int $page = null, int $item_count): array
    {
        $item_count = $item_count < 1 ? 1 : $item_count;

        $data = [];
        $data['limit'] = $this->page_unit;
        $data['max_page'] = (int)(($item_count - 1) / $this->page_unit) + 1;

        if (is_null($page) || $page === 0 || $data['max_page'] < $page)
        {
            $data['current_page'] = $this->default_page;
            $data['offset'] = 0;
        }
        else
        {
            $data['current_page'] = $page;
            $data['offset'] = ($page - 1) * $this->page_unit;
        }

        $data['page_link'] = $this->page_link;
        if ($data['current_page'] < $data['max_page'] - (int)($data['page_link'] / 2))
        {
            $start_number = $data['current_page'] - (int)($data['page_link'] / 2);
        }
        else
        {
            $start_number = $data['max_page'] - $data['page_link'] + 1;
        }
        $data['start_page'] = $start_number < $this->default_page ? $this->default_page : $start_number;

        $data['pre_page'] = $data['start_page'] > 1 ? $data['start_page'] - 1 : null;
        $end_number = $data['start_page'] + $data['page_link'] - 1;
        $data['next_page'] = $end_number < $data['max_page'] ? $end_number + 1 : null;

        return $data;
    }
}
```

コントローラ

```php
use App\Model\Page;
～～～～～～～～～～～～～～～～～～

$page = 処理;// クエリーストリングから取得（int|null）
$item_count = 処理;// DB等から対象データ件数取得（int）

$page_model = Page::getInstance();
$data['page_data'] = $page_model->getPageData($page, $item_count);

// DB等から$data['page_data']['offset']と$data['page_data']['limit']で表示データ取得
$data['item_object_list'] = 処理;

// $dataをテンプレートエンジンに渡す
```

Bootstrap付きテンプレートエンジンで表示

* blade

```html
<div class="text-center">
    <ul class="pagination">
        @for ($i = $page_data['start_page']; $i < $page_data['start_page'] + $page_data['page_link']; $i++)
            @if ($i > $page_data['max_page'])
                @break
            @endif
            @if ($i === $page_data['current_page'])
                <li class="active"><a href="?page={{ $i }}">{{ $i }}</a></li>
            @else
                <li><a href="?page={{ $i }}">{{ $i }}</a></li>
            @endif
        @endfor
    </ul>
</div>

<!--ここにアイテム-->
<table class="table table-striped table-hover table-bordered">
    <tr>
        <th>ID</th>
        <th>Name</th>
    </tr>
    @foreach ($item_object_list as $item_object)
        <tr>
            <td>{{ $item_object->id }}</td>
            <td>{{ $item_object->name }}</td>
        </tr>
    @endforeach
</table>
```

* Smarty

```html
<div class="text-center">
    <ul class="pagination">
        {for $i = $page_data['start_page'] to ($page_data['start_page'] + $page_data['page_link'] - 1)}
            {if $i > $page_data['max_page']}
                {break}
            {/if}
            {if $i === $page_data['current_page']}
                <li class="active"><a href="?page={$i}">{$i}</a></li>
            {else}
                <li><a href="?page={$i}">{$i}</a></li>
            {/if}
        {/for}
    </ul>
</div>

<!--ここにアイテム-->
<table class="table table-striped table-hover table-bordered">
    <tr>
        <th>ID</th>
        <th>Name</th>
    </tr>
    {foreach from=$item_object_list item=item_object}
        <tr>
            <td>{$item_object->id}</td>
            <td>{$item_object->name}</td>
        </tr>
    {/foreach}
</table>
```

![pagenation.png](https://qiita-image-store.s3.amazonaws.com/0/156195/98272c36-5a6e-78dc-7d50-bb5cb0fecde5.png)

※`前へ`や`次へ`のリンクを作る際は`$data['pre_page']`か`$data['next_page']`を使う。