Solved thread

This post is marked as solved. If you think the information contained on this thread must be part of the official documentation, please contribute submitting a pull request to its repository.

Manually "failing" the validation process in a Model's validation() method?

I'm separating chunks of my validation code from my model's validation() method into reusable \Phalcon\Validation components. A realistic scenario for why i'm doing this is I have a set of Validators for validating an account email (using StringLength and Email validators). I need to reuse this "account email validator" that's consisted of many validators to validate an email passed in to a login action method.

I ran into a problem where appending messages to my model via the mode's appendMessage() method does not cause the model validation to fail. So it appears Phalcon doesn't look at the length of the validation messages in a model before determining if validation failed?

So my question is: How can I manually trigger a model's validation to fail in the mode's validation() method after I manually append new validation messages?

Here's my custom email validator:

/**
 * Validates an email that is associated to a Player model.
 *
 * @package App\Validators\Players
 */
class PlayerEmailValidator extends Validation
{
    public function initialize()
    {
        $this->add('Email', new Email([
            'message' => 'Please enter a valid email.'
        ]));

        $this->add('Email', new StringLength([
            'message' => 'We only accept emails up to 255 characters long.',
            'max' => 255,
            'min' => 300 // this is only for testing! I'm intentionally setting this to 300 so I don't have to put in a 300 char email to fail this validation
        ]));
    }
}

Here's my AbstractModel class (all models inherit from this):

abstract class AbstractModel extends Model
{
    /**
     * Appends a list of messages in a \Phalcon\Validation\Message\Group object to the models validation messages.
     *
     * @param Group $messages
     * @return AbstractModel
     */
    protected function appendValidationMessages(Group $messages)
    {
        foreach($messages as $message)
        {
            $this->appendMessage(new Message(
                $message->getMessage(),
                $message->getField(),
                $message->getType()
            ));
        }

        return $this;
    }
}

Here's my AbstractPlayers class (abstract model class generated by Phalcon):

abstract class AbstractPlayers extends AbstractModel
{

    /**
     *
     * @var string
     * @Column(type="string", length=255, nullable=false)
     */
    protected $Email;

}

Here's my Players model:

class Players extends AbstractPlayers
{
    public function validation()
    {
        $validation = new Validation();

//        $validation->add('Email', new Email([
//            'message' => 'Please enter a valid email.'
//        ]));
//
//        $validation->add('Email', new StringLength([
//            'message' => 'We only accept emails up to 255 characters long.',
//            'max' => 255,
//            'min' => 6
//        ]));

        $emailValidator = new PlayerEmailValidator();

        $this->appendValidationMessages($emailValidator->validate($this));

        var_dump($this->validate($validation), $this->getMessages());

        return $this->validate($validation);
    }
}

Here's the result of var_dump($this->validate($validation), $this->getMessages()) in my Players model.

bool(true)

array(1) {
  [0]=>
  object(Phalcon\Mvc\Model\Message)#94 (5) {
    ["_type":protected]=>
    string(8) "TooShort"
    ["_message":protected]=>
    string(48) "Field Email must be at least 300 characters long"
    ["_field":protected]=>
    string(5) "Email"
    ["_model":protected]=>
    NULL
    ["_code":protected]=>
    int(0)
  }
}
edited Mar '17

Yea i agree, the problem with validate method in model that count is done on messages returned from validation, not from model, it should be changed imho.

Actually there is validationHasFailed which will do exactly what you want.

I tried using the result of validationHasFailed() and it doesn't work.

Looks like Phalcon model doesn't use the length of getMessages() or the result of validationHasFailed() to determine if validation fails... Starting to feel like this validation process is tightly closed and there's little room to customize it.

I updated my Players model to this:

class Players extends AbstractPlayers
{
    public function validation()
    {
        $validation = new Validation();

//        $validation->add('Email', new Email([
//            'message' => 'Please enter a valid email.'
//        ]));
//
//        $validation->add('Email', new StringLength([
//            'message' => 'We only accept emails up to 255 characters long.',
//            'max' => 255,
//            'min' => 6
//        ]));

        $emailValidator = new PlayerEmailValidator();

        $this->appendValidationMessages($emailValidator->validate($this));

        $this->validate($validation);
        var_dump($this->getMessages(), $this->validationHasFailed());
        exit;
        return $this->validationHasFailed();
    }
}

The result of the var_dumps are below, as you can see validationHasFailed() is indeed returning true.

array(1) {
  [0]=>
  object(Phalcon\Mvc\Model\Message)#94 (5) {
    ["_type":protected]=>
    string(8) "TooShort"
    ["_message":protected]=>
    string(48) "Field Email must be at least 300 characters long"
    ["_field":protected]=>
    string(5) "Email"
    ["_model":protected]=>
    NULL
    ["_code":protected]=>
    int(0)
  }
}
bool(true)

Actually there is validationHasFailed which will do exactly what you want.



723
Accepted
answer

I found a solution that works. All you need to do is create a custom Validation class for your model and from within that validation class you can run your validations and optionally use recursive validation.

Here is my Players model:

class Players extends AbstractPlayers
{
    public function validation()
    {
        $validation = new PlayerModelValidator();

        return $this->validate($validation);
    }
}

Here is my PlayerModelValidator:

class PlayerModelValidator extends Validation
{
    /** @var PlayerEmailValidator */
    protected $emailValidation;

    public function initialize()
    {
        $this->emailValidation = new PlayerEmailValidator();

        $this->setFilters('InGameName', 'trim');

        $this->add('InGameName', new StringLength([
            'max' => 25,
            'min' => 4
        ]));

        $this->add('InGameName', new Alnum([
            'message' => 'In-Game Name must contain only alphanumeric characters.'
        ]));

        $this->setFilters(['FirstName', 'LastName'], 'trim');

        $this->add(['FirstName', 'LastName'], new StringLength([
            'max' => 50,
            'min' => 2,
            'messageMaximum' => [
                'FirstName' => 'First name can only be up to 50 characters long.',
                'LastName' => 'Last name can only be up to 50 characters long.'
            ],
            'messageMinimum' => [
                'FirstName' => 'First name must be at least 2 characters.',
                'LastName' => 'Last name must be at least 2 characters.'
            ]
        ]));

        $this->add(['FirstName', 'LastName'], new Alpha([
            'message' => [
                'FirstName' => 'First name can only contain alphabetic characters.',
                'LastName' => 'Last name can only contain alphabetic characters.'
            ]
        ]));
    }

    /**
     * Executed after validation
     *
     * @param array $data
     * @param object $entity
     * @param Group $messages
     */
    public function afterValidation($data, $entity, $messages)
    {
        /** @var Validation\Message[] $emailMessages */
        $emailMessages = $this->emailValidation->validate($data, $entity);

        $messages->appendMessages($emailMessages);
    }
}
edited Mar '17

validationHasFailed return true IF validation failed, false if not. You need to return here just return !this->validationHasFailed(); and your previous code should work. https://github.com/phalcon/cphalcon/blob/master/phalcon/mvc/model.zep#L1548