前置き
Phalcon使い始めの頃、モデルのバリデーションのコードをみると、よく以下のようなコードを見かけた。
public function isValid()
{
// account ID
if($this->account_id === null || $this->account_id === '')
{
$this->appendMessage(new Message('account-id.required', 'account_id', 'accountIdRequired'));
}
else if(!is_numeric($this->account_id) || $this->account_id < 1)
{
$this->appendMessage(new Message('account-id.invalid', 'account_id', 'accountIdInvalid'));
}
else if()
....
return $this->validationHasFailed() !== true;
}
せっかくPhalconがバリデーションを提供してくれているのに、なぜそれを使わないのか尋ねてみたところ、Phalconが提供しているバリデーションだけだとチェックできないことがあるうえ、Phalconのドキュメントがわかりにくくカスタムバリデーションの作り方もよくわからないし、、バリデーションをifで書いたほうが早いと言われた。
いいたいことはわかるのだが、こういったことを繰り返してコードのメンテナンス性が悪くなっていくのが目に見えているため、改善しようと思い立ち、その時に作成したカスタムバリデータークラスや、クラスを使わなくてもバリデーションをかけるようにするCustomクラスについて書こうと思う。
環境
PHP 5.6.26
Phalcon2.0.13
やりたいこと
Phalconのバリデーションを使ってカスタムバリデーションを簡単にかけるようにする。
こんな感じでかけるようにしたい。
$this->validation = new Validation();
$this->validation->add('account_id', new PresenceOf(array('cancelOnFail' => true)))
->add('account_id', new Length(0, 255, true))
->add('account_id', new Custom(
function($validation, $attribute, $value){
if ($value > 0){
return true;
}
return false;
})
); //0より大きい数字であること
カスタムバリデーターのサンプル
カスタムバリデーターのサンプルクラス。このクラスはパラメータの長さをチェックする。
※実際、長さをチェックしたい場合は、PhalconがPhalcon\Validation\Validator\StringLengthを提供しているので、こちらを使うのが良い。
<?php
namespace Araiguma\Common\Validator;
use \Phalcon\Validation\Message;
use \Phalcon\Validation\Validator;
use \Phalcon\Validation\ValidatorInterface;
class Length extends Validator implements ValidatorInterface
{
const MAX_LENGTH=10000;
private $min;
private $max;
private $multiByte;
public function __construct($min=0, $max=self::MAX_LENGTH, $multiByte=false)
{
$this->min = $min;
$this->max = $max;
$this->multiByte = $multiByte; //multiByteがtrueの場合はmb_strlenを長さ計算に用いる
}
/**
* Executes the validation
*
* @param Phalcon\Validation $validation
* @param string $attribute
* @return boolean
*/
public function validate(\Phalcon\Validation $validation, $attribute)
{
$value = $validation->getValue($attribute);
$length = $this->getLength($value);
if ($value === true || $value === false) {
//true, falseは文字列としてみる。
$length = 4;
}
if ($this->min <= $length && $length <= $this->max)
{
return true;
}
$validation->appendMessage(new Message('invalidLength', $attribute, 'invalidLength'));
return false;
}
private function getLength($value)
{
if ($this->multiByte)
{
return mb_strlen($value);
}
return strlen($value);
}
}
Customクラス
バリデーターのクラスをつくらなくても、コードをかけるように作ったCustomクラス。
<?php
/**
* 任意のバリデーションを登録できるバリデータ。
*/
namespace Araiguma\Common\Validator;
use \Phalcon\Validation\Message;
use \Phalcon\Validation\Validator;
use \Phalcon\Validation\ValidatorInterface;
class Custom extends Validator implements ValidatorInterface
{
private $func;
/**
* funcには以下の引き数を定義した関数を指定する。
*
* func($validation, $val, $attribute)
* $validation: Phalcon\Validation
* $val: mix 'attribute'の値
* $attribute: string 'チェックするフィールド名'
**/
public function __construct($func)
{
$this->func = $func;
}
/**
* Executes the validation
*
* @param Phalcon\Validation $validation
* @param string $attribute
* @return boolean
*/
public function validate(\Phalcon\Validation $validation, $attribute)
{
$value = $validation->getValue($attribute);
return call_user_func($this->func, $validation, $attribute, $value);
}
}
最終的なコード例
上のクラスを利用して、以下のような感じでかけるようになった。
namespace Araiguma\Mvc\Model;
use Araiguma\Common\Validator\Custom;
use Araiguma\Common\Validator\Length;
use Phalcon\Mvc\Model\Message as Message;
use Phalcon\Validation\Message as ValidationMessage;
use Phalcon\Validation;
use Phalcon\Validation\Validator\PresenceOf;
class AraigumaModel extends BaseModel
{
protected $account_id = '';
protected $file_name = '';
...
public function isValid()
{
$this->validation = new Validation();
$params = array(
'account_id' => $this->account_id,
'file_name' => $this->file_name
);
$this->validation->add('account_id', new PresenceOf(array('cancelOnFail' => true)))
->add('account_id', new Custom(
function($validation, $attribute, $value){
if ($value > 0){
return true;
}
return false;
})
); //0より大きい数字であること
$this->validation->add('file_name', new PresenceOf(array('cancelOnFail' => true)))
->add('file_name', new Length(0, 255, true));
->add('file_name', new Custom(
function($validation, $attribute, $value){
if (self::findFirstByFileName($value)) {
$validation->appendMessage(new ValidationMessage('同じファイル名がすでに存在します。', $attribute, 'duplicateFileName'));
return false;
}
return true;
})
); // 同じファイル名がDBに存在しているか
$messages = $this->validation->validate($params);
foreach ($messages as $message) {
$this->appendMessage(new Message( $message->getMessage() , $message->getField(), $message->getType()));
}
return !$this->validationHasFailed();
}
}
結果
プロジェクト的には、のちの資産になるため各バリデーションで独自クラスは毎回作るようにしたいが、どうしてもそれが大変というときはCustomクラスを今でも使ってます。
課題
既存の各バリデーションも全部書き換えたいが、そちらにはなかなか手をつけられない...
テスト
以下に各クラスのphpunitのテストを載せておく。
LengthTest.php
CustomTest.php
参考
[Phalcon Validation]
(http://phalcon-docs-ja.readthedocs.io/ja/stable/reference/validation.html)