We have moved our forum to GitHub Discussions. For questions about Phalcon v3/v4/v5 you can visit here and for Phalcon v6 here.

scenario-based validation in Phalcon ?

Hello,

Do you know how to implement scenario-based validation in Phalcon, in similar way as it is in Yii ?

I am going to create an intermediate class to extend Model and implement scenario functionality in it... But may be it is present already, I just did not find??



16.1k
Accepted
answer

Hey Dimonsky, I too came from Yii and quite like how Yii handles validation. Here is a rough base model class I've been using. I haven't implemented all the features yet, but it mostly works for my purposes.

It lets you define a rules method in your model like this:

public function rules()
    {
        return [
            ['member_id', 'PresenceOf'],
            ['title', 'StringLength', 'min'=>5, 'max'=>50],
            ['title', 'Uniqueness', 'message'=>'An item with this title already exists'],
            ['description', 'StringLength', 'min'=>50, 'max'=>255],
            ['demo_url', 'Url'],
            ['status', 'Inclusionin', 'domain'=>[0, 1, 2, 3]],
            ['accept_terms', 'Inclusionin', 'domain'=>[0, 1]]
        ];
    }

You can set a scenario just by doing $model->scenario = 'myscenario'; Or you can explicity skip fields by calling $model->skipValidation(array('title', 'description'))... which takes either a string or array.

On a side note, use at your own risk. I haven't tested the logic and I use $model->skipValidation() rather than setting scenarios. Heres the rough class:

use Phalcon\Mvc\Model\Message as Message;

class Model extends \Phalcon\Mvc\Model
{

    protected $validatorMap = [
        'Email'=>'Phalcon\Mvc\Model\Validator\Email',
        'Exclusionin'=>'Phalcon\Mvc\Model\Validator\Exclusionin',
        'Inclusionin'=>'Phalcon\Mvc\Model\Validator\Inclusionin',
        'Numericality'=>'Phalcon\Mvc\Model\Validator\Numericality',
        'PresenceOf'=>'Phalcon\Mvc\Model\Validator\PresenceOf',
        'Regex'=>'Phalcon\Mvc\Model\Validator\Regex',
        'StringLength'=>'Phalcon\Mvc\Model\Validator\StringLength',
        'Uniqueness'=>'Phalcon\Mvc\Model\Validator\Uniqueness',
        'Url'=>'Phalcon\Mvc\Model\Validator\Url',
        'compare'=>'compare',
        'default'=>'defaultValue'
    ];

    protected $scenario;

    protected $validators;

    /**
     * Array of fields to skip validation on
     *
     * @var unknown
     */
    protected $_skipFields = array();

    public function initialize()
    {
        $this->setup(['notNullValidations'=>FALSE]);
    }

    protected function rules()
    {
        return array();
    }

    public function skipValidation($field)
    {
        $type = gettype($field);
        if ($type == "array") {
            foreach ($field as $name) {
                $this->_skipFields[] = (string) $name;
            }
        } else {
            $this->_skipFields[] = (string) $field;
        }
    }

    protected function validation()
    {
        foreach ($this->rules() as $rule) {
            $fields = explode(',', $rule[0]);

            if (!isset($rule[0], $rule[1])) {
                throw new Phalcon\Exception('Invalid model rule');
            }

            $validator = $rule[1];
            $args = array_slice($rule, 2);
            $on = isset($rule['on'])? (array)$rule['on'] : false;
            $except = isset($rule['except'])? (array)$rule['except'] : false;

            // EXCEPT FLAG PRESENT, AND SCENARIO MATCHES, DONT VALIDATE
            if ($except && in_array($except, $this->scenario)) {
                continue;
            }

            // ON FLAG PRESENT, BUT SCENARIO DOESNT MATCH, DONT VALIDATE
            if ($on && !in_array($on, $this->scenario)) {
                continue;
            }

            $validatorClass = $this->getValidatorClass($validator);
            foreach ($fields as $field) {
                // FLAGGED TO SKIP
                if (in_array($field, $this->_skipFields) ) {
                    continue;
                }

                $args['field'] = trim($field);

                if (method_exists($this, $validatorClass)) {
                    $this->{$validatorClass}($field, $args);
                } else {
                    $this->validate( new $validatorClass($args) );
                }
            }
        }

        return !$this->validationHasFailed() == true;
    }

    private function getValidatorClass($validator = "")
    {
        return isset($this->validatorMap[$validator])? $this->validatorMap[$validator] : $validator;
    }

    private function compare($field, $args)
    {
        if ( !isset($args['with']) || $this->{$field} !== $this->{$args['with']}) {
            $this->appendMessage(new Message("{$field} doesn't match", $field));
            return false;
        }

        return true;
    }

    public function defaultValue($field, $args)
    {

    }

}


3.8k

Hi jymboche, This is really good code, thanks a lot!! I'll put here a comment when test this and add something.



3.8k

Hello jymboche!!

I have created a class, extended the one you provided above. Found some issues that might be interesting for someone reading this topic. From what I've found, scenario shall be an array assigned as

$mm->scenario = ['scenario1','scenario2'] ;

in this case the piece of code

if ($on && !in_array($on, $this->scenario))

will work only.

Next thing is, when a user overloads a function Rules(), he shall write the scenario as a string, but not array. So it shall look like

public function rules(){

    return [

        ['id', 'PresenceOf', 'on'=>'scenario1'],

        ['email', 'Email', 'on'=>'scenario2'],

        ['email', 'Uniqueness', 'message'=>'An item with this email already exists', 'on'=>'scenario2'],

        ['name', 'StringLength', 'min'=>3, 'max'=>20, 'on'=>'scenario2']

    ];

}

Thank you for the code!!



16.1k

Awesome, glad you worked it out. When/if you've finished your class maybe consider adding it to the phalcon incubator repository: https://github.com/phalcon/incubator

I think a lot of users would like something similar to this in the phalcon core eventually. Thanks!

Yeah, an integrated scenario functionality could be usefull!

I'd rather prefer anyway a more object oriented approach to the problem instead and avoid the use of $validatorMap simply using the fully qualified namespace or a valid callable on the second parameter of the validator definition.

Thanks for the start!



16.1k

I've had a change of philosophy over the past couple months. I've embraced using forms and form validation/sanitization and keep model validation to the bare minimum. It's a somewhat different approach than other frameworks but I've grown to like it.

Me too Jym! I use model validation just to ensure the right type on the persistent layer