「PHPエンジニアにおくるCakePHP3 予習入門」を読み終えて

### 目的
初めてのPHPを勉強にて、こちらの著書でCakePHPに触れたので振り返りもかねて。
【2021/09/01】
転職先で古めのCakeを使うとのことなので3系でやったが予想以上に古く、実際使っていたのはCake2.xだった…。
###著者
辛島信芳

###対象読者（抜粋）
>・今後のために、あらかじめCakePHPを学習したいと思っている
>・スピーディーにPHP開発の出来るフレームワークに興味がある

~~KindleUnlimitedで無料だったので~~
私の境遇も上記に当てはまるので本書を選びました。

本書ではCakePHP3を対象としていますが、私の環境は最新を使いましたのでCakePHP4.2.7となっています。

<h2>第1章：CakePHPの導入～第2章：基本的なディレクトリ構成</h2>

特筆すべき内容はあまりないので省略。
強いて上げるなら`本書はすでにPHPの環境は整っている前提`という点くらい。
<h1>第3章：簡単チュートリアル</h1>
・MySQLサーバを使用するため準備が必要。私はXamppでMySQLを使用。
・config/app.phpをDBの情報に応じて修正する。

:::note warn
基本設定はapp.phpを使うが、ローカル開発の場合はapp_local.phpが優先されるため、環境に応じてconfig/app_local.phpも編集が必要。
:::

https://www.webopixel.net/cakephp/1612.html

・本書に従って、各テーブルを作成したら初めての「bake」を行う。
`「bake」とは作成したテーブルを元に自動でMVC(Model,View,Controller)を作成してくれるCakePHPのメインの機能`
アプリケーション開発をしている人はテンプレートエンジンだと思ってもらえればいいです。

以下のコマンドでテーブルに応じて自動作成してくれます。
```bin/cake bake all テーブル名```

:::note warn
bakeで再作成の場合はキャッシュ等が残ってしまい、想定外の挙動をする可能性があるので
「bin\cake cache clear_all」を行った後にbakeしてください。
:::


・Timezoneの設定追加

```php:config/app.php
// 'timezone' => 'UTC',
'timezone' => 'Asia/Tokyo',
```
```php:config/bootstrap.php
//date_default_timezone_ set(Configure::read('App.defaultTimezone'));
date_default_timezone_set('Asia/ Tokyo');
```
・mysql_tzinfo_to_sqlコマンドでタイムゾーン情報をMySQLに登録
コマンドでもできるようですが、私は以下でタイムゾーンを追加しました。

https://blog.s-giken.net/345.html

<h1>第4章：CRUD(Create,Read,Update,Delete)</h1>

この章で`src/Template/○○/xx.ctp`と記載があります。
CakePHP3とCakePHP4の差なのかわかりませんが、作成画面のテンプレートに関しては
CakePHP4では`templates/○○/xx.php`に格納されています。

この章では主にCRUDに関連する処理の説明がされてます。

<h4>Insert</h4>
新規データの作成はnewEntity()。その後、$this->コントローラー名->save(編集したオブジェクト)でインサート。
<h4>Read</h4>
get($id,....)で取得
主キーのカラム名がidであることを前提に自動生成されているので、 変えたい場合はsrc/Model/Table/〇〇Table.phpのinitialize内で setPrimaryKeyメソッドで指定。

```php:sample.php
$this->setPrimaryKey('id');//←ここを変更
```
<h4>Update</h4>
リードして$this->コントローラー名->save(編集したオブジェクト)
<h4>Delete</h4>
delete($削除したいオブジェクト)で削除

:::note info
どのメソッドに関してもControllerにて$this->request->allowMethod(['post','delete'])のように
許可するHTTPメソッドを設定する必要がある。
:::

以下は覚えておくと便利。

<summary><b>・$this->request->is('〇〇')でリクエスト判定を行える。</summary>

| タイプ名    |説明                 | 
|:-----------|-------------------: |
| get        |GETメソッドか判別|
| post       |POSTメソッドか判別する|
| ajax       |ajaxか判別する|

https://qiita.com/kazu56/items/58f18681a942c3f13bd1

<summary> <b>・$this->set()でデータをビューに渡す</summary>
ここで、compactメソッドを使うと変数をそのまま渡すことができる。

```php:sample.php
$msg ="test";
$this->set(compact('msg'));
```
<h1>第5章：データの関連付け</h1>
bakeを行えば自動で指定してくれるが、概念としてテーブル同士の繋がりについて、以下を理解しておく必要がある。

| アソシエーションの種類    |説明                 | 
|:-----------|-------------------: |
| hasOne|1:1の関係|
| hasMany|1:Nの関係|
| belongsTo|N:1の関係|
| belongsToMany|N:Nの関係|

このアソシエーションは`各テーブル名"+Table.phpのInitializeメソッド`に記載されている。

また、同様の個所に外部キー設定（`'foreignKey'=>〇〇`）やJoinの種類（`'joinType'=>'INNER'`）なども同様の個所に記載されているためテーブル間の繋がりをソースで確認できるのは便利。

buildRules()内で`$rules->add/$rules->existsIn`として、ルールを定義してます。

`$rules->add`でルールを追加
`$rules->existsIn`メソッドでの存在チェックとなります。

1.Aテーブルのレコード追加で関連するBテーブルのプルダウンを選択
2.MySQL側でBの該当レコード削除
3.画面でSUBMITボタン押下とすると、Bテーブルのプルダウンの下に「This value does not exist」とエラーメッセージが表示されます。

<h1>第6章：ユーザー機能の追加と改修</h1>
これまでの章は、bakeされたソースを見てきただけですが、この章では以下の改修を行います。

・カスタマイズValidation追加
・パスワードのハッシュ化
・ユーザー認証

<b>・カスタマイズのValidation追加

基本のValidationはbakeされた時点でテーブル定義に基づいて以下に作成されている。
`src/Validation/Validator.php`

Validationを行うタイミングについては、以下がある。
例としてallowEmptyStringであれば第三引数に指定することでValidateのタイミングを調整できる。

|種類|説明| 
|:-|-: |
| true|常に|
| false|必要ない or ではない|
| create|create実行時のバリデーションをする場合|
| update|update実行時のバリデーションをする場合|

プロバイダーをもとに、バリデーションルールの関数が提供されます。プロバイダーのデフォルトはValidation\Validationのクラスにマッピングされます。

:::note warn
・デフォルト以外を使う場合該当するproviderキーを指定する必要があります。
:::
`'table'`とすれば`src/ORM/Table.php`になります。

```
['rule'=>'validateUnique','provider'=>'table']
```

とすると`src/ORM/Table.php`の`validateUnique`を使うという意味になります。

<b>・パスワードのハッシュ化</b>

setterを定義し、ハッシュ化したものを返す。実際の処理は以下。


```php:
protected function _setPassword($password)
{
  //・・・省略・・・
  $hasher = new DefaultPasswordHasher();
  return $hasher->hash($password);
}
```
ハッシュ化してDBに格納すると、呼び出し側のPassword欄にはハッシュ化されたものが出てきます。その状態で、そのままSUBMITすると、さらにハッシュ化してDBに格納されてしまうのでPassword欄である`echo$this->Form->control('password');`の部分を削除します。

<b>・ユーザー認証</b>
AppController.phpのinitialize関数内にAuthコンポーネントのロードを追記します。

```php
$this->loadComponent('Auth', 認証設定の配列); 
```

:::note warn
※Authコンポーネントはデフォルトではusersテーブル、usernameとpasswordによって認証してます。
変更したい場合は、Authコンポーネントのロード時の認証設定の変更を追記します。
:::

<b>例)users（username、password）→members（membername、memberpass）の場合

```php:src/Controller/AppController.php

public function initialize()
{
  //…省略…
  $this->loadComponent('Auth', ['authenticate' => ['Form' => [
           'userModel' => 'Members',//ここ
           'fields' => ['username' => 'membername', 'password' => 'memberpass']]],//ここ
           'loginAction' => '/members/login',
           'logoutRedirect' => ['controller' => 'Members', 'action' => 'login'],
           'loginRedirect' => ['controller' => 'Messages',' action' => 'index']
         ]);
}
```

<b> 認可チェック</b>
ControllerAuthorizeを使用。認証設定にて'authorize' => 'Controller'を追記します。

```php:src/Controller/AppController.php
$this->loadComponent('Auth',[
                            'authorize' =>'controller',//※1
                            'authenticate'=>['Form'=>['finder=>auth']],//※2
                            'loginRedirect' => ['controller' => 'Messages', 'action' => 'index']]);
```
※1:ControllerAuthorizeの指定、つまり'authorize' => 'Controller'を追記したら、コールバックでisAuthorized関数を呼ぶ必要あり。
指定のない場合は以下のExceptionが発生する。
>does not implement an isAuthorized() method.
>Cake\Core\Exception\CakeException

※2:ファインダーメソッドの関数名は、AppsController.authenticate内のfinder指定に準じ、'finder' => 'jyouken'としたらファインダーメソッドはfindJyouken関数となる。

:::note warn
※この章でbeforeFilter使用の際にはuse Cake\Event\Event;を記載とあるが、Cake4ではuse Cake\Event\EventInterface;を使用する。
:::

<h1>第7章：メッセージ機能の改修</h1>
特筆すべき内容はなかったが、ConnectionManagerを使用してデータ取得のサンプルがあるが、コネクションのケア等が記載されていない。これだけでいいものなのか現時点ではわからないが楽というか雑というか心配になりました。

<h1>第8章：レイアウト</h1>
共通テンプレートの反映について記載されている。

AppControllerの initialize() にsetLayoutをします。
parent::initialize(); の次の行あたりに追記します。

```php:src/Controller/AppController.php
public function initialize()
{
    parent:: initialize();
    $this->viewBuilder()->setLayout('basic_layout');//templates/baic_layout.phpが共通画面の場合
//…省略…
}
```

<h1>総評・感想</h1>
初めてのCakePHPでしたが、bakeで機能を一括作成してくれるおかげで割とスムーズに進行できた。
しかし、何よりコマンドが苦手なのでChromeOSで進行している本著ではいちいちWindowsに置き換えを検索して・・・
だったので少し時間がかかった。そもそもコマンドについて学ぶべきなのかもしれません。

各ディレクトリ構成についても粗方、役割を理解できたので後は実践あるのみかというところです。
実際に応用を効かせたサンプルの作成などを通して進めていく予定。