Help us understand the problem. What is going on with this article?

Phalconでカスタムバリデーションを作る

More than 1 year has passed since last update.

前置き

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

tabelog
お店選びで失敗したくない人のためのグルメサイト「食べログ」を開発しています
https://tabelog.com
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした