CakePHPで絞り込み機能の実装

この記事は [CakePHP Advent Calendar 2018](https://qiita.com/advent-calendar/2018/cakephp) 25日目の記事です。

空いていましたので、滑り込ませて頂きました。

##やりたい事

ある一覧表のようなものから、絞り込み表示したい。

##実装フロー

1. マイグレーションファイルでテーブル作成
2. マイグレーションファイルで仮データをインサート
3. TableとEntity作成
4. 対象テーブルにリレーションを張る
5. Controller作成
6. 対象テーブルから全データ取得
7. View作成
8. 取得データを表示
9. フォームの作成
10. 絞り込みボタン設置
11. 絞り込みボタンが押されたらクエリパラメータをGETメソッドでリクエスト
12. クエリパラメータを元にテーブルからデータを取得する際に絞り込む
13. 絞り込まれたデータを表示

##1. マイグレーションファイルでテーブル作成

今回は2つのテーブルを作成
- **categories Table**
- **master_category_types Table**

```
$ bin/cake migrations create CreateCategories
$ bin/cake migrations create CreateMasterCategoryType
```

マイグレーションファイル内のコードは下記参照

*categories*テーブル作成マイグレーションファイル

```php:MigrationFile1
<?php
use Migrations\AbstractMigration;

class CreateCategories extends AbstractMigration
{
    public function up()
    {
        $table = $this->table('categories');
        $table->addColumn('name', 'string', [
            'null' => false
        ]);
        $table->addColumn('user_id', 'integer', [
            'null' => false
        ]);
        $table->addColumn('type_id', 'integer', [
            'null' => false
        ]);
        $table->create();
    }

    public function down()
    {
        $table = $this->table('categories');
        $table->drop();
    }
}
```

*master_category_types*テーブル作成マイグレーションファイル

```php:MigrationFile2
<?php
use Migrations\AbstractMigration;

class CreateMasterCategoryType extends AbstractMigration
{
    public function up()
    {
        $table = $this->table('master_category_type');
        $table->addColumn('name', 'string', [
            'null' => false
        ]);
        $table->create();
    }

    public function down()
    {
        $table = $this->table('master_category_type');
        $table->drop();
    }
}
```


##2. マイグレーションファイルで仮データをインサート

それぞれのテーブルにデータをインサートするマイグレーションファイル作成

```
$ bin/cake migrations create InsertCategories
$ bin/cake migrations create InsertMasterCategoryType
```

*categories*テーブルにインサートするマイグレーションファイル

```php:MigrationFile3
<?php
use Migrations\AbstractMigration;

class InsertCategories extends AbstractMigration
{
    public function up()
    {
        $table = $this->table('categories');
        $row = [
            [
                'name' => 'リンゴ',
                'user_id' => 2343,
                'type_id' => 1
            ],
            [
                'name' => 'ピアノ',
                'user_id' => 2343,
                'type_id' => 2
            ],
            [
                'name' => 'ビール',
                'user_id' => 2343,
                'type_id' => 3
            ],
            [
                'name' => 'バナナ',
                'user_id' => 2343,
                'type_id' => 1
            ],
            [
                'name' => 'ギター',
                'user_id' => 2343,
                'type_id' => 2
            ],
            [
                'name' => 'ワイン',
                'user_id' => 2343,
                'type_id' => 3
            ],
            [
                'name' => 'ブドウ',
                'user_id' => 2343,
                'type_id' => 1
            ],
            [
                'name' => 'ベース',
                'user_id' => 2343,
                'type_id' => 2
            ],
            [
                'name' => '日本酒',
                'user_id' => 2343,
                'type_id' => 3
            ],
            [
                'name' => 'ミカン',
                'user_id' => 2343,
                'type_id' => 1
            ],
        ];
        $table->insert($row);
        $table->saveData();
    }

    public function down()
    {
        $this->execute('DELETE FROM categories');
    }
}
```

*master_category_types*テーブルにインサートするマイグレーションファイル

```php:MigrationFile4
<?php
use Migrations\AbstractMigration;

class InsertMasterCategoryType extends AbstractMigration
{
    public function up()
    {
        $table = $this->table('master_category_type');
        $row = [
            [
                'name' => '食べ物'
            ],
            [
                'name' => '楽器'
            ],
            [
                'name' => 'お酒'
            ],
        ];
        $table->insert($row);
        $table->saveData();
    }

    public function down()
    {
        $this->execute('DELETE FROM master_category_type');
    }
}
```

##3. TableとEntity作成

それぞれのTableクラスとEntityクラスを生成

```
$ bin/cake bake model categories
$ bin/cake bake model master_category_types
```

それぞれ中身は*bake*時から修正しています。

```php:CategoriesTable
class CategoriesTable extends Table
{
    public function initialize(array $config)
    {
        parent::initialize($config);

        $this->setTable('categories');
        $this->setDisplayField('name');
        $this->setPrimaryKey('id');

        $this->belongsTo('Users', [
            'foreignKey' => 'user_id',
            'joinType' => 'INNER'
        ]);
        $this->belongsTo('MasterCategoryTypes', [
            'foreignKey' => 'type_id',
            'joinType' => 'INNER'
        ]);
    }

    public function validationDefault(Validator $validator)
    {
        return $validator;
    }
}
```

```php:CategoryEntity
<?php
namespace App\Model\Entity;

use Cake\ORM\Entity;

class Category extends Entity
{
    protected $_accessible = [
        'name' => true,
        'user_id' => true,
        'type_id' => true,
    ];
}
```

```php:MasterCategoryTypesTable
<?php
namespace App\Model\Table;

use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;

class MasterCategoryTypesTable extends Table
{
    public function initialize(array $config)
    {
        parent::initialize($config);

        $this->setTable('master_category_types');
        $this->setDisplayField('name');
        $this->setPrimaryKey('id');
    }

    public function validationDefault(Validator $validator)
    {
        return $validator;
    }
}
```

```php:MasterCategoryTypeEntity
<?php
namespace App\Model\Entity;

use Cake\ORM\Entity;

class MasterCategoryType extends Entity
{
    protected $_accessible = [
        'name' => true
    ];
}
```

##4. 対象テーブルにリレーションを張る
*CategoriesTable*に以下のリレーションを張りました。

```php
$this->belongsTo('Users', [
    'foreignKey' => 'user_id',
    'joinType' => 'INNER'
]);
$this->belongsTo('MasterCategoryTypes', [
    'foreignKey' => 'type_id',
    'joinType' => 'INNER'
]);
```


##5. Controller作成

*SampleController*を作成

```
$ bin/cake bake controller sample
```

##6. 対象テーブルから全データ取得

```php
<?php
namespace App\Controller;

use App\Controller\AppController;

class SampleController extends AppController
{
    public function initialize()
    {
        parent::initialize();
        $this->loadModel('Categories');
        $this->loadModel('MasterCategoryTypes');
    }

    public function index()
    {
        $categories = $this->Categories
            ->find()
            ->where(['user_id' => $this->Auth->user()['id']])
            ->all();
        $this->set(compact('categories'));

        $categoryTypes = $this->MasterCategoryTypes
            ->find()
            ->all();
        $this->set(compact('categoryTypes'));

        $this->render($this->request->action);
    }
}
```


##7. View作成

```
$ pwd
project/src/Template
$ mkdir Sample
$ cd Sample
$ touch index.ctp
```

##8. 取得データを表示

```php
<h1>欲しい物リスト</h1>
<div>
    <ul>
        <?php foreach ($categories as $category): ?>
            <li><?= $category->name ?></li>
        <?php endforeach; ?>
    </ul>
</div>
```


##9. フォームの作成
##10. 絞り込みボタン設置
##11. 絞り込みボタンが押されたらクエリパラメータをGETメソッドでリクエスト

```php
<h1>欲しい物リスト</h1>
<div>
    <?= $this->Form->create('', [
        'type' => 'get',
        'url' => [
            'controller' => 'sample',
            'action' => 'index'
        ]
    ]) ?>
    <?= $this->Form->radio('selected_type_id', $options, $attributes) ?>
    <?= $this->Form->button('絞り込む', [
        'type' => 'submit'
    ]) ?>
    <?= $this->Form->end() ?>
    <ul>
        <?php foreach ($categories as $category): ?>
            <li><?= $category->name ?></li>
        <?php endforeach; ?>
    </ul>
</div>
```

##12. クエリパラメータを元にテーブルからデータを取得する際に絞り込む

```php
    public function index()
    {
        $categories = $this->Categories
            ->find()
            ->where(['user_id' => $this->Auth->user()['id']]);
        if (!empty($this->request->query['selected_type_id']) && $this->request->query['selected_type_id'] != 0) {
            $categories->where([
                'type_id' => $this->request->query['selected_type_id']
            ]);
        }
        $categories->all();
        $this->set(compact('categories'));

        $categoryTypes = $this->MasterCategoryTypes
            ->find()
            ->all();
        $this->set(compact('categoryTypes'));

        $options[0] = [
            0 => '一覧'
        ];
        foreach ($categoryTypes as $categoryType) {
            $options[$categoryType->id] = [
                $categoryType->id => $categoryType->name
            ];
        }
        $this->set(compact('options'));

        $attributes = [
            'value' => 0
        ];
        if (!empty($this->request->query['selected_type_id'])) {
            $attributes['value'] = $this->request->query['selected_type_id'];
        }
        $this->set(compact('attributes'));

        $this->render($this->request->action);
    }
```

##13. 絞り込まれたデータを表示

>８. 取得データを表示

と同様のロジックの為割愛

##実際の挙動

![ezgif.com-video-to-gif.gif](https://qiita-image-store.s3.amazonaws.com/0/267738/1f653a07-2cb5-c129-e524-f18f9ca6201c.gif)


##まとめ

復習も兼ねて実装してみました。
テーブル作成からやると若干大変でしたが、理解を深められたので良かったです。

##おまけ

Twitterやってます！外部のエンジニアの方ともどんどん繋がりたいと考えていますので、是非フォローして頂ければと思います！[@Tatsuo96](https://twitter.com/Tatsuo96)
ブログ始めました！
https://note.mu/tatsuo_iriyama