またどこかのCTOになった人のブログ

またどこかのCTOになった人が書いてます。最近はCakePHP3とConoHaのネタが多いです。

CakePHP3でカスタムバリデーション作ってみた。

2016/10/31 追記

カスタムバリデーションではないのですが、バリデーション関連の記事を追加してみました。
tsuralabo.hatenablog.com




チマチマ作ってるアレですが、管理画面の方に着手しています。
入力項目や登録するデータが出てくるので、どうしてもバリデーションが必要になってきます。

CakePHP3のバリデーションはCakePHP2までと少し変わっていまして、2パターンのバリデーションがあります。
1つ目はPOSTデータをチェックする際のバリデーション。
2つ目はsaveメソッド実行時のバリデーション。

ググるとわかりますが前者は「validationDefault」、後者は「buildRules」なんて言うらしいです。
コードで書くとこんな感じです。

<?php
namespace App\Model\Table;
use Cake\ORM\Table;
use Cake\Validation\Validator; // validationDefault使う時に必要です
use Cake\ORM\RulesChecker;     // buildRules使う時に必要です

class UsersTable extends Table {
  public function validationDefault(Validator $validator) {
    // POSTデータ受け取り時のバリデーション

    // 結果を返す
    return $validator;
  }

  public function buildRules(RulesChecker $rules) {
    // saveメソッド実行時のバリデーション

    // 結果を返す
    return $rules;
  }
}

見たらわかると思いますが、CakePHP3ではTableにバリデーションを書きます。
上のソースだとUsersTableに書いてます。

バリデーションの書き方はCakePHP2の頃と似ているようで違います。
Validator型(でいいのかな?)の$validatorに対して、「このフィールド」に対して「こういうバリデーション」します、という書き方です。
例えばusernameというフィールドに対して入力必須のバリデーションをチェックしたい時は

<?php
$validator->notEmpty('username', 'ユーザ名が入力されていません。');

こんな感じです。
さらに4文字以上、8文字以内というバリデーションをチェックしたい時は

<?php
$validator
  ->notEmpty('userid', 'ユーザIDが入力されていません。')
  ->lengthBetween('username', [4, 8], 'ユーザIDは4文字以上、8文字以内。');

このようになります。
他のフィールドもチェックしたい時はどんどん繋げていけばいいのですが、ソースが見難くなるかもしれません。
そんな時は

<?php
$validator
  ->notEmpty('username', 'ユーザ名が入力されていません。')
  ->lengthBetween('username', [4, 8], 'ログインIDは4文字以上、8文字以内。');

$validator
  ->notEmpty('password', 'パスワードが入力されていません。')
  ->lengthBetween('password', [8, 32], 'パスワードは8文字以上、32文字以内。');

と分けて書いても大丈夫です。
個人的にはこちらの書き方の方が好きです。
理由は何の項目に対して、どんなバリデーションをチェックしているか見やすいからです(個人的な意見です)
notEmptyが何とか、lengthBetweenって何? とかはグーグル先生に聞いてみて下さい。



さて。
今回はユーザIDに対して半角英数字のみというバリデーションをチェックすることにします。
CakePHP2の頃からある「alphaNumeric」を使えばよさそうなのですが、これ実は日本語を通してしまうんです。
なので少なくとも日本で展開するWebサービスだと使いものになりません(多分)
CakePHP3本体のValidationを書き換えればいけちゃうと思うんですが、あまり本体には手を入れたくない。

こんな時、CakePHP2だったらAppModelにサラっと書いて、それを呼び出せばよかったのですがCakePHP3にはありません。
ですので、本家のマニュアルを見ながらそれっぽいものを作ってみました。
それがやっと辿り着いた、カスタムバリデーションです。


本家の書き方を踏襲して、src\Model配下にValidationというディレクトリをとりあえず作ります。
作ったValidationディレクトリにCustomValidation.phpを作成します。
中身はこんな感じです。
ついでに半角英数字のバリデーション用のfunctionも書いてしまいます。
ただの正規表現なんですけどね。

<?php
namespace App\Model\Validation;
use Cake\Validation\Validation;

/**
 * カスタムバリデーションサンプル
 */
class CustomValidation extends Validation {
  
  /**
   * 半角英数字バリデーション
   * @param type $check
   * @return type
   */
  public static function alphaNumericCustom($check) {
    return (bool) preg_match('/^[a-zA-Z0-9]+$/', $check);
  }
}

名前はもちろん、CustomValidationじゃなくて構いません。
RururuValidationだろうが、SpecialValidationだろうが何でもOKです。
ただ、注意する必要があるのは以下の2点。


その1 作成するfunctionは「static」にすること
その2 作成したfunctionの戻り値はbool型にすること


この2点です。
試しにstatic取ってみると、staticで書けとエラーが出ます。
これさえ守ればあとはただの正規表現的な何かでしかありません。
とりあえずカスタムバリデーションとかいう偉そうな名前の正規表現ができたので、先程のバリデーションに組み込みます。

<?php
/**
 * POST処理時のバリデーション
 * @param Validator $validator
 * @return Validator
 */
public function validationDefault(Validator $validator) {
    
  // カスタムバリデーション設定
  // 書き方は「$validator->provider('プロバイダのキー', 'カスタムバリデーションのパス');」です。
  $validator->provider('ProviderKey', 'App\Model\Validation\CustomValidation');
    
  $validator
    ->notEmpty('username', 'ユーザ名が入力されていません。')
    // ここからカスタムバリデーション
    ->add('username', 'ruleName', [
          'rule' => ['alphaNumericCustom'], 
          'provider' => 'ProviderKey',   // カスタムバリデーション設定で書いたプロバイダのキーを入れます。
          'message' => 'ログインIDは半角英数字で入力してください。'])
    // ここまでカスタムバリデーション
    ->lengthBetween('username', [4, 8], 'ログインIDは4文字以上、8文字以内。');
    
  // 結果を返す
  return $validator;
}

※2016/07/11にここから修正しました ----------

カスタムバリデーション設定と書いている部分は、CakePHP3的に言うとプロバイダ設定です。
呼び出しというか、バリデータに対して「このプロバイダ使いますよ」追加してるわけです。
「add」メソッドでカスタムバリデーションに作ったfunctionを追加すると、無事にカスタムバリデーションが使えるようになります。
ruleNameと書いてある箇所はバリデーションのルール名です。適当に入力しておいて下さい(ダメかもしれませんが)

試したみた感じ、プロバイダのキーは何でもOKでした。
上の例だと「ProviderKey」と書いてますが、RururuでもSpecialでも何でも大丈夫です。

※ここまで修正しました-----------------------

バリデーションチェックでエラーが発生した場合、設定したメッセージが返ってきます。
それを呼び元でごにょごにょしてViewにメッセージ表示すればいいんじゃないかなと思います。
一応、思い出したかのようにController側からの呼び出し方も書いておきます。

<?php
/**
 * ユーザ追加functionということにします
 */
public function add() {
  // POSTデータを渡す
  $user = $this->Users->newEntity($this->request->data);

  if (!$user->errors()) {
    // バリデーションエラーがなかった場合
  } else {
    // バリデーションエラーの場合
  }
}

エラー時はerrorsという中にエラーメッセージが入ってるので、Flashなどでメッセージを表示するといいです。
エラーメッセージを1つずつまとめて表示したい!という場合は、イケてない書き方ですが

<?php
/**
 * ユーザ追加functionということにします
 */
public function add() {
  // POSTデータを渡す
  $user = $this->Users->newEntity($this->request->data);

  if (!$user->errors()) {
    // バリデーションエラーがなかった場合
  } else {
    // バリデーションエラーの場合
    foreach ($user->errors() as $error) {
      foreach ($error as $message) {
        pr($message);
      }
    }
  }
}


$message辺りを配列にブチ込んでView側で出力しながら改行コード入れると、それっぽくなります。
もう少しいい書き方できればいいんですが、模索中ということで。

ちなみにbuildRulesの方も書き方は同じです。
多分。