CakePHP 4 でファイルアップロード時のバリデーションを実装する方法

はじめに
以前の投稿「CakePHP 4 でファイルアップロードを実装する方法」では、ファイルをアップロードする方法はご紹介しましたが、バリデーションの実装方法は説明しませんでした。
今日は CakePHP 4 でファイルアップロード時のバリデーションを実装する方法を紹介します。
今回使用したのは CakePHP 4.0.8 です。
1. 下準備
今回は Samples コントローラの index アクションとして実装しました。
<?php
declare(strict_types=1);
namespace App\Controller;
class SamplesController extends AppController
{
public function index()
{
}
}
<?= $this->Form->create(null, ['type' => 'file']) ?>
<!-- エラーメッセージ -->
<?php if ($this->Form->isFieldError('upfile')) : ?>
<?= $this->Form->error('upfile') ?>
<?php endif; ?>
<!-- ファイル選択フォーム -->
<?= $this->Form->file('upfile') ?>
<!-- 送信ボタン -->
<?= $this->Form->submit('アップロード') ?>
<?= $this->Form->end() ?>
Form ヘルパーの使い方は下記が参考になると思います。
file() は control() でも差支えありません。
- Options for Form Creation (CakePHP 4.x Cookbook)
- https://book.cakephp.org/4/en/views/helpers/form.html#options-for-form-creation
- Creating File Inputs (CakePHP 4.x Cookbook)
- https://book.cakephp.org/4/en/views/helpers/form.html#creating-file-inputs
- Options for Control (CakePHP 4.x Cookbook)
- https://book.cakephp.org/4/en/views/helpers/form.html#options-for-control
サンプルコードを試される場合は、アップロードしたファイルの保存用として /webroot/upload フォルダを作ってください。これが無いとエラーになります。
「2. モデルを使う場合」のサンプルコードは、下記テーブルで確認しました。
CREATE TABLE my_files (
id int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
path varchar(255) NOT NULL,
created datetime NOT NULL,
modified datetime NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2. モデルを使う場合
モデルを使う場合には、通常のバリデーションと同様に Table クラスに実装します。
今回のサンプルでは Entity/MyFile.php は無くて大丈夫です。
<?php
declare(strict_types=1);
namespace App\Model\Table;
use ArrayObject;
use Cake\Datasource\EntityInterface;
use Cake\Event\EventInterface;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
class MyFilesTable extends Table
{
public function initialize(array $config): void
{
parent::initialize($config);
$this->setTable('my_files');
$this->setDisplayField('id');
$this->setPrimaryKey('id');
$this->addBehavior('Timestamp');
}
public function validationDefault(Validator $validator): Validator
{
$validator
// 未選択
->notEmptyFile('upfile', 'ファイルを選択してください')
// アップロードエラー
->add('upfile', 'uploadError', [
'rule' => ['uploadError'],
'message' => 'ファイルのアップロードができませんでした',
'last' => true,
])
// ファイルサイズ
->add('upfile', 'fileSize', [
'rule' => ['fileSize', '<', '102400'],
'message' => '100 キロバイト未満のファイルを選択してください',
])
// 拡張子
->add('upfile', 'extension', [
'rule' => ['extension', ['jpg', 'png']],
'message' => '拡張子が jpg か png のファイルを選択してください',
'last' => true,
])
// MIME タイプ
->add('upfile', 'mimeType', [
'rule' => ['mimeType', ['image/jpeg', 'image/png']],
'message' => 'JPEG か PNG 形式のファイルを選択してください',
]);
return $validator;
}
// ファイルの保存処理はコントローラなど別な場所に書いても影響ありません
public function beforeSave(
EventInterface $event,
EntityInterface $entity,
ArrayObject $options
) {
// ファイル保存
// ※ webroot/upload フォルダを事前に作ってください
$file = $entity->upfile;
$name = $file->getClientFilename();
$path = WWW_ROOT . 'upload' . DS . $name;
$file->moveTo($path);
$entity->set(compact('path'));
}
}
public function index()
{
$this->loadModel('MyFiles');
$myFile = $this->MyFiles->newEntity([]);
$errors = [];
if ($this->request->is('post')) {
$myFile = $this->MyFiles->patchEntity($myFile, $this->request->getData());
if ($this->MyFiles->save($myFile)) {
$this->Flash->success('アップロード完了');
} else {
$errors = $myFile->getErrors();
$this->Flash->error('アップロード失敗');
}
}
$this->set(compact('errors', 'myFile'));
}
<!-- ▼ $myFile に変更 -->
<?= $this->Form->create($myFile, ['type' => 'file']) ?>
...
ファイルのバリデーションルールは公式ドキュメントでは見つけられず、下記ページで見つけた mimeType を手がかりに
/vendor/cakephp/cakephp/src/Validation/Validation.php を見て書きました。
下記ページの例では、特定条件を満たす場合にだけファイルのバリデーションを行う 'on' オプションの使い方が紹介されています。
- Conditional Validation (CakePHP 4.x Cookbook)
- https://book.cakephp.org/4/en/core-libraries/validation.html#conditional-validation
僕は使ったことがないのですが uploadedFile ルールを使えば、uploadError、mimeType、fileSize が1つでチェックできるようです。uploadedFile の機能については下記ページに説明はあるのですが、記述が少ないので、前述の Validation.php を見るのが良いのかなと思います。
- uploadedFile() (CakePHP 4.x API)
- https://api.cakephp.org/4.0/class-Cake.Validation.Validator.html#uploadedFile
3. モデル無しの場合
データベースへの登録が不要な場合など、モデルを使用しない場合には、下記のように独自の Form クラスを作ってバリデーションを実装できます。
<?php
namespace App\Form;
use Cake\Form\Form;
use Cake\Form\Schema;
use Cake\Validation\Validator;
class MyForm extends Form
{
protected function _buildSchema(Schema $schema): Schema
{
return $schema;
}
public function validationDefault(Validator $validator): Validator
{
$validator
// 未選択
->notEmptyFile('upfile', 'ファイルを選択してください');
// 他のルールは先に紹介した「MyFilesTable.php」の例などを参考にしてください
return $validator;
}
// ファイルの保存処理はコントローラなど別な場所に書いても影響ありません
protected function _execute(array $data): bool
{
// ファイル保存
// ※ webroot/upload フォルダを事前に作ってください
$file = $data['upfile'];
$name = $file->getClientFilename();
$path = WWW_ROOT . 'upload' . DS . $name;
$file->moveTo($path);
return true;
}
}
...
use App\Form\MyForm; // ← 追加
class SamplesController extends AppController
{
// ▼ 処理を追加
public function index()
{
$myForm = new MyForm();
$this->set(compact('myForm'));
$errors = [];
if ($this->request->is('post')) {
$postData = $this->request->getData();
if ($myForm->execute($postData)) {
$this->Flash->success('アップロード完了');
} else {
$errors = $myForm->getErrors();
$this->Flash->error('アップロード失敗');
}
}
}
}
<!-- ▼ $myForm に変更 -->
<?= $this->Form->create($myForm, ['type' => 'file']) ?>
...
モデル不使用でフォームをつくる方法は、公式ドキュメントの下記ページが参考になります。
- Modelless Forms (CakePHP 4.x Cookbook)
- https://book.cakephp.org/4/en/core-libraries/form.html
4. おわりに
CakePHP 4 はファイルのバリデーションも実装されていて便利だなと思います。自前で書くと結構大変なんですよね。
また、モデル無しフォームも使い勝手がいいと思います。モデルと書き方が同様なので、後日 DB に保存することになった場合には、モデルへの移行が簡単にできますね。
CakePHP の公式ドキュメントはかなり充実していると思うのですが、残念ながらファイルのバリデーションについては詳細な説明がみつかりませんでした。
同じように困っている方に、この記事が参考になればいいなと思っています。