CakePHP Bake による雛型コードの作成

公開日:2018-05-30

1. 概要

本家のサイトを参考にして、bake で Scaffold コード(雛型コード) を生成し、試行錯誤をしながら CakePHP の動作確認を行います。

2. データベースの作成とconfigの設定

MySQLに接続して、データベースとテーブルを作成します。

コマンドプロンプト
mysql -u root -p
SQL
CREATE DATABASE db_my_app DEFAULT CHARACTER SET utf8mb4;

use db_my_app

CREATE TABLE articles (
    id INT AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    data TEXT,
    created DATETIME,
    modified DATETIME
);
絵文字に対応するため、エンコーディングは utf8mb4 を指定します。

次に、config/app.php の Datasources に、現在の環境の値を設定します。
今回は username、password、database を以下のようにしました。
app.php 抜粋
    'Datasources' => [
        'default' => [

                 :

            'username' => 'root',
            'password' => 'root',
            'database' => 'db_my_app',

                 :

3. Scaffoldコード(雛型コード) の生成

bake を使って、Scaffoldコードを作成します。
以下のコマンドを実行します。
コマンドプロンプト
cd C:\pleiades\xampp\htdocs\my_app\bin
cake bake all articles
エラーが発生しました。
エラー
Exception: Database driver Cake\Database\Driver\Mysql cannot be used due to a missing PHP extension or unmet dependency in [C:\pleiades\xampp\htdocs\my_app\vendor\cakephp\cakephp\src\Database\Connection.php, line 179]
2018-05-29 01:16:21 Error: [Cake\Database\Exception\MissingExtensionException] Database driver Cake\Database\Driver\Mysql cannot be used due to a missing PHP extension or unmet dependency in C:\pleiades\xampp\htdocs\my_app\vendor\cakephp\cakephp\src\Database\Connection.php on line 179
Exception Attributes: array (
  'driver' => 'Cake\\Database\\Driver\\Mysql',
)
php.iniを開いて、pdo_mysqlを有効にします。
php.ini
extension=pdo_mysql
もう1度実行します。
コマンドプロンプト
C:\pleiades\xampp\htdocs\my_app\bin>cake bake all articles
Bake All
---------------------------------------------------------------
One moment while associations are detected.

Baking table class for Articles...

Creating file C:\pleiades\xampp\htdocs\my_app\src\Model\Table\ArticlesTable.php
Wrote `C:\pleiades\xampp\htdocs\my_app\src\Model\Table\ArticlesTable.php`
Deleted `C:\pleiades\xampp\htdocs\my_app\src\Model\Table\empty`

Baking entity class for Article...

Creating file C:\pleiades\xampp\htdocs\my_app\src\Model\Entity\Article.php
Wrote `C:\pleiades\xampp\htdocs\my_app\src\Model\Entity\Article.php`
Deleted `C:\pleiades\xampp\htdocs\my_app\src\Model\Entity\empty`

Baking test fixture for Articles...

Creating file C:\pleiades\xampp\htdocs\my_app\tests\Fixture\ArticlesFixture.php
Wrote `C:\pleiades\xampp\htdocs\my_app\tests\Fixture\ArticlesFixture.php`
Deleted `C:\pleiades\xampp\htdocs\my_app\tests\Fixture\empty`
Bake is detecting possible fixtures...

Baking test case for App\Model\Table\ArticlesTable ...

Creating file C:\pleiades\xampp\htdocs\my_app\tests\TestCase\Model\Table\ArticlesTableTest.php
Wrote `C:\pleiades\xampp\htdocs\my_app\tests\TestCase\Model\Table\ArticlesTableTest.php`

Baking controller class for Articles...

Creating file C:\pleiades\xampp\htdocs\my_app\src\Controller\ArticlesController.php
Wrote `C:\pleiades\xampp\htdocs\my_app\src\Controller\ArticlesController.php`
Bake is detecting possible fixtures...

Baking test case for App\Controller\ArticlesController ...

Creating file C:\pleiades\xampp\htdocs\my_app\tests\TestCase\Controller\ArticlesControllerTest.php
Wrote `C:\pleiades\xampp\htdocs\my_app\tests\TestCase\Controller\ArticlesControllerTest.php`

Baking `index` view template file...

Creating file C:\pleiades\xampp\htdocs\my_app\src\Template\Articles\index.ctp
Wrote `C:\pleiades\xampp\htdocs\my_app\src\Template\Articles\index.ctp`

Baking `view` view template file...

Creating file C:\pleiades\xampp\htdocs\my_app\src\Template\Articles\view.ctp
Wrote `C:\pleiades\xampp\htdocs\my_app\src\Template\Articles\view.ctp`

Baking `add` view template file...

Creating file C:\pleiades\xampp\htdocs\my_app\src\Template\Articles\add.ctp
Wrote `C:\pleiades\xampp\htdocs\my_app\src\Template\Articles\add.ctp`

Baking `edit` view template file...

Creating file C:\pleiades\xampp\htdocs\my_app\src\Template\Articles\edit.ctp
Wrote `C:\pleiades\xampp\htdocs\my_app\src\Template\Articles\edit.ctp`
Bake All complete.
正常に実行できました。

tests 以外では、MVCの以下のファイルが作成されたようです。
フォルダ構成
C:/pleiades/xampp/htdocs/my_app/src
├─Controller
│  └─ArticlesController.php
├─Model
│  ├─Table
│  │  └─ ArticlesTable.php
│  └─Entity
│      └─ Article.php
└─Template
    └─Articles
        ├─index.ctp
        ├─add.ctp
        ├─edit.ctp
        └─view.ctp

4. 自動生成された画面の動作確認

http://localhost/my_app/articles/ にアクセスします。
画面が表示されました。


config/routes.php を見ても、controller の指定はありません。
どうやら、routes.php の最後に書かれた $routes->fallbacks(DashedRoute::class); で、
自動的に controller を呼び出すようです。
試しに http://localhost/my_app/test/ にアクセスしたところ、
src\Controller\TestController.php を作成してくださいと言うエラーメッセージが表示されました。

Articlesの画面を操作してみます。
左側の「New Article」をクリックします。
入力画面()になりました。
何か値を入力して SUBMIT をクリックします。
最初の画面に戻ると、1行データが追加されています。

次に、追加された行の View、Edit、Delete をクリックすると、
参照、編集、削除が行えることがわかります。
bakeを使うと、ここまで自動で作ってくれるようです。

5. URLとコントローラーの確認

http://localhost/my_app/articles/ の「New Article」をクリックすると、
URL は http://localhost/my_app/articles/add になりました。
リンクをクリックしなくても、URL を直接入力しても、画面遷移できます。

データを追加後、「View」をクリックすると、
URL は http://localhost/my_app/articles/view/1 になりました。

「Edit」をクリックすると、
URL は http://localhost/my_app/articles/edit/1 になりました。

src/Controller/ArticlesController.php を確認します。
中には、以下の5つのメソッドが定義されていました。
ArticlesController.php 抜粋
public function index()
public function view($id = null)
public function add()
public function edit($id = null)
public function delete($id = null)
URLとメソッドが関連していることがわかります。
試しに、http://localhost/my_app/articles/test にアクセスすると、
「Error: Create ArticlesController::test()」と言うエラーメッセージが表示されました。
ArticlesController.php に以下を追加します。
ソース
public function test($id = null) { }
もう1度 http://localhost/my_app/articles/test にアクセスします。
すると、View(src/Template/Articles/test.ctp) がないと言うエラーが発生します。
src/Template/Articles/test.ctp を作成します。
src/Template/Articles/test.ctp
Test
http://localhost/my_app/articles/test にアクセスすると、画面に Test と表示されました。

routes.php で controller、action(メソッド)、viewを指定していましたが、
省略すると、action と view は URL によって自動的に決まるようです。

また、ArticlesController.php に追加した test() を、
PagesController.php の display(...$path) のように修正して、
ソース
public function test(...$param) {
  var_dump($param);
}
http://localhost/my_app/articles/test/1/2/3 にアクセスすると、
$param に URL で指定した値が入ってきます。
var_dump($param)
array(3) {
  [0]=>
  string(1) "1"
  [1]=>
  string(1) "2"
  [2]=>
  string(1) "3"
}

6. view() の確認

ソース
    public function view($id = null)
    {
        $article = $this->Articles->get($id, [
            'contain' => []
        ]);

        $this->set('article', $article);
    }
$this->Articles->get() で、指定した ID のデータを DB を取得しています。
var_dump($article) で、取得した値を確認できます。

$this->set() は、View に $article の値を渡しています。
View では、1番目の引数の名前の変数($article) が使えるようになります。
自動生成された src/Template/Articles/view.ctp の先頭を見ると、
$article が使えるということがわかります。
$article のクラスが、\App\Model\Entity\Article と言うこともわかりました。

7. add() の確認

ソース
    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(__('The article has been saved.'));

                return $this->redirect(['action' => 'index']);
            }
            $this->Flash->error(__('The article could not be saved. Please, try again.'));
        }
        $this->set(compact('article'));
    }
最初の newEntity() で、空のエンティティ(\App\Model\Entity\Article)を生成しています。
$this->Articles は、\App\Model\Table\ArticlesTable です。

$this->request->is('post') で、Httpメソッドが POST かどうかをチェックしています。
初回アクセスは URL の直接入力(GET)でも問題ないため、初回はスルーされて、値のない空の入力画面が表示されます。
入力画面の SUBMIT を押すと、もう1度 add() が呼ばれ、その時に if の中に入るようです。

$this->request->getData() で、入力値を取得しています。
var_dump($this->request->getData());
array(2) {
  ["title"]=>
  string(2) "test1"
  ["data"]=>
  string(2) "data1"
}
var_dump($_POST);
array(3) {
  ["_method"]=>
  string(4) "POST"
  ["title"]=>
  string(2) "test1"
  ["data"]=>
  string(2) "data1"
}
$_POST には _method が入っていますが、$this->request->getData() には入っていません。
getData() では純粋な入力値のみが連想配列で取得できるようです。

次に、取得した入力値を $this->Articles->patchEntity() に渡して、$article に反映しています。
patchEntity() は vendor/cakephp/cakephp/src/ORM/Table.php に定義されていますが、
コメントによると、値を反映する際に入力チェックが行われるようです。

$this->Articles->save($article) で DB に保存します。

$this->Flash->success() で完了メッセージを設定しています。

そして最後に、$this->redirect(['action' => 'index']) で、トップページへリダイレクトしています。

7. edit() の確認

ソース
    public function edit($id = null)
    {
        $article = $this->Articles->get($id, [
            'contain' => []
        ]);
        if ($this->request->is(['patch', 'post', 'put'])) {
            $article = $this->Articles->patchEntity($article, $this->request->getData());
            if ($this->Articles->save($article)) {
                $this->Flash->success(__('The article has been saved.'));
                return $this->redirect(['action' => 'index']);
            }
            $this->Flash->error(__('The article could not be saved. Please, try again.'));
        }
        $this->set(compact('article'));
    }
基本的には add() と同じです。
違う点は、最初のエンティティを DB から取得してる点です。

8. delete() の確認

ソース
    public function delete($id = null)
    {
        $this->request->allowMethod(['post', 'delete']);
        $article = $this->Articles->get($id);
        if ($this->Articles->delete($article)) {
            $this->Flash->success(__('The article has been deleted.'));
        } else {
            $this->Flash->error(__('The article could not be deleted. Please, try again.'));
        }

        return $this->redirect(['action' => 'index']);
    }
$this->request->allowMethod() は、POST か DELETE だけ許可しそうです。
試しに URL を直接入力して、http://localhost/my_app/articles/delete/1 にアクセスしてみます。
MethodNotAllowedException エラーが発生しました。

次に get() でデータを取得しています。
そして、それを delete() に渡すと、DBから削除されるようです。

9. DB操作

以下のソースを ArticlesController.php に追加します。
ソース
    public function test() {
        
        //空データの作成
        $article = $this->Articles->newEntity();
        
        //データの割当
        $data = ["title" => "aaa", "data" => "bbb"];
        $article = $this->Articles->patchEntity($article, $data);
        
        //データの追加
        $this->Articles->save($article);
        
        //追加されたIDの取得
        $id = $article->id;
        
        //データの取得
        $article = $this->Articles->get($id);
        
        //データの確認
        var_dump($article->title);
        var_dump($article->data);
        
        //データの割当
        $data = ["title" => "AAA", "data" => "BBB"];
        $article = $this->Articles->patchEntity($article, $data);
        
        //データの更新
        $this->Articles->save($article);
        
        //データの取得
        $article = $this->Articles->get($id);
        
        //データの確認
        var_dump($article->title);
        var_dump($article->data);
        
        //データの削除
        $this->Articles->delete($article);
        
        //データの取得
        try {
          $article = $this->Articles->get($id);
        } catch (\Exception $e) {
          print("Error!!");
        }
コメントに書いてある通りですが、データの追加、更新、削除を行っています。
最後に、削除したデータを取得して、データの取得に失敗してエラーが発生します。

http://localhost/my_app/articles/test にアクセスすると、
test() が実行されて以下のように出力されます。
ソース
string(3) "aaa"
string(3) "bbb"
string(3) "AAA"
string(3) "BBB"
Error!!

10. バリデーション(入力チェック)

1. patchEntity() の確認

patchEntity() でデータを反映させる時にバリデーションが行われるため、
チェックに引っかかった場合の動作を確認します。

先ほど追加した test() を全てコメントにして、
以下のソースを ArticlesController.php に追加します。
ソース
    public function test() {
      //空データの作成
      $article1 = $this->Articles->newEntity();
      
      //データの割当 1回目(正常)
      $data = ["title" => "aaa", "data" => "bbb"];
      $article2 = $this->Articles->patchEntity($article1, $data);
      
      //データの割当 2回目(異常)
      $data = ["title" => "", "data" => "ccc"];
      $article3 = $this->Articles->patchEntity($article2, $data);
      
      var_dump($article1);
      if ($article1 === $article3) {
        print("同じオブジェクトです。\n");
      }
      
      //バリデーションエラー
      var_dump($article3->errors());
      var_dump($article3->errors()["title"]["_empty"]);
    }
src/Model/Table/ArticlesTable.php の validationDefault() で、バリデーションの定義をしています。
notEmpty('title') と書いてあるため、上記ソースでは、title を空で設定しています。

http://localhost/my_app/articles/test にアクセスします。
以下のように出力されました。
結果
object(App\Model\Entity\Article)#100 (10) {
  ["title"]=>
  string(3) "aaa"
  ["data"]=>
  string(3) "ccc"
  ["[new]"]=>
  bool(true)
  ["[accessible]"]=>
  array(4) {
    ["title"]=>
    bool(true)
    ["data"]=>
    bool(true)
    ["created"]=>
    bool(true)
    ["modified"]=>
    bool(true)
  }
  ["[dirty]"]=>
  array(2) {
    ["title"]=>
    bool(true)
    ["data"]=>
    bool(true)
  }
  ["[original]"]=>
  array(1) {
    ["data"]=>
    string(3) "bbb"
  }
  ["[virtual]"]=>
  array(0) {
  }
  ["[errors]"]=>
  array(1) {
    ["title"]=>
    array(1) {
      ["_empty"]=>
      string(31) "This field cannot be left empty"
    }
  }
  ["[invalid]"]=>
  array(1) {
    ["title"]=>
    string(0) ""
  }
  ["[repository]"]=>
  string(8) "Articles"
}
同じオブジェクトです。
array(1) {
  ["title"]=>
  array(1) {
    ["_empty"]=>
    string(31) "This field cannot be left empty"
  }
}
string(31) "This field cannot be left empty"
1回目の patchEntity() で title に「aaa」、data に「bbb」を設定して、
2回目の patchEntity() で title を空、data に「ccc」を設定していますが、
最終的な値は、title は「aaa」、data は「ccc」となっています。
バリデーションが働いて、data は更新されていますが、title は空になっていません。
バリデーションで引っかかったデータは反映されないようです。
また、エラーの有無とメッセージは、$article3->errors() で取得できます。

2. 画面操作による確認

画面を操作して入力チェックの動作を確認します。
http://localhost/my_app/articles/add にアクセスして、
title に値を入れずに SUBMIT をクリックします。
すると、title欄に「このフィールドを入力してください。」と言うメッセージが表示されます。
これは、HTML のタグによって制御された入力必須エラーです。
バリデーションの定義により、出力される HTML が変わるようです。

次に、src/Model/Table/ArticlesTable.php の
「->maxLength('title', 255)」を、
「->maxLength('title', 10)」に書き換えて、
入力画面の title に長い文字列を入力してみます。

すると、画面上部に、
「The article could not be saved. Please, try again.」と言うエラーメッセージが表示されました。
title欄の下にも、エラーメッセージが表示されています。
先ほどと異なり、これはサーバからの応答になります。