CakePHP3で一覧画面表示の実装を共通化する方法

# CakePHP3で一覧画面表示の実装を共通化する方法

## index.ctpでよく見る一覧画面の実装

例）ユーザーの一覧画面

```
<table>
<thead>
  <tr>
    <th><?= $this->Paginator->sort('id') ?></th>
    <th><?= $this->Paginator->sort('mame') ?></th>
    <th><?= $this->Paginator->sort('sex') ?></th>
    <th><?= $this->Paginator->sort('age') ?></th>
    <th><?= $this->Paginator->sort('mail') ?></th>
    <th><?= $this->Paginator->sort('tel') ?></th>
    <th><?= __('Actions') ?></th>
  </tr>
</thead>
<tbody>
<?php foreach ($users as $user): ?>
  <tr>
    <td><?= $user->id ?></td>
    <td><?= $user->name ?></td>
    <td><?= $user->sex ?></td>
    <td><?= $user->age ?></td>
    <td><?= $user->mail ?></td>
    <td><?= $user->tel ?></td>
    <td><?= $this->Html->link(__('Edit'), ['action' => 'edit', $user->id]) ?></td>
  </tr>
<?php endforeach; ?>
</tbody>
</table>
```

という感じで他のindex画面も同じように作っていくわけですが、書いていくのが面倒です・・・  
Bakeで楽はできるけども、コード量が増えるということはバグが増えるということで極力書きたくないもの。

というわけで良い方法がないか考えてみました。

## 検討したポイントとしては3点  
1. `共通化`したい  
2. `<table>`特に、`<tr><td>`タグを書きたくない  
3. 表示項目（テーブルのカラム名）を`index.ctp`に書きたくない  


## 検討した結果の実装コード

### 表示項目をコントローラーに記述

src/Controller/UsersController.ctp

```
namespace App\Controller;

use App\Controller\AppController;
use Cake\Collection\Collection;

class UsersController extends AppController
{
    /*
     * ヘッダー(この項目が一覧として表示される)
     */
    public $tableHeader = [
        'id',
        'name',
        'sex',
        'age',
        'mail',
        'tel',
    ];

    public function index()
    {
        $users = $this->paginate($this->Users);
        $this->set(compact('users'));
        $this->set('_serialize', ['users']);

        $seedHeader = new Collection($this->tableHeader);
        $this->set('seedHeader', $seedHeader);
    }
```

表示項目を毎回を書きたくないのでControllerに表示項目を記述してtemplate側に渡すことにする

### table部分をElement化

src/Template/Elememt/default_table.ctp

```
  <table>
    <thead>
      <?php
      $contentsHeader = [];
      $contentsHeader[] = __('Actions');
      $contentsHeader = array_merge($contentsHeader, $seedHeader->map(function ($header) {
        return $this->Paginator->sort($header);
      })->toArray());
      ?>
      <?= $this->Html->tableHeaders($contentsHeader) ?>
    </thead>
    <tbody>
      <?php
      $contentsBody = $seedBody->map(function ($body) use ($seedHeader) {
        $row = [];
        $row[] = $this->Html->link(__('Edit'), ['controller' => $body->source(), 'action' => 'edit', $body->id]);
        $row = array_merge($row, $seedHeader->map(function ($header) use ($body) {
          return $body->{$header};
        })->toArray());
        return $row;
      })->toArray();
      ?>
      <?= $this->Html->tableCells($contentsBody); ?>
    </tbody>
  </table>
```

どの画面でも使えるようにしたいのでtable部分をelement化して外出し

CakePHP3から新しく入った `$this->Html->tableHeaders()` 、`$this->Html->tableCells()`を利用して<table>の中身をいい感じに生成



### Element化したファイルを呼び出して使用する

src/Template/Users/index.ctp

```
～省略～

<?= $this->element('default_table', ['seedBody' => $users]); ?>

～省略～
```


こんな感じで作ってみました。  
これでコード量をガッツリ減らすことができるので便利かなーと思います。


## ポイント
・controller側で指定したヘッダー項目がcollection型で渡ってくるのでこれを`$this->Html->tableHeaders()`、`$this->Html->tableCells()`を利用して`map()`で処理しているいるところです。


これでindex.ctpに表示項目を書いていく作業から解放されます(๑˃̵ᴗ˂̵)و。