CakePHP 4 で条件付きの重複チェック(isUnique)を行う方法

はじめに
CakePHP 4 には重複チェック機能として isUnique クラスが用意されています。この関数は複数フィールドの組み合わせでのチェックにも対応しているのですが、任意の条件を追加することはできないようです。
そこで今日は CakePHP 4 で条件付きに重複チェックを行う方法 をご紹介します。今回は例として、ユーザー名の重複チェックで、削除フラグが立っているデータを対象外にする場合を想定します。
今回使用したのは CakePHP 4.0.7 です。
1. 下準備
今回動作確認で使用したデータベースやコードです。
データベース
CREATE TABLE users (
id int(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
username varchar(255) NOT NULL,
deleted tinyint(1) NOT NULL DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO users (id, username, deleted) VALUES
(1, 'mike', 0),
(2, 'dan', 1);
モデル
/src/Model/Entity/User.php
<?php
declare(strict_types=1);
namespace App\Model\Entity;
use Cake\ORM\Entity;
class User extends Entity
{
protected $_accessible = [
'username' => true,
'deleted' => true,
];
}
/src/Model/Table/UsersTable.php
<?php
declare(strict_types=1);
namespace App\Model\Table;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
class UsersTable extends Table
{
public function initialize(array $config): void
{
parent::initialize($config);
$this->setTable('users');
$this->setPrimaryKey('id');
}
public function validationDefault(Validator $validator): Validator
{
return $validator;
}
public function buildRules(RulesChecker $rules): RulesChecker
{
return $rules;
}
}
コントローラ
下記の「新規登録」と「更新」の部分を切り替えて動作確認をしました。
/src/Controller/SamplesController.php
<?php
declare(strict_types=1);
namespace App\Controller;
class SamplesController extends AppController
{
public function index()
{
$this->loadModel('Users');
// 新規登録
$user = $this->Users->newEntity(['username' => 'mike']);
// 更新
// $user = $this->Users->get(1);
// $user = $this->Users->patchEntity($user, ['username' => 'dan']);
if (!$this->Users->save($user)) {
debug($user->getErrors());
} else {
echo 'Success';
}
exit;
}
}
2. 対応方法
Table クラスの $rules->add() の引数を関数にすることで、条件に応じてルールを設定することができます。
例えば deleted カラムが 0 のものは重複チェックの対象にしない場合、下記のように書くことができます。
/src/Model/Table/UsersTable.php
<?php
...
// ▼追加(beforeRules で使用)
use ArrayObject;
use Cake\Datasource\EntityInterface;
use Cake\Event\EventInterface;
...
class UsersTable extends Table
{
public function buildRules(RulesChecker $rules): RulesChecker
{
// ▼これを追加
$rules->add(
function ($entity, $options) use ($rules) {
// 削除済データの場合は重複チェックはしない
if ($entity->deleted === true) {
return true;
}
// username と deleted の組み合わせで重複チェック
$rule = $rules->isUnique(
['username', 'deleted'], 'このユーザー名は使用できません'
);
return $rule($entity, $options);
}
);
return $rules;
}
// ▼ 新規登録時に deleted を追加
public function beforeRules(
EventInterface $event, EntityInterface $entity,
ArrayObject $options, $operation
)
{
if ($entity->isNew()) {
$entity->deleted = false;
}
}
}
- Using Conditional Rules (CakePHP 4.x Cookbook)
- https://book.cakephp.org/4/en/orm/validation.html#using-conditional-rules
3. おわりに
isUnique に限らず、条件付きのルールは $rules->add() の引数を関数にすることで対応するのが CakePHP 流のようです。
ケースによってはカスタムルールとして定義すると便利なこともあるかもしれません。今回はその実装方法は割愛しますが、折を見て記事を書こうかなと思っています。
- Creating Custom Rule Objects (CakePHP 4.x Cookbook)
- https://book.cakephp.org/4/en/orm/validation.html#creating-custom-re-usable-rules