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

はじめに

CakePHP 4 には重複チェック機能として isUnique クラスが用意されています。この関数は複数フィールドの組み合わせでのチェックにも対応しているのですが、任意の条件を追加することはできないようです。

そこで今日は CakePHP 4 で条件付きに重複チェックを行う方法 をご紹介します。今回は例として、ユーザー名の重複チェックで、削除フラグが立っているデータを対象外にする場合を想定します。

今回使用したのは CakePHP 4.0.7 です。

目次
  1. 下準備
  2. 対応方法
  3. おわりに

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;
        }
    }
}

3. おわりに

isUnique に限らず、条件付きのルールは $rules->add() の引数を関数にすることで対応するのが CakePHP 流のようです。

ケースによってはカスタムルールとして定義すると便利なこともあるかもしれません。今回はその実装方法は割愛しますが、折を見て記事を書こうかなと思っています。