【CakePHP3】データベースと連携したフォームを作成する方法




ウェブアプリケーションを作っていると、データベースと連携したフォームを作りたいと思うことがあるでしょう。そこでこの記事では、あらかじめ設計したテーブルをフォームとしてブラウザに出す方法を紹介したいと思います。

MVCモデルの大まかな役割分担について、今一度確認したい方はこちらの記事もご覧ください。

【CakePHP3】処理の流れを把握しよう!MVCモデルを解説します

2019年7月21日

環境

  • OS:CentOS
  • PHP:7.1
  • CakePHP:3.8.1
  • Webサーバ:Apache

今回使うデータベース

今回は、2つのテーブルを用意しました。

【articles】

名前データ型その他1その他2
idint(10)AUTO_INCREMENTPRIMARY_KEY
user_idint(10)usersテーブルの外部キー
titlevarchar(64)
bodyvarchar(20000)

【users】

名前データ型その他1その他2
idint(10)AUTO_INCREMENTPRIMARY_KEY
namevarchar(32)

さて、CakePHPの規約から「テーブル名(単数)_id」と名付けたカラムは指定したテーブルの外部キーとして設定されます。つまり、articlesテーブルのuser_idはusersテーブルのidと紐付いています。

そして、articlesテーブルのレコードはusersテーブルに対してbelongsToの関係にあり、逆にusersテーブルはarticlesテーブルに対してhasManyの関係を持ちます。アソシエーションについては別記事でまとめてあるので、気になる方はぜひご覧ください。

【CakePHP3】アソシエーションについて解説します

2019年7月25日
アソシエーション

Model、View、Controllerをつくる

さて、これからModelとViewとControllerをつくります。今回はarticles、つまり記事をウェブからフォーム入力してデータをデータベースに保存出来るようにしていきます。実はcakePHPでは下記のコマンドをターミナルで実行するだけでフォームを自動作成することが出来ます。

$php bin/cake.php bake all articles
$php bin/cake.php bake all users

まずターミナルでアプリケーションのディレクトリに入ります。srcディレクトリやbinディレクトリ、configディレクトリなどがあるディレクトリです。PHPにパスが通っている場合は、頭の「php」は省略してbin/cake.php~からでも通ります。

これによって作られたものが以下のソースになります。ひとつひとつ解説していきます。

Model

ArticlesTable.php

<?php
namespace App\Model\Table;

use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;

class ArticlesTable extends Table
{
    /**
     * Initialize method
     *
     * @param array $config The configuration for the Table.
     * @return void
     */
    public function initialize(array $config)
    {
        parent::initialize($config);

        $this->setTable('articles');
        $this->setDisplayField('title');
        $this->setPrimaryKey('id');

        $this->belongsTo('Users', [
            'foreignKey' => 'user_id',
            'joinType' => 'INNER'
        ]);
    }

    public function validationDefault(Validator $validator)
    {
        $validator
            ->integer('id')
            ->allowEmptyString('id', null, 'create');

        $validator
            ->scalar('title')
            ->maxLength('title', 64)
            ->requirePresence('title', 'create')
            ->notEmptyString('title');

        $validator
            ->scalar('body')
            ->maxLength('body', 20000)
            ->requirePresence('body', 'create')
            ->notEmptyString('body');

        return $validator;
    }

    public function buildRules(RulesChecker $rules)
    {
        $rules->add($rules->existsIn(['user_id'], 'Users'));

        return $rules;
    }
}

25行目では、usersテーブルに対するアソシエーションが定義されています。また、31行目からはじまるvalidationDefaultでは、カラムごとの制約条件を定義しています。52行目以降では、articlesテーブルの条件を定義しています。

この例での条件は、articlesテーブルの’user_id’は、usersテーブルのidとして登録されているものでなければならない、という制約です。例えば、usersテーブルに{id: 1,name: Taro}、{id: 2, name: Hanako}しか登録していない場合、articlesテーブルでuser_id=3と入れてしまうと、存在しない人が記事を書いたことになり整合性が取れなくなるのでNGというわけです。

UsersTable.php

<?php
namespace App\Model\Table;

use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;

class UsersTable extends Table
{
    public function initialize(array $config)
    {
        parent::initialize($config);

        $this->setTable('users');
        $this->setDisplayField('name');
        $this->setPrimaryKey('id');

        $this->hasMany('Articles', [
            'foreignKey' => 'user_id'
        ]);
    }

    public function validationDefault(Validator $validator)
    {
        $validator
            ->integer('id')
            ->allowEmptyString('id', null, 'create');

        $validator
            ->scalar('name')
            ->maxLength('name', 32)
            ->requirePresence('name', 'create')
            ->notEmptyString('name');

        return $validator;
    }
}

こちらも上記のArticlesTable.phpとほぼ同じなので省略しますが、belongsToがhasManyになっていますね。

Controller

ArticlesController.php(一部抜粋)

    public function add()
    {
        $article = $this->Articles->newEntity(); #articlesテーブルのエンティティを作成
        if ($this->request->is('post')) { #もしPOSTが行われたら
            $article = $this->Articles->patchEntity($article, $this->request->getData());
            if ($this->Articles->save($article)) {#DBを追加・更新する
                $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.'));
        }
        $users = $this->Articles->Users->find('list', ['limit' => 200]);#Usersのデータを取ってくる
        $this->set(compact('article', 'users'));#Viewに変数として送る
    }

さて、Controllerの中に、上記のadd()メソッドが記述されていると思います。このメソッドの流れとしては、まずarticlesテーブルのエンティティを作成します。そして、つくったエンティティの中に、ブラウザからPOSTされたデータを入れて(patchEntity)、保存(save)します。

また、一番注目すべきは13行目です。articlesテーブルでは記事の作成者はusers_id、つまり1や2などの数字で管理されています。しかし、ブラウザから入力をする際に何番が誰に対応しているか確認しながら入れるのは面倒ですよね。1、2、と数字を入力するのではなく、Taro、Hanako、と名前で入力をしたい。13行目は、そのための準備です。

usersテーブルからデータを引っ張ってくることでViewで使う準備をしています。

View

add.ctp

<nav class="large-3 medium-4 columns" id="actions-sidebar">
    <ul class="side-nav">
        <li class="heading"><?= __('Actions') ?></li>
        <li><?= $this->Html->link(__('List Articles'), ['action' => 'index']) ?></li>
        <li><?= $this->Html->link(__('List Users'), ['controller' => 'Users', 'action' => 'index']) ?></li>
        <li><?= $this->Html->link(__('New User'), ['controller' => 'Users', 'action' => 'add']) ?></li>
    </ul>
</nav>
<div class="articles form large-9 medium-8 columns content">
    <?= $this->Form->create($article) ?>#フォームを作成する
    <fieldset>
        <legend><?= __('Add Article') ?></legend>#凡例をつける、無くても良し
        <?php
            echo $this->Form->control('user_id', ['options' => $users]);
            echo $this->Form->control('title');
            echo $this->Form->control('body');
        ?>
    </fieldset>
    <?= $this->Form->button(__('Submit')) ?>
    <?= $this->Form->end() ?>
</div>

さて、ではViewでフォームを作っていきます。14〜16行目でフォームを出力していますが、14行目に注目です。ここではoptionsとして、先ほどControllerでViewに送った$usersを設定しています。

これを行うことによって、ブラウザではuser_id、つまり数字を選ぶのではなくname、つまりTaroやHanakoといった名前で選択を行うことが出来るようになります。

セレクトメニュー

そして、このフォームのソースコードを見てみると、

<option value="1">Taro</option><option value="2">Hanako</option>

このように、ブラウザ上で表示されているのは「Taro」や「Hanako」ですが、valueは1や2など、user_idそのものになっているので、データベースには数字のuser_idがそのまま入ることになります。このように、データベースのテーブルをアソシエーションで関連付けして、ControllerでViewに変数を送ることで、簡単にフォームをつくることができるのです。

しかも、cake.phpでbakeしたときにデフォルトで設定が行われます。

ちょっとだけフォームをカスタマイズ

#元
$this->Form->control('title');

#カスタマイズ
 $this->Form->control('title',[
              'class' => 'title',
              'label' => 'タイトル',
            ]);

また、control()に引数を渡すことでさまざまな設定を行うことが出来ます。機能はたくさんあるので、CakePHPの公式ドキュメントをご覧いただければと思いますが、上のようにフォームにClassをつけたり、タイトルを変えたり出来ます。

デフォルト設定だとフォームの入力項目名はデータベースのカラム名と同じtitleになってしまいますが、変更したい場合もあると思います。そんな時にタイトルを変えることで、下のようにユーザーが分かりやすい表記でフォームをつくることができます。

カスタマイズ

titleから「タイトル」に変わっていますね。

よく使う機能は、’type’だと思います。この’type’は、フォームの形を変更してくれます。プルダウンなのか、ラジオボタンなのか、といったことです。

「text」「textarea」「select」「radio」「checkbox」「password」「file」「date」「time」「hidden」が設定できます。

まとめ

cakePHPはフォームをとても簡単に作ってくれる機能(Form Helperという)が備わっており、手でハードコーディングをしなくても良いのでとても使いやすいです。

とはいえ、書き方に決まりがあるので使い慣れないうちはどのように使えば良いのか分からないので、少しづつ覚えていくのが良いと思います。