Ajaxを用いて動的セレクトボックスを作成する（CakePHP）

## 記事作成の背景
Ajaxって聞くけど、、、なんだ？っていうのと、個人的に動的セレクトボックスの
ヒントを残しておきたくて作成しました。
なんかパッとする記事が見つけられなかったので、これが少しでも困ってる方の助けになれば幸いです。

## 環境
CakePHP4 （最新）
jQuery
MySQL 8.0

### 全体のコード
とりあえずコード見てみるか・・・って方向けに最初に記載しておきます。
勉強会向けに色々書いてありますが、こちらの記事では必要箇所のみ抜粋して記述いたします。
https://github.com/Kurogoma939/cake_ajax_sample.git
### テストデータ
今回、検証環境はMAMPを用いて開発しました。
そのため、エクスポートしたSQLは以下の通りです。
保存場所　： `app/SQL/create_tabel`、`app/SQL/insert_table`

```sql
CREATE TABLE `marks` (
    `id` int(4) NOT NULL COMMENT 'ID',
    `name` varchar(10) NOT NULL COMMENT 'マーク名',
    `created` date NOT NULL COMMENT '作成日時'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='マークテーブル';

CREATE TABLE `trumps` (
     `id` int(100) NOT NULL COMMENT 'ID',
     `mark_id` int(4) NOT NULL COMMENT 'マークID',
     `number` int(100) NOT NULL COMMENT 'カードNo',
     `created` date NOT NULL COMMENT '登録日時',
     `name` varchar(50) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='トランプテーブル';

```

## 解説
### 全体の処理の流れ
![cakeajax.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/735195/e6ab1f7a-2b38-a88d-8d1e-3ff0008da0de.png)

##### ① セレクトボックスを選択
##### ② jQueryで①でイベント着火
##### ③ AjaxでデータのPOST+メソッド呼び出し
##### ④ 絞り込みクエリの実行＋レスポンス（JavaScriptに返す）
##### ⑤ ④の値を用いてセレクトボックスの値を作り直す

## コード
### template（HTML）ファイル（全体）
```template/Marks/index.php
<div class="marks index content">
    <?= $this->Form->create(null, ['url' =>['controller' => 'Marks', 'action' => 'buttonAction', 'id' => 'mark-form']]) ?>
        <div><h1>マークのリスト</h1></div>
        <?= $this->Form->label('marks','トランプのマーク') ?>
        <?=
            $this->Form->select('marks', $markList,[
                'class' => 'markList', 'id' => 'mark', 'empty' => true,
            ])
        ?>
        <div><h1>トランプのリスト</h1></div>
        <?= $this->Form->label('trump', 'トランプの番号とマーク') ?>
        <?= $this->Form->select('trump', $trumpList,[
            'class' => '', 'id' => 'trump', 'empty' => true,
        ])
        ?>
    <?= $this->Form->end() ?>
</div>
```
### JavaScript（Ajax）の処理
`$.ajax({...})`がJavaScript => PHPの処理で
`.done((data) => {})`がPHP => JavaScriptの処理になります。

```Main.js
$(document).ready(function () {
    $('#mark').on('change', function () {
        let markId = $(this).val(); // セレクトのvalueを受け取り
        // POST処理する場合、CSRFは手動で送信
        const _csrfToken = $('input[name="_csrfToken"]').val();

        $.ajax({
            type: 'POST', // GET
            datatype: 'json',
            // ↓ MarksControllerのchangeMarkメソッドを呼び出し
            url: "/marks/changeMark" // URLでメソッドを呼び出す URLはドメイン以降
            data: {
                markId: markId,
                _csrfToken: _csrfToken
            },
            beforeSend: function (xhr) {
                xhr.setRequestHeader("X-CSRF-Token", _csrfToken);
            },
        })
            .done((data) => {
                let objectData = JSON.parse(data); // JSON型をObject型へ変換
                let trumpList = objectData.trumpList; // $response[]で指定した値

                // トランプのセレクト要素
                let trumpSelect = $('#trump');
                let selectOptions = '<option value="0">該当するカードがありません</option>';
                // 中身を一度全てリセット
                trumpSelect.children().remove();
                // データがあった場合
                if (data) {
                    selectOptions = '';
                    // foreach($trumpList as $key => $val) みたいな処理の実装
                    for (const key in trumpList) {
                        var val = trumpList[key];
                        selectOptions += `<option value = '${key}'> ${val} </option>`;
                    }
                }
                trumpSelect.append(selectOptions);
            })
            .fail((jqXHR, textStatus, errorThrown) => {
                // JavaScriptの例外処理
                console.log(errorThrown);
            });
        return false;
    })
});
```
### PHP（CakePHP側の処理）
```app/Controller/MarksController.php
<?php
declare(strict_types=1);

namespace App\Controller;

use Cake\ORM\TableRegistry;

/**
 * Marks Controller
 */
class MarksController extends AppController
{
    public function initialize(): void
    {
        parent::initialize();
        $this->Trumps = TableRegistry::getTableLocator()->get('Trumps');
    }
    /**
     * Index method
     */
    public function index()
    {
        /* マークのセレクトボックス用データ */
        $this->set('markList', $this->Marks->find('list',
            [
                'keyField' => 'id', # セレクトボックスのvalue（見えない部分：Postされるデータ）
                'valueField' => 'name', # セレクトボックスの見える部分
            ]
        )
            ->distinct('id')); # 重複排除できる。Groupより確実

        /* トランプのセレクトボックス用データ */
        $this->set('trumpList', $this->Trumps->find('list',
            [
                'keyField' => 'id', # セレクトボックスのvalue（見えない部分：Postされるデータ）
                'valueField' => 'name', # セレクトボックスの見える部分
            ]
        ));
    }


    public function changeMark()
    {
        // AjaxからPOSTされたデータの受け取り
        $request = $this->request->getData();
        $markId = $request['markId'];

        // トランプリストを、取得したmark_idで絞り込み
        $trumpList = $this->Trumps->find('list',
            [
                'keyField' => 'id', # セレクトボックスのvalue（見えない部分：Postされるデータ）
                'valueField' => 'name', # セレクトボックスの見える部分
            ]
        )
            ->where(['mark_id' => $markId])
            ->order(['id' => 'ASC']);

        // JavaScriptに返す値（key部分で参照する）
        $response = [
            'trumpList' => $trumpList
        ];
        // Json形式にして返す
        return $this->getResponse()->withStringBody(json_encode($response));
    }
}
```

## 動作確認
#### 親のセレクトボックスの中身
![sssss.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/735195/edae2fab-82f8-c573-87d6-fb70058025b6.png)

#### 子のセレクトボックスの中身
![casa.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/735195/cbde21f0-18cf-42dd-0db4-4e74f2c549ac.png)

#### 親のセレクトボックスを選択した状態（動的セレクトボックス実行後）
![sadasa.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/735195/32320d7b-ce89-9656-2a6d-211add58e47a.png)


以上です！


P.S.
最近のPHPのFWはしっかりと`declare(strict_types=1);`がデフォルトで付くんですね