RitoLabo

CakePHP3のFormクラスとカスタムバリデーションを使ってファイルアップロード機能を構築する

  • 公開:
  • カテゴリ: PHP CakePHP
  • タグ: PHP,validation,Form,CakePHP,3.5,FormClass,CustomValidation

CakePHPでWebアプリケーションを構築していると、直接モデルとは関係のない(データベースを絡めない)フォームとバリデーションを用いた機能一式が必要になる時があります。

テキストや選択での入力項目のあるものについては、ほぼほぼそれらをデータベースへ格納する場合が多いですが、ファイルの場合はどうでしょうか?アップロード情報そのものを格納する場合ももちろんありますが、ファイルをアップロードする際のバリデーションというのは実質的にデータベースに絡まないので、テーブルエンティティにてバリデーションを実装する事が難しくなります。(というか、倫理的に違うというか)

そこで今回は、CakePHP3の「Formクラス」「カスタムバリデーション」を用いて、ファイルのアップロード機能を構築します。

アジェンダ
  1. 開発環境
  2. コントローラ作成
  3. テンプレート作成
  4. カスタムバリデーション作成
  5. Formクラス作成
  6. コントローラ実装
  7. 動作確認

開発環境

今回の開発環境は以下の通りです。

  • 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

まとめ

以上で作業は完了です。モデルが絡む・絡まないは関係なく、やはりフォーム機能は共通のロジック(というかフローというか)で回したいですよね。

地味な部分ですが知っておいて損はない機能ですので、是非試してみてください。