DB(テーブル)の構造
今回使用するDBをマイグレーションファイルで作成します。
config/Migrations/00000000000000_CreateProjects.php
<?php declare(strict_types=1); use Migrations\AbstractMigration; class CreateProjects extends AbstractMigration { public function change() { $table = $this->table('projects'); $table->addColumn('title', 'string', [ 'limit' => 150, 'null' => false, ]) ->addColumn('description', 'text', [ 'limit' => 255, ]) ->addColumn('created', 'datetime') ->addColumn('modified', 'datetime') ->create(); $table = $this->table('project_details'); $table->addColumn('title', 'string', [ 'limit' => 150, 'null' => false, ]) ->addColumn('description', 'text', [ 'limit' => 255, ]) ->addColumn('project_id', 'integer', [ 'null' => false ]) ->addColumn('created', 'datetime') ->addColumn('modified', 'datetime') ->addForeignKey('project_id', 'projects', 'id') ->create(); $table = $this->table('schedules'); $table ->addColumn('start_date', 'date') ->addColumn('end_date', 'date') ->addColumn('project_detail_id', 'integer', [ 'null' => false ]) ->addColumn('created', 'datetime') ->addColumn('modified', 'datetime') ->addForeignKey('project_detail_id', 'project_details', 'id') ->create(); } }
メインとなるProjectテーブルがありhasMany
でproject_detailsを持っている、project_detailsはhasOne
でschedulesを持っているという構造です。
基本となるファイルをbakeで作成しておきましょう。
$ bin/cake bake all projects $ bin/cake bake all project_details $ bin/cake bake all schedules
単一テーブルの更新
最初にアソシエーションのない場合の例です。
Projectだけを追加作成してみましょう。
src/Controller/ProjectsController.php
<?php declare(strict_types=1); namespace App\Controller; /** * Projects Controller * * @property \App\Model\Table\ProjectsTable $Projects * @method \App\Model\Entity\Project[]|\Cake\Datasource\ResultSetInterface paginate($object = null, array $settings = []) */ class ProjectsController extends AppController { /** @var \Cake\Http\Session */ private $session; public function initialize(): void { parent::initialize(); $this->session = $this->getRequest()->getSession(); } // ...(その他の処理) // 入力画面 public function add() { $project = $this->Projects->newEmptyEntity(); // セッションに入力情報があったら読み込む if ($this->session->check('inputs')) { $inputData = $this->session->consume('inputs'); $project = $this->Projects->patchEntity( $project, $inputData ); } // セッションにエラー情報があったらセット if ($this->session->check('errors')) { $project->setErrors($this->session->consume('errors')); } $this->set(compact('project')); } // 確認画面 public function addConfirm() { $this->request->allowMethod(['post']); $inputData = $this->request->getData(); $this->session->write('inputs', $inputData); $project = $this->Projects->newEmpty($inputData); // バリデーションエラーの場合はセッションに入れて戻る if ($project->hasErrors()) { $this->session->write('errors', $project->getErrors()); $this->Flash->error('バリデーションエラーです。'); return $this->redirect(['action' => 'add']); } $this->set(compact('project')); } // 完了画面 public function addComplete() { $this->request->allowMethod(['post']); // 入力セッションがない場合戻る if (!$this->session->check('inputs')) { $this->Flash->error('セッションがありません。'); return $this->redirect(['action' => 'add']); } $inputData = $this->session->consume('inputs'); $project = $this->Projects->newEntity($inputData); // バリデーションエラーの場合はセッションに入れて戻る if ($project->hasErrors()) { $this->session->write('errors', $project->getErrors()); $this->Flash->error('バリデーションエラーです。'); return $this->redirect(['action' => 'add']); } if ($this->Projects->save($project)) { $this->Flash->success('保存しました。'); return $this->redirect(['action' => 'index']); } $this->Flash->error('保存に失敗しました。'); return $this->redirect(['action' => 'add']); } }
入力画面と完了(登録)画面の間に確認画面が入ることによって入力情報をセッションで保持する必要があります。
またCakePHPではバリデーションエラーの情報はエンティティに入るので、確認画面ではエラー発生時getErrors
でセッションに保持し、入力画面ではエラーセッションがある場合はsetErrors
でエンティティに入れてます。
ビュー
入力画面のビューファイルから作っていきましょう。
複数のForm->control
を使用する場合はForm->controls
を使うとまとめて作成できます。
(タグ構造が固定されるので実際はあまり使うケースはないと思いますが)
templates/Projects/add.php
<h3>入力画面</h3> <?= $this->Form->create($project, ['url' => ['action' => 'addConfirm']]) ?> <fieldset> <?= $this->Form->controls(['title', 'description']) ?> </fieldset> <?= $this->Form->button('確認画面') ?> <?= $this->Form->end() ?>
確認画面は詳細画面とほぼ同じです。
違いはフッターに戻るボタンと登録ボタンを設置することです。
templates/Projects/add_confirm.php
<h3>確認画面</h3> <table> <tr> <th>タイトル</th> <td><?= h($project->title) ?></td> </tr> <tr> <th>概要</th> <td><?= $this->Text->autoParagraph(h($project->description)) ?></td> </tr> </table> <?= $this->Html->link('戻る', ['action' => 'add']) ?> <?= $this->Form->create($project, ['url' => ['action' => 'addComplete']]) ?> <?= $this->Form->button('登録') ?> <?= $this->Form->end() ?>
アソシエーションのある場合
次にアソシエーションがある場合の設定方法を見ていきましょう。
基本的には命名規則をしっかりと守っていればある程度CakePHPがやってくれます。
モデル
モデルのアソシエーションから確認してみましょう。
Projectsは複数のProjectDetailsを持つのでhasManyで繋ぎます。
src/Model/Table/ProjectsTable.php
$this->hasMany('ProjectDetails', [ 'foreignKey' => 'project_id', ]);
ProjectDetailsは一つのSchedulesを持つのでhasOneで繋ぎます。
src/Model/Table/ProjectDetailsTable.php
$this->hasOne('Schedules', [ 'foreignKey' => 'project_detail_id' ]);
上記の場合は命名規則の通りのカラム名になっているので、foreignKey
オプションは指定しなくても問題ありません。
エンティティの$_accessible
にアソシエーションのプロパティ(project_details,schedule)が設定されていることを確認してください。
src/Model/Entity/Project.php
$protected $_accessible = [ 'title' => true, 'description' => true, 'created' => true, 'modified' => true, 'project_details' => true, ];
src/Model/Entity/ProjectDetail.php
protected $_accessible = [ 'title' => true, 'description' => true, 'project_id' => true, 'created' => true, 'modified' => true, 'project' => true, 'schedule' => true, ];
これでProjectsからProjectDetailsとSchedulesまでを繋げることができました。
コントローラー
コントローラーはProjectsとProjectDetails(1階層)まではCakePHPが自動で保存してくれるので、特に設定は必要ないのですが、Schedulesまでの2階層のアソシエーションを保存したい場合はオプションを追加する必要があります。
次のようにnewEntity
にassociated
オプションを追加します。
$project = $this->Projects->newEntity( $inputData, ['associated' => ['ProjectDetails.Schedules']] );
patchEntity
の場合も同じように設定します。
$project = $this->Projects->patchEntity( $project, $inputData, ['associated' => ['ProjectDetails.Schedules']] );
ビュー
add.php
のcontrols
を次のように編集します。
パラメータのの記述はスネークケースで記述し、hasMany
は複数形、hasOne
は単数形で指定します。
なのでProjectDetails
はproject_details
になり、Schedules
はschedule
になります。
またhasMany
はインデックスを指定します。サンプルでは一つなので0だけですが、複数になる場合は0の部分が1,2,3と増えていくことになります。
templates/Projects/add.php
<?= $this->Form->controls([ 'title', 'description', 'project_details.0.title', 'project_details.0.description', 'project_details.0.schedule.start_date', 'project_details.0.schedule.end_date' ]) ?>
確認画面は次のようになります。
templates/Projects/add.php
<h3>確認画面</h3> <table> <tr> <th>タイトル</th> <td><?= h($project->title) ?></td> </tr> <tr> <th>概要</th> <td><?= $this->Text->autoParagraph(h($project->description)) ?></td> </tr> </table> <?php if (!empty($project->project_details)) : ?> <?php foreach ($project->project_details as $detail) : ?> <table> <tr> <th>詳細タイトル</th> <td><?= h($detail->title) ?></td> </tr> <tr> <th>詳細概要</th> <td><?= h($detail->description) ?></td> </tr> <tr> <th>開始日</th> <td><?= h($detail->schedule->start_date) ?></td> </tr> <tr> <th>終了日</th> <td><?= h($detail->schedule->end_date) ?></td> </tr> </table> <?php endforeach; ?> <?php endif; ?> <?= $this->Html->link('戻る', ['action' => 'add']) ?> <?= $this->Form->create($project, ['url' => ['action' => 'addComplete']]) ?> <?= $this->Form->button('登録') ?> <?= $this->Form->end() ?>
今回はhasMenyの項目は固定でしたが、入力項目を自由に増減させたいことがあると思います。
その場合JavaScriptを使用することになります。下記の記事で項目の増減方法を解説してるのよろしかったら!
jQueryで入力フォームの可変項目を増減する度にname属性を採番する