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

公開日:2018-05-30 更新日:2019-05-13

1. 概要

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

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

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

コマンドプロンプト
mysql -u root -p
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を有効にします。
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欄の下にも、エラーメッセージが表示されています。
先ほどと異なり、これはサーバからの応答になります。