cakephp3 ブログチュートリアル解説

#【目的】cakephp3のブログチュートリアルを使ってフレームワークの基本を抑える
参考：[cakephp3ブログチュートリアル（公式ドキュメント）](https://book.cakephp.org/3/ja/tutorials-and-examples/blog/blog.html)

###環境
PC：Mac
DB：mysql
ローカル環境：MAMP

#第1章（インストールからMVCの説明と一覧ページと詳細ページ作成まで）の目次
・cakephpインストール
・DBとテーブルを作成する（今回はarticlesテーブル作成）
・config/app_local.phpでDBとの紐付する
・モデルファイルをsrc/Model/Table/articles.phpとして作成する
・コントローラーファイルをsrc/Controller/ArticlesController.phpとして作成する
　→コントローラーにはアクションの記述をする。
　→アクションごとにURLが紐付く（hoge.jp/テーブル名/アクション名)
・ビューを作成する
　→ファイルはsrc/Template/直下にテーブル名でディレクトリを作成し、アクションごとにctpファイルを作る
   今回は```src/Template/Articles/index.ctp```
　　ctpファイルの中にヘルパーリンクを作成することで詳細ページへのリンクを作る事が可能
・ビューの詳細ページを作成する
　→コントローラーに追加でviewアクションを作成する
　→```src/Template/Articles/view.ctp```を作成し詳細ページを表示可能にする。

##cakephpのインストール
インストールは過去記事を参考に行ってください。
[cakephp4のインストール方法](https://qiita.com/H-Toshi/items/17e36b49caa8ceb743b7)

###抑えるべき点
テーブル名：テーブル名を使用して各ファイルを作成・操作していくので作成したテーブル名をしっかり抑えておく。

##DB作成

```php
# まず、articles テーブルを作成します
CREATE TABLE articles (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(50),
    body TEXT,
    created DATETIME DEFAULT NULL,
    modified DATETIME DEFAULT NULL
);

# それから、テスト用に記事をいくつか入れておきます
INSERT INTO articles (title,body,created)
    VALUES ('タイトル', 'これは、記事の本文です。', NOW());
INSERT INTO articles (title,body,created)
    VALUES ('またタイトル', 'そこに本文が続きます。', NOW());
INSERT INTO articles (title,body,created)
    VALUES ('タイトルの逆襲', 'こりゃ本当にわくわくする！うそ。', NOW());
```

上記テーブルを今回はmysqlで作成します。
phpmyadminにてSQL文を入力し作成します。
事前に```cake_blog```というDBを作成しそのDBにて上記SQLを実行→テーブルを作成します。

###作成したテーブル画面
![スクリーンショット 2021-04-28 10.59.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/670884/0ffd66ac-0366-917d-0c2d-7ce5631fd06a.png)

![スクリーンショット 2021-04-28 11.01.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/670884/562ae636-3916-5d3f-63d8-7e135926f519.png)


####※ここで作成したテーブル名がarticlesという点をしっかり抑えておく。


##DBの設定
テーブル作成後DBとcakeの紐付けをします。
公式ドキュメントには以下のように```config/app.php```を変更するように書いてありますが、ローカル環境では```config/app_local.php```に記述していきます。

【公式ドキュメント】
![スクリーンショット 2021-04-28 10.51.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/670884/2d29b335-8e63-bd92-6efb-ffcb76a72d90.png)

上記を参考に以下のように記述します。

**【app_local.php】**

```php
    'Datasources' => [
        'default' => [
            'className' => 'Cake\Database\Connection',
            'driver' => 'Cake\Database\Driver\Mysql',
            'persistent' => false,
            'host' => 'localhost',
            'username' => 'root',
            'password' => 'root',
            'database' => 'cake_blog',
            'encoding' => 'utf8',
            'timezone' => 'UTC',
        ],
    ],
```

DBの設定が完了すると**cakeのトップ画面のDatabaseの部分が緑になります**
![スクリーンショット 2021-04-28 11.09.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/670884/26ca4f9b-58b0-3297-1305-5b9d02f4342e.png)



###追加の設定
セキュリティソルトを変更します

【公式ドキュメント】
![スクリーンショット 2021-04-28 10.58.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/670884/d961a74f-7e78-513e-1503-11e3c9000c73.png)

上記を参考に任意のランダムな文字列を入力します

```php
    'Security' => [
        'salt' => 'randamunamoziretunisuruuuuuuuurandfadfafadfa',
    ],
```

##Articles モデルの作成
articlesテーブルを作成したので、ここでは```src/Model/Table/```の中に
**ArticlesTable.php**を作成します。
このファイルは後に作成する**ArticlesController.php**と紐付きます。
こうすることでこのファイルは自動的にDBの**articles**と紐付けがされます。

**【ArticlesTable.php】**

```php
namespace App\Model\Table;

use Cake\ORM\Table;

class ArticlesTable extends Table
{
    public function initialize(array $config)
    {
        $this->addBehavior('Timestamp');
    }
}
```

![スクリーンショット 2021-04-28 14.05.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/670884/24cb875a-7724-33b3-aa3b-f801b1f8832c.png)
↑公式より


##Articles コントローラーの作成

**src/Controller/**の中に**ArticlesController.php**を作成します。


**【ArticlesController.php】**

```php
// src/Controller/ArticlesController.php

namespace App\Controller;

class ArticlesController extends AppController
{

    public function index()
    {
        $articles = $this->Articles->find('all');
        $this->set(compact('articles'));
    }
}
```

ここではまずindexというactionを作成しています。
Controllerで設定したactionはデフォルトでは```www.example.com/articles/index```というURLでアクセス出来ます。
同様に、 foobar() という関数を定義すると、ユーザーは、```www.example.com/articles/foobar``` でアクセス出来ます。

URLのアクセスについては後述する**ルーティング**にて変更出来ます。
その際は```config/routes.php```を修正します。
```$this->set(compact('articles');```は上の行で設定している```$articles```を配列にしてviewへ渡す機能です。
compact()はもともとphpにある関数です。

参考：[compact()](https://www.php.net/manual/ja/function.compact.php)

###ポイント
コントローラーはモデルの記述によってDBとやりとり可能になったデータを呼び出してそれをactionを使用してviewへ渡す機能を担っています


##Articles ビューの作成
Viewファイルはコントローラーで作成したactionの内容をURLで表示させたい時にそのアクション名を使用して作成します。
今回のindexであれば作成場所は```src/Template/Articles/index.ctp```となります。

###ポイント
テーブルごとに```src/Template/```の直下にディレクトリを作成します（今回はArticles)。
さらにその作成したディレクトリの中にviewファイルをアクションごとに作っていきます。
仮に**ArticlesController.php**に**hoge**というアクションが作成されていた場合、そのactionに紐づけるviewファイルは
```src/Template/Articles/hoge.ctp```というファイルを作ることになります。

**【index.ctp】**

```php
<h1>ブログ記事</h1>
<table>
    <tr>
        <th>Id</th>
        <th>タイトル</th>
        <th>作成日時</th>
    </tr>
    <?php foreach ($articles as $article) : ?>
        <tr>
            <td><?= $article->id ?></td>
            <td>
                <?= $this->Html->link($article->title, ['action' => 'view', $article->id]) ?>
            </td>
            <td>
                <?= $article->created->format('y/m/d'); ?>
            </td>
        </tr>
    <?php endforeach; ?>
</table>
```
###コードの説明

 ```php
<?php foreach ($articles as $article) : ?>
```
上記でコントローラーから```$this->set(compact('articles');```で受け取った値を```foreach ($articles as $article)```で回して全件表示しています。

```php
 <?= $this->Html->link($article->title, ['action' => 'view', $article->id]) ?>
```
上記はヘルパーというcakeの機能を使用してidごとの詳細ページへのリンクを作成しています。
ここでは**link() メソッドが、指定されたタイトル（最初のパラメーター）と URL (二つ目のパラメーター) で HTML リンクを生成する**ということを行っています。
これにより```/articles/view/some\_id ```にこれから作るviewファイルを紐づけ詳細ページを作ることが可能です。
[ヘルパー](https://book.cakephp.org/3/ja/views/helpers.html)
<br>
<br>


これでコントローラーで指定したURL（```www.example.com/articles/index```)にアクセスするとindex.ctpで記述した画面が表示されるようになります。


【表示画面】
![スクリーンショット 2021-04-28 15.07.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/670884/e5593a5d-904d-0193-5ef1-5346ffd9a474.png)


##詳細ページ作成
上述のヘルパー機能によって生成したリンク先ページを作っていきます。
・コントローラーにviewアクションを追加
・リンク先ページviewを作成

**【ArticlesController.php】**


```php
// src/Controller/ArticlesController.php

namespace App\Controller;

class ArticlesController extends AppController
{

    public function index()
    {
        $articles = $this->Articles->find('all');
        $this->set(compact('articles'));
    }

    public function view($id = null)
    {
        $article = $this->Articles->get($id);
        $this->set(compact('article'));
    }
}
```
今回は全件取得ではなく1件なので```get($id)```を記述します。

新しいviewページ**view.ctp**を作成します

**【view.ctp】**

```php
<!-- File: src/Template/Articles/view.ctp -->
<h1><?= h($article->title) ?></h1>
<p><?= h($article->body) ?></p>
<p><small>Created:<?= $article->created->format('y/m/d') ?></small></p>
```
これでページ詳細ページにアクセス可能になります。

【表示された詳細ページ】
![スクリーンショット 2021-04-28 15.47.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/670884/b05c0794-190e-bad4-11cd-7a31a92634ff.png)


#第2章（記事の追加・編集・削除）
##要点
・ArticlesController.phpに「add(), edit(), delete()」アクションを追加
・各アクションへのリンクをArticles/index.ctp内に記述
　→
　　追加：```<?= $this->Html->link('記事追加',['action' => 'add']) ?>```
　　編集：```<?= $this->Html->link('編集', ['action' => 'edit', $article->id]) ?>```
　　削除：

```php          
  <?= $this->Form->postLink(
                '削除　',
                ['action' => 'delete', $article->id],
                ['confirm' => '本当に削除しますか?'])
            ?>
```
・追加・編集用のビューを作成（add.ctp, edit.ctp)
・追加や編集などのフォームで使用するバリデーションをモデル（```src/Model/ArticlesTable.php)の既存クラスにバリデーション用のアクションを追記し作成する。




##記事の追加

```src/Controller/ArticlesController.php```のクラス内にaddアクションを追記する

```php
    public function add()
    {
        $article = $this->Articles->newEntity();
        if ($this->request->is('post')) {
            $article = $this->Articles->patchEntity($article, $this->request->getData());
            if ($this->Articles->save($article)) {
                $this->Flash->success(__('記事を保存する'));
                return $this->redirect(['action' => 'index']);
                $this->Flash->error(__('記事の追加が出来ません。'));
            }
        }
        $this->set('article', $article);
    }
```

###add() アクション説明
・リクエストの HTTP メソッドが POST なら、Articles モデルを使ってデータの保存を試みます。 何らかの理由で保存できなかった場合には、単にビューを表示
・ユーザーがフォームを使ってデータを POST した場合、その情報は、 ```$this->request->getData()```の中に入ってきます。
・FlashComponent の success() および error() メソッドを使って セッション変数にメッセージをセット。これらのメソッドは PHP の マジックメソッド を利用→レイアウトでは```<?= $this->Flash->render() ?>``` を用いてメッセージを表示
・``` ['action' => 'index']``` パラメーターは ```/articles```、つまり articles コントローラーの index アクションを表す URL に移動する設定をしている。
・```save()``` メソッドを呼ぶと、バリデーションエラーがチェックされ、もしエラーがある場合には 保存動作を中止します。



##記事追加ページの作成とバリデーションについて
![スクリーンショット 2021-04-28 16.26.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/670884/c5cd4f20-175b-08a1-5578-9e80db32ff7d.png)

↑公式ドキュメントより

cakephpではバリデーションを使用する為にビューの中でcakephpのフォームを利用するようにと言っている。
上記を念頭にadd.ctpファイルをTemplate/Articles/直下に作成します。



```php
<h1>記事追加</h1>
<?php 
 echo $this->Form->create($article);
 echo $this->Form->control('title');
 echo $this->Form->control('body');
 echo $this->Form->button(__('Save Article'));
 echo $this->Form->end();
 ?>
```

URLで~/articles/addにアクセスすると

![スクリーンショット 2021-04-28 16.25.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/670884/e810aaf2-9e96-4399-5b15-6f44155fc6c7.png)

上記画面が表示されるようになる


##一覧ページから記事追加へのリンクの作成方法
index.ctpから上記add.ctpへ遷移するよう、以下のリンクをindex.ctpに記述する
```<?= $this->Html->link('Add Article', ['action' => 'add']) ?>```


index.ctp


```php
<h1>ブログ記事</h1>
<?= $this->Html->link('記事追加',['action' => 'add']) ?>⬅︎この記述追加
<table>
    <tr>
        <th>Id</th>
        <th>タイトル</th>
        <th>作成日時</th>
    </tr>

~以下略~
```

![スクリーンショット 2021-04-28 16.35.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/670884/da2b57dc-b70f-ea5a-e671-7413e9ed7d10.png)


記事追加のリンクが追加されている事が確認出来る

##バリデーション作成方法
バリデーションのルールは、モデルの中で定義することができます

ArticlesTable.phpの既存クラス内に```validationDefault()```というアクションを追加します。

**【ArticlesTable.php】に以下を追記**


```php
use Cake\Validation\Validator; //このクラスの読み取り追加

//以下のメソッドを追加
 public function validationDefault(Validator $validator)
    {
        $validator
            // ->notEmpty('title') //コンソールで「'notEmpty' is deprecated.」とnotEmptyは非推奨ですと表示されるのでnotBlankを使用
            ->notBlank('title')
            ->requirePresence('title')
            ->notBlank('body')
            ->requirePresence('body');

        return $validator;
    }
```
コメントになっているnotEmpty()はもともとcakeのドキュメントに記載されているものですが、こちらは現在非推奨のようなので（ターミナルに注意が表示されます）、notBlank()を使用している。

validationDefault() メソッドを使って save() メソッドが呼ばれた時に、 どうやってバリデートするかを CakePHP に教えます。ここでは、本文とタイトルのフィールドが、 空ではいけない、そして作成及び編集の際にどちらも必要であるということを設定しています。 CakePHP のバリデーションエンジンは強力で、 組み込みのルールがいろいろあります (クレジットカード番号、メールアドレスなど）。 また柔軟に、独自ルールを作って設定することもできます。この設定に関する詳細は、 バリデーション を参照してください。


##投稿記事の編集
コントローラーにedit()アクションを追加します。


**【ArticlesController.php】の既存クラス内に以下を追記**

```php
    public function edit($id = null)
    {
        $article = $this->Articles->get($id);
        if($this->request->is(['post','put'])){
            $this->Articles->patchEntity($article, $this->request->getData());
            if($this->Articles->save($article)){
                $this->Flash->success(__('記事が更新されました'));
                return $this->redirect(['action' =>'index']);//ここの記述で保存実行時にindexページへ戻っている
            }
            $this->Flash->error(__('更新出来ませんでした。'));
        }
        $this->set('article', $article);
    }
```

このアクションではまず、ユーザーが実在するレコードにアクセスしようとしていることを確認します。 もし $id パラメーターが渡されてないか、ポストが存在しない場合、 NotFoundException を送出して CakePHP の ErrorHandler に処理を委ねます。

次に、リクエストが POST か PUT であるかをチェックします。もしリクエストが POST か PUT なら、 patchEntity() メソッドを用いてPOST データを記事エンティティーに更新します。 最終的にテーブルオブジェクトを用いて、エンティティーを保存したり、退けてバリデーションエラーを表示したりします。


次に編集用のビューファイルを作成します


**【src/Template/Articles/edit.ctp】**

```php
<h1>記事の編集</h1>
<?php 
    echo $this->Form->create($article);
    echo $this->Form->control('title');
    echo $this->Form->control('body',['rows' =>'3']);
    echo $this->Form->button(__('記事保存'));
    echo $this->Form->end();
?>
```
![スクリーンショット 2021-04-28 17.02.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/670884/d623536b-ffe2-4584-1028-002c1aa8de9e.png)



作成される画面（hoge.jp/articles/edit)
![スクリーンショット 2021-04-28 16.59.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/670884/dac219ab-4615-3480-d4c2-e94744bb5631.png)

-------------
###ポイント(追加と編集の違い）
コントローラーでアクションを作成する際に

add
```$article = $this->Articles->newEntity();```

edit
```$article = $this->Articles->get($id);```

と変数articleに入れている値が異なっている。
これによって新たにエンティティを作るか既存のものを取得するかが変わる。

-----------------------------------------------------

あとはindex.ctpにeditへのリンクを作る

```php
<td>
  <?= $this->Html->link('編集', ['action' => 'edit', $article->id]) ?>
</td>
```
上記を追加すればOK。

##投稿記事の削除

**【ArticlesTable.php】に以下のアクションを追加**



```php
    public function delete($id)
    {
        $this->request->allowMethod(['post', 'delete']);
        $article = $this->Articles->get($id);
        if($this->Articles->delete($article)){
            $this->Flash->success(__('id: {0} の記事が削除されました。', h($id)));
            return $this->redirect(['action' => 'index']);
        }
    }
```

![スクリーンショット 2021-04-28 17.11.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/670884/d626934e-8ec6-d75d-5b12-bc14f0f7135f.png)

###ポイント
deleteはロジックを実行してリダイレクトする為、アクションに対してビューがありません。
こういうパターンがあることを抑える。

最後に編集と同様削除のリンクをindex.ctpの
```<td></td>```内に作成します。

**【Articles/index.ctp】**

```php
<h1>ブログ記事</h1>
<?= $this->Html->link('記事追加',['action' => 'add']) ?>
<table>
    <tr>
        <th>Id</th>
        <th>タイトル</th>
        <th>作成日時</th>
        <th>編集</th>

    </tr>
    <?php foreach ($articles as $article) : ?>
        <tr>
            <td><?= $article->id ?></td>
            <td>
                <?= $this->Html->link($article->title, ['action' => 'view', $article->id]) ?>
            </td>
            <td>
                <?= $article->created->format('y/m/d'); ?>
            </td>
            <td>
            <?= $this->Form->postLink(
                '削除　',
                ['action' => 'delete', $article->id],
                ['confirm' => '本当に削除しますか?'])
            ?>
            <?= $this->Html->link('編集', ['action' => 'edit', $article->id]) ?>
        </td>
        </tr>
    <?php endforeach; ?>
</table>
```

【完成した画面】
![スクリーンショット 2021-04-28 17.17.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/670884/8d0ceaaf-327b-4ac9-91c7-43469eabaddf.png)

以上で基本的なCRUD操作は完了です。

#第3章その他機能
##ルーティング
###デフォルトでhoge.jp/articlesとなっている記事一覧のURLをhoge.jpに変更する方法。
**config/routes.php**を編集します。

デフォルトで
```$routes->connect('/', ['controller' => 'Pages', 'action' => 'display', 'home']);```
となっている上記を
```$routes->connect('/', ['controller' => 'Articles', 'action' => 'index']);```
へ変更します。
これで、「/」でリクエストしてきたユーザーを、ArticlesController の index() アクションに 接続させることができます。


さらに詳しい内容は公式のブログチュートリアル３に記載があります。
[ブログチュートリアル３](https://book.cakephp.org/3/ja/tutorials-and-examples/blog/part-three.html)


ひとまず以上です。