CakePHP3のFormクラスとカスタムバリデーションを使ってファイルアップロード機能を構築する
- 公開日
- カテゴリ:CakePHP
- タグ:PHP,validation,Form,CakePHP,3.5,FormClass,CustomValidation

CakePHP で Web アプリケーションを構築していると、直接モデルとは関係のない(データベースを絡めない)フォームとバリデーションを用いた機能一式が必要になる時があります。
テキストや選択での入力項目のあるものについては、ほぼほぼそれらをデータベースへ格納する場合が多いですが、ファイルの場合はどうでしょうか?アップロード情報そのものを格納する場合ももちろんありますが、ファイルをアップロードする際のバリデーションというのは実質的にデータベースに絡まないので、テーブルエンティティにてバリデーションを実装する事が難しくなります。(というか、倫理的に違うというか)
そこで今回は、CakePHP3 の「Form クラス」「カスタムバリデーション」を用いて、ファイルのアップロード機能を構築します。
Contents
開発環境
今回の開発環境は以下の通りです。
- Linux CentOS 7
- Apache 2.4
- MySQL 5.7
- PHP 7.1
- CakePHP 3.5
CakePHP のルートディレクトリを「cakephp/」とします。
コントローラ作成
まずは基本部分から。ベースとなるコントローラを作成します。 cakephp のルートディレクトリへ移動し、以下の bake コマンドを叩きます。
# CakePHP のルートディレクトリへ移動する
cd /path/to/cakephp
# bakek コマンドでコントローラを生成する
bin/cake bake controller UploadFiles
# 実行結果
[demo@localhost cakephp]# bin/cake bake controller UploadFiles
Baking controller class for UploadFiles...
Creating file /var/www/html/cakephp/src/Controller/UploadFilesController.php
Wrote `/var/www/html/cakephp/src/Controller/UploadFilesController.php`
Bake is detecting possible fixtures...
Baking test case for App\Controller\UploadFilesController ...
Creating file /var/www/html/cakephp/tests/TestCase/Controller/UploadFilesControllerTest.php
Wrote `/var/www/html/cakephp/tests/TestCase/Controller/UploadFilesControllerTest.php`
cakephp/src/Controller 配下に UploadFilesController.php が生成されます。
cakephp
├─ src
│ ├─ Controller
│ │ ├─ UploadFilesController.php
コントローラ内のソースですが、初期状態として以下とします。
<?php
namespace App\Controller;
use App\Controller\AppController;
class UploadFilesController extends AppController
{
public function index()
{
}
}
何もありませんが最後に実装していくので、ひとまずこれで OK です。
テンプレート作成
次に、テンプレートを作成します。
cakephp/src/Template 配下に UploadFiles ディレクトリを作成し、その中に index.ctp を作成します。
cakephp
├─ src
│ ├─ Template
│ │ ├─ UploadFiles
│ │ │ └─ index.ctp
テンプレートファイルは以下のように記述します。
- cakephp/src/Template/UploadFiles/index.ctp
-
<div class="uploadFiles index large-9 medium-8 columns content"> <h3><?= __('File Upload Form') ?></h3> <div class="form_wrap"> <?php echo $this->Form->create($fileform, ['type' => 'file', 'url' => ['controller' => 'UploadFiles', 'action' => 'index']]); echo $this->Form->control('upload_file', ['type' => 'file', 'label' => 'ファイル', 'required' => true,]); echo $this->Form->submit(); echo $this->Form->end(); ?> </div> </div>
HTML で定義しているクラス名はデフォルトのものを使っているので特に気にせずで OK です。一点だけ
<?php
echo $this->Form->create($fileform, ['type' => 'file', 'url' => ['controller' => 'UploadFiles', 'action' => 'index']]);
echo $this->Form->control('upload_file', ['type' => 'file', 'label' => 'ファイル', 'required' => true,]);
echo $this->Form->submit();
echo $this->Form->end();
?>
ここで Form ヘルパーを使ってフォームを作成しています。ファイルを選択するパーツと、送信ボタンを生成します。
そして、変数 $fileform はコントローラから渡す Form クラスのインスタンスです。
カスタムバリデーション作成
次に、カスタムバリデーションを作成していきます。
ここでは、独自のバリデーションを定義します。折角なのでなにか制約を設けようと思うので、以下の2点を制約項目にします。
- アップロードできるのは CSV ファイルのみ
- アップロード可能なファイルサイズは 200 バイトまで
今回はたまたま CSV ファイルにしましたが、画像ファイルなどでも全然 OK です。
cakephp/src/Model 配下に Validation ディレクトリを作成し、その中に CustomValidation.php を作成します。
cakephp
├─ src
│ ├─ Model
│ │ ├─ Validation
│ │ └─ CustomValidation.php
ファイルが作成できたら、カスタムバリデーションを定義していきます。
- cakephp/src/Model/Validation/CustomValidation.php
-
<?php namespace App\Model\Validation; use Cake\Validation\Validation; class CustomValidation extends Validation { /** * CSVファイルであるか * @param $files * @return bool */ public static function isCsv($files) { $ret = true; $allows = array("application/vnd.ms-excel", "text/csv"); $ext = explode($files['name'], '.'); if (!in_array($files['type'], $allows) || !end($ext)=='csv') { $ret = false; } return $ret; } /** * ファイル容量制限 * @param $files * @return bool */ public static function limitFileSize($files) { return ($files['size'] < 200); } }
isCsv() メソッドで CSV ファイルであるかのチェックを行っています。簡易的ではありますが、MIME タイプと拡張子を見て CSV であるかどうかを bool 型で返します。
limitFileSize() メソッドでは、ファイルサイズをチェックします。直書きで申し訳ないですが、200 バイトより小さいかどうかを bool 型で返します。
カスタムバリデーションの定義はこれで完了です。
Form クラス作成
次に、Form クラスを作成していきます。 Form クラスは独自のフォーム周りの機能を実装できるクラスですが、モデルの絡まないフォームを作成する際にはここを使うと色々と便利です。
cakephp/src 配下に Form ディレクトリを作成し、その中に UploadFileForm.php を作成します。
cakephp
├─src
│ ├─ Form
│ │ └─ UploadFileForm.php
ファイルが作成できたら、Form クラスを実装します。
- cakephp/src/Form/UploadFileForm.php
-
<?php namespace App\Form; use Cake\Form\Form; use Cake\Validation\Validator; class UploadFilesForm extends Form { protected function _buildValidator(Validator $validator) { $validator->provider('customValidate', 'App\Model\Validation\CustomValidation'); $validator ->add('upload_file', 'isCsv', [ 'provider' => 'customValidate', 'rule' => 'isCsv', 'message' => 'CSVファイルのみアップロード可能です', ]) ->add('upload_file', 'limitFileSize', [ 'provider' => 'customValidate', 'rule' => 'limitFileSize', 'message' => '200バイト以内にしてください', ]); return $validator; } }
上から解説していきます。
$validator->provider('customValidate', 'App\Model\Validation\CustomValidation');
「customValidate 」という名前で、先に作成したカスタムバリデーションをプロパイダに登録しています。こうする事で、カスタムバリデーションを呼び出せるようになります。
$validator
->add('upload_file', 'isCsv', [
'provider' => 'customValidate',
'rule' => 'isCsv',
'message' => 'CSVファイルのみアップロード可能です',
])
->add('upload_file', 'limitFileSize', [
'provider' => 'customValidate',
'rule' => 'limitFileSize',
'message' => '200バイト以内にしてください',
]);
ここでバリデーションを定義しています。 upload_file(フォームパーツでいう name 属性)に対し、isCsv項目として以下を定義しています。
provider 使用プロパイダ。ここでは先ほど登録した customValidate を指定 rule どのバリデーションを行うか。ここでは isCsv() メソッドを指定 message バリデーションエラーの際のメッセージを定義## コントローラ実装
最後に、これらを踏まえコントローラを実装します。
その前に、アップロードした CSV ファイルの設置場所として、cakephp/ 配下に storage ディレクトリを作成しておきます。
cakephp
├─ src
│ ├─ storage
そして、コントローラは以下のようになります。
- cakephp/src/Controller/UploadFilesController.php
-
<?php namespace App\Controller; use App\Controller\AppController; use App\Form\UploadFilesForm; class UploadFilesController extends AppController { public function index() { $fileform = new UploadFilesForm(); if ($this->request->is('post')) { if ($fileform->validate($this->request->data)) { move_uploaded_file( $this->request->data['upload_file']['tmp_name'], sprintf( '/var/www/html/cakephp/storage/%s', $this->request->data['upload_file']['name'] ) ); } } $this->set(compact('fileform')); } }
上から解説します。
use App\Form\UploadFilesForm;
先ほど作成した Form クラスを use しています。
public function index()
{
$fileform = new UploadFilesForm();
if ($this->request->is('post')) {
if ($fileform->validate($this->request->data)) {
move_uploaded_file($this->request->data['upload_file']['tmp_name'], sprintf('/var/www/html/cakephp/storage/%s',$this->request->data['upload_file']['name']));
}
}
$this->set(compact('fileform'));
}
まずは Form クラスのインスタンスを生成しています。
次に、POST での送信があった場合には、Form クラスの validate() メソッドに POST データを渡してバリデーションを行っています。
バリデーションに通った場合は、move_uploaded_file() メソッドでファイルをアップロードしています。(デモなのでパスは直書き)
バリデーションエラーが出た場合にはアップロード処理は行わずそのまま最後に Form クラスインスタンスがセットされるだけですが、エラーが入っているのでこれで成立します。
動作確認
これで全ての実装が完了したので、ブラウザからアクセスして確認してみます。
http://YOUR-DOMAIN/upload-files
にアクセスします。
シンプルなフォーム画面が表示されました。ここに適当なファイルをセットして送信します。
バリデーションエラーの場合は以下のようになります。
エンティティで回すのと同じ形でエラーメッセージが表示されています。
逆に、正常なファイルを送信しバリデーションに通ると、ファイルがアップロードされます。
[demo@localhost cakephp]# ll storage/
total 4
-rw-r--r--. 1 apache apache 163 Feb 22 22:06 test.csv
まとめ
以上で作業は完了です。モデルが絡む・絡まないは関係なく、やはりフォーム機能は共通のロジック(というかフローというか)で回したいですよね。
地味な部分ですが知っておいて損はない機能ですので、是非試してみてください。