Love Fuel?    Donate

FuelPHP Forums

Ask your question about FuelPHP in the appropriate forum, or help others by answering their questions.
Custom validation callbacks in ORM models?
  • Sorry if I've got this in the wrong place. It's related to both validation & ORM. Here is a slimmed version of my user model:
    class Model_User extends \Orm\Model
    {
    
        protected static $_properties = array(
            'username' => array(
                'label' => 'Username',
                'validation' => array(
                    'required',
                    'min_length' => array(2),
                    'max_length' => array(50),
                )
            )
        );
    
    }
    
    In my controller, I have
    $val = Validation::forge()->add_model('Model_User');
    
    if ($val->run())
    {
        try
        {
            $user = Model_User::forge(array(
                .......
                'username' => Input::post('username'),
                .......
            ));
            $user->save();
    
            .....
        }
        catch (Orm\ValidationFailed $e)
        {
            $errors = $e->getMessage();
        }
    }
    else
    {
        $errors = $val->error();
    }
    
    In the model, I want to add validation on the username property to ensure the username is unique. It needs to be checked for uniqueness before creation and before updating (only if it has been changed). At first I thought ORM observers would be perfect for this, but I'm not sure how to tie in the ORM's validation with the observers and also have the errors found in the observer sent all the way back up to the controller. Any ideas? P.S. It would also be nice if I could specify a callback in ORM properties' validation key, such as Model_User::is_username_unique. Is that possible and just not documented?
  • The validation documentation describes how to add custom validation. As an example, this (https://github.com/fuel/depot/blob/1.0/develop/fuel/modules/admin/classes/model/version.php) model in Depot uses a custom validation method.
  • Thanks for the example. Can I somehow combine the validation object returned by Model_User::validate with a validation object in my controller? I may want to do this for a "confirm password" field for example. I don't feel that the "confirm password" validation belongs in the model, so I'd like to have it in the controller.
  • No, you can't. There is only one validation object. You are however completely free on where you store your validation rules, they don't have to be in the model. I have an app that has a separate class that contains all custom rules that are used by multiple models. Some people make a base model that extends ORM\Model and contains the rules, and have their models extend that. There are lots of options. You have access to the object in your controller, so you can add as many extra fields and rules to it as you want before running the validation.
  • In case anyone has a similar question in the future, this is how I solved it: Model_User:
    <?php
        
    class Model_User extends \Orm\Model
    {
        protected static $_properties = array(
            'id', 'first_name', 'last_name', 'username', 'password', 'email',
        );
    
        public static function get_validation(\Validation $val)
        {
            $val->add_callable('\Model_User');
            $val->add_field('first_name', 'First Name', 'required|min_length[2]|max_length[50]');
            $val->add_field('last_name', 'Last Name', 'required|min_length[2]|max_length[50]');
            $val->add_field('username', 'Username', 'required|min_length[2]|max_length[50]');
            $val->add_field('password', 'Password', 'required|min_length[6]|max_length[100]');
            $val->add_field('email', 'Email Address', 'required|valid_email|min_length[5]|max_length[250]');
            return $val;
        }
    
        public static function _validation_unique_username($username, Model_User $user)
        {
            if ( ! $user->is_new() and $user->username === $username)
            {
                return true;
            }
    
            $exists = DB::select(DB::expr('COUNT(*) as total_count'))->from($user->table())
                    ->where('username', '=', $username)->execute()->get('total_count');
    
            return (bool) !$exists;
        }
    
        public static function _validation_unique_email($email, Model_User $user)
        {
            if ( ! $user->is_new() and $user->email === $email)
            {
                return true;
            }
    
            $exists = DB::select(DB::expr('COUNT(*) as total_count'))->from($user->table())->where('email', '=', $email)
                    ->execute()->get('total_count');
    
            return (bool) !$exists;
        }
    }
    
    In one of my controllers:
    if ('POST' == Input::method())
    {
        $user = Model_User::forge();
    
        $val = Validation::forge();
        $val = Model_User::get_validation($val);
        $val->field('username')->add_rule('unique_username', $user);
        $val->field('email')->add_rule('unique_email', $user);
        $val->add_field('password_confirm', 'Confirm Password', 'required|match_field[password]');
    
        if ($val->run())
        {
            $user->values(array(
                'first_name'           => Input::post('first_name'),
                'last_name'           => Input::post('last_name'),
                'username'            => Input::post('username'),
                'password'             => Input::post('password'),
                'email'                    => Input::post('email'),
                'activation_token' => Str::random('alnum', 40),
            ));
            $user->save();
            // ... do something after save
            // e.g. Response::redirect( Router::get('register_success') );
        }
        else
        {
            $errors = $val->error();
        }
    }
    

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

In this Discussion