CakePHP2から5に乗り換えた話 〜バリデーション〜

AIにエラーチェックしているイラストを描いてもらった
HTMLとCakePHPでフォームを実装する場合などにエラーチェックを行うことがあると思います。今日はそのエラーチェック、バリデーションの基本的な使い方を見ていきましょう。



基本形

例えばusersというテーブルに新規にデータを入れる場合。ここではコントローラーやモデルをUsersController.phpとUsersTable.phpにしていると仮定します。

// UsersTable.php
use Cake\Validation\Validator;

class UsersTable extends AppTable {
  public function validationDefault(Validator $validator): validator {
    $validator->notEmptyString('name', '名前が未入力です');
    $validator->add('email', 'email', [
      'rule' => 'email',
      'message' => 'メールアドレスの形式が不正です',
    ]);

    return $validator;
  }
}

// UsersController.php
class UsersController extends AppController {
  public function add() {
    // フォームから送信されたデータ
    $data = $this->request->getData();

    // エンティティを作成
    $entity = $this->Users->newEntity($data);

    // エラーがあるか判定
    if($entity->hasErrors()) {
      // エラー時の処理
    }
  }
}

ざっくりと書くとこんな感じですね。

notEmptyStringは未入力の場合にエラーになる関数です。必須入力のフィールドに使うといいでしょう。他にもメールアドレスやURLの形式をチェックしたり何文字以内とか数字以外はエラーとかいろんなパターンをCakeさんが用意してくれています。

これを作っておくとnewEntityやpatchEntityの際にエラーチェックを行ってくれます。エラーがあるかどうかはhasErrorsやgetErrorsなどで判定を行うことができるので、それを使ってエラー処理を入れることが可能です。



default以外の関数を使用する

validationXxxのXxxの部分を上記のようにDafaultとするとCakePHPがnewEntity時などに自動的にこれを使用してエラーチェックをしてくれます。もし任意に名前を変えたい場合はこのようにします。

// UsersTable.php
use Cake\Validation\Validator;

class UsersTable extends AppTable {
  public function validationCustom(Validator $validator): validator {
    $validator->notEmptyString('name', '名前が未入力です');
    $validator->add('email', 'email', [
      'rule' => 'email',
      'message' => 'メールアドレスの形式が不正です',
    ]);

    return $validator;
  }
}

// UsersController.php
class UsersController extends AppController {
  public function add() {
    // フォームから送信されたデータ
    $data = $this->request->getData();

    // エンティティを作成
    $entity = $this->Users->newEntity($data, ['validate' => 'custom']);

    // エラーがあるか判定
    if($entity->hasErrors()) {
      // エラー時の処理
    }
  }
}

newEntityやpatchEntityのときにバリデーション名を指定するとそれに応じた関数でエラーチェックを行ってくれます。例えば上記のようにバリデーション名をcustom(先頭は小文字)とした場合はvalidationCustomという関数が使われます。状況によってチェック内容を切り替えたい場合などに使えますね。



他の関数から呼び出す

バリデーション関数の中で他の関数を呼び出すこともできます。例えば名前の必須入力は共通で、メールアドレスやパスワードのチェックは別々に行うみたいな場合。

use Cake\Validation\Validator;

class UsersTable extends AppTable {
  public function validationDefault(Validator $validator): validator {
    $validator->notEmptyString('name', '名前が未入力です');

    return $validator;
  }

  public function validationEmail(Validator $validator): validator {
    $validator = $this->validationDefault($validator);

    $validator->notEmptyString('email', 'メールアドレスが未入力です');
    $validator->add('email', 'email', [
      'rule' => 'email',
      'message' => 'メールアドレスの形式が不正です',
    ]);

    return $validator;
  }

  public function validationPassword(Validator $validator): validator {
    $validator = $this->validationDefault($validator);

    $validator->notEmptyString('password', 'パスワードが未入力です');
    $validator->add('text', 'minLength', [
      'rule' => ['minLength', 10],
      'message' => 'パスワードは10文字以上にしてください',
    ]);

    return $validator;
  }
}

こんな感じでvalidationEmailやvalidationPasswordの中でvalidationDefaultを呼び出せばnameの必須チェックを共通化できます。



ルールを自作する

notEmptyStringなどCakePHPが用意してくれているルールで全てのエラーをチェックできればいいのですが、中には自作の関数を使わないと上手くチェックできないこともあると思います。

ルールの作り方はいくつかありますが、手っ取り早いのはvalidationXxxの中に直接書いてしまうことです。

// UsersTable.php
use Cake\Validation\Validator;

class UsersTable extends AppTable {
  public function validationDefault(Validator $validator): validator {
    $validator->notEmptyString('name', '名前が未入力です');
    $validator->add('name', 'ngWord', [
      'rule' => function($value, $context) {
        $words = ['test', 'sample'];
        return !is_array($value, $words);
      },
      'message' => '名前に使用できない単語です',
    ]);

    return $validator;
  }
}

これは入力データがNGワードかどうかを判定するみたいな処理だと思ってください。add関数の中に直接ルールを書くことで独自のエラーチェックを実装できます。入力データは第一引数(上記の場合は$value)に入ってくるので、それを使って判定を行えばOKです。今回は名前がtestやsampleだったらエラーになるって感じですね。



自作のルールを別ファイルで作る

validationXxxの中に直接ルールを書くこともできますが、同じルールを複数の箇所で使いたい場合に毎回コピペするのもあれなので、そういうときは別ファイルに作って共通化しておく方が便利ですね(AppTableに書くという手もあるけど)

例えばModelフォルダの中にValidationというフォルダを作成し、そこにCustomValidator.phpというファイルを作成したとしましょう。

// CustomValidator.php
namespace App\Model\Validation;
use Cake\Validation\Validator;

class CustomValidator extends Validator {
  // NGワードのエラーチェック
  public static function ngWord($value, $words, $context) {
    if(!is_array($word)) {
      $words = [$words];
    }

    return !in_array($value, $words);
  }
}

これを先ほどのvalidationDefaultの中で使いたいときはsetProviderという関数を使ってこのクラスを読み込みます。

// UsersTable.php
use Cake\Validation\Validator;

class UsersTable extends AppTable {
  public function validationCustom(Validator $validator): validator {
    // カスタムクラスの読み込み
    $validator->setProvider('custom', 'App\Model\Validation\CustomValidator');

    $validator->notEmptyString('name', '名前が未入力です');
    $validator->add('name', 'ngWord', [
      'rule' => ['ngWord', ['test', 'sample']],
      'provider' => 'custom',
      'message' => '名前に使用できない単語です',
    ]);

    return $validator;
  }
}

setProviderで読み込めばそのクラスの中にある関数を他のルールと同じように使うことができます。通常ruleのところは呼び出したい関数名を書くのですが、何かしらの値を渡したい場合はruleのところを[関数名、値]という配列にすることで関数の第二引数に値を渡すことができます。今回の場合だとngWord関数の$wordsにtestとsampleが配列で渡るってことですね。

ちなみにngWordの第三引数にある$contextにはテーブルの情報などが自動で入ってきます。






基本はこんなとこですかね。CakePHP2の頃に比べるとだいぶ書きやすくなったと思います。かなり応用が利くというか、長らくCakePHP2を使っていた僕からすると独自のルールも作りやすくて便利になったなあと思います。

フォームの場合はHTMLやJavaScriptだけでもある程度のエラーチェックはできますが、そこを突破されたときにサーバー側で何のチェックもしないというわけにもいかないので、バリデーションの使い方はぜひ押さえておきたいですね。

その他のCakePHP5系の話はこちら
ようやくのぼりはじめたはてしなく遠い開発坂
 もしかしたら何か関連しているかも? 
 質問や感想などお気軽にコメントしてください