TIP: Use Markdown or, <pre> for multi line code blocks / <code> for inline code.
Custom Schema with Auth Module
  • Hi! I was trying to extend the Auth Module so I can use my custom Schema (with same relations but differents names in tables and columns)
    I wrote this:
    (basically it's a transparent extenstion for Model_User where we can set the table name, PK, and colum names...)

    <?php defined('SYSPATH') or die('No direct access allowed.');
    
    class Model_User extends Model_Auth_User {
    
        protected $_table_name = 'strange_tablename';
    
        protected $_primary_key = 'strange_pkey';
    
        protected $_db_group = 'default'; 
    
        protected $_has_many = array(
            'user_tokens' => array('model' => 'user_token', 'foreign_key' => 'user_id'),
            'roles'       => array('model' => 'role', 'foreign_key' => 'user_id', 'through' => 'roles_users'),
        );
    
        protected $_columns = array(
            'username' => 'columna_username',
            'password' => 'columna_password',
            'email' => 'columna_email', 
            'logins' => 'columna_logins',
            'last_login' => 'columna_last_login',       
        );
    
        public $password_confirm = 'password_confirm';
    
        /*  
         *  ==================================================
         *  ----------------- end of Schema config ------------------ 
         *  ==================================================  
         */
    
        public static function column($key)
        {
            return $this->_columns[$key];
        }
    
        /**
         * Rules for the user model. Because the password is _always_ a hash
         * when it's set,you need to run an additional not_empty rule in your controller
         * to make sure you didn't hash an empty string. The password rules
         * should be enforced outside the model or with a model helper method.
         *
         * @return array Rules
         */
        public function rules()
        {
            return array(
                Model_User::column('username') => array(
                    array('not_empty'),
                    array('min_length', array(':value', 4)),
                    array('max_length', array(':value', 32)),
                    array('regex', array(':value', '/^[-\pL\pN_.]++$/uD')),
                    array(array($this, 'username_available'), array(':validation', ':field')),
                ),
                Model_User::column('password') => array(
                    array('not_empty'),
                ),
                Model_User::column('email') => array(
                    array('not_empty'),
                    array('min_length', array(':value', 4)),
                    array('max_length', array(':value', 127)),
                    array('email'),
                    array(array($this, 'email_available'), array(':validation', ':field')),
                ),
            );
        }
    
        /**
         * Filters to run when data is set in this model. The password filter
         * automatically hashes the password when it's set in the model.
         *
         * @return array Filters
         */
        public function filters()
        {
            return array(
                Model_User::column('password') => array(
                    array(array(Auth::instance(), 'hash'))
                )
            );
        }
    
        /**
         * Labels for fields in this model
         *
         * @return array Labels
         */
        public function labels()
        {
            return array(
                Model_User::column('username')         => 'username',
                Model_User::column('email')            => 'email address',
                Model_User::column('password')         => 'password',
            );
        }
    
        /**
         * Complete the login for a user by incrementing the logins and saving login timestamp
         *
         * @return void
         */
        public function complete_login()
        {
            if ($this->_loaded)
            {
                // Update the number of logins
                $this->{Model_User::column('logins')} = new Database_Expression(Model_User::column('loigins').' + 1');
    
                // Set the last login date
                $this->{Model_User::column('last_login')} = time();
    
                // Save the user
                $this->update();
            }
        }
    
        /**
         * Does the reverse of unique_key_exists() by triggering error if username exists.
         * Validation callback.
         *
         * @param   Validation  Validation object
         * @param   string      Field name
         * @return  void
         */
        public function username_available(Validation $validation, $field)
        {
            if ($this->unique_key_exists($validation[$field], Model_User::column('username')))
            {
                $validation->error($field, 'username_available', array($validation[$field]));
            }
        }
    
        /**
         * Does the reverse of unique_key_exists() by triggering error if email exists.
         * Validation callback.
         *
         * @param   Validation  Validation object
         * @param   string      Field name
         * @return  void
         */
        public function email_available(Validation $validation, $field)
        {
            if ($this->unique_key_exists($validation[$field], Model_User::column('email')))
            {
                $validation->error($field, 'email_available', array($validation[$field]));
            }
        }
    
        /**
         * Tests if a unique key value exists in the database.
         *
         * @param   mixed    the value to test
         * @param   string   field name
         * @return  boolean
         */
        public function unique_key_exists($value, $field = NULL)
        {
            if ($field === NULL)
            {
                // Automatically determine field by looking at the value
                $field = $this->unique_key($value);
            }
    
            return (bool) DB::select(array('COUNT("*")', 'total_count'))
                ->from($this->_table_name)
                ->where($field, '=', $value)
                ->where($this->_primary_key, '!=', $this->pk())
                ->execute($this->_db)
                ->get('total_count');
        }
    
        /**
         * Allows a model use both email and username as unique identifiers for login
         *
         * @param   string  unique value
         * @return  string  field name
         */
        public function unique_key($value)
        {
            return Valid::email($value) ? Model_User::column('email') : Model_User::column('username');
        }
    
        /**
         * Password validation for plain passwords.
         *
         * @param array $values
         * @return Validation
         */
        public static function get_password_validation($values)
        {
            return Validation::factory($values)
                ->rule(Model_User::column('password'), 'min_length', array(':value', 8))
                ->rule($this->password_confirm, 'matches', array(':validation', ':field', Model_User::column('password')));
        }
    
        /**
         * Create a new user
         *
         * Example usage:
         * ~~~
         * $user = ORM::factory('user')->create_user($_POST, array(
         *  'username',
         *  'password',
         *  'email',
         * );
         * ~~~
         *
         * @param array $values
         * @param array $expected
         * @throws ORM_Validation_Exception
         */
        public function create_user($values, $expected)
        {
            // Validation for passwords
            $extra_validation = Model_User::get_password_validation($values)
                ->rule(Model_User::column('password'), 'not_empty');
    
            return $this->values($values, $expected)->create($extra_validation);
        }
    
        /**
         * Update an existing user
         *
         * [!!] We make the assumption that if a user does not supply a password, that they do not wish to update their password.
         *
         * Example usage:
         * ~~~
         * $user = ORM::factory('user')
         *  ->where('username', '=', 'kiall')
         *  ->find()
         *  ->update_user($_POST, array(
         *      'username',
         *      'password',
         *      'email',
         *  );
         * ~~~
         *
         * @param array $values
         * @param array $expected
         * @throws ORM_Validation_Exception
         */
        public function update_user($values, $expected = NULL)
        {
            if (empty($values[Model_User::column('password')]))
            {
                unset($values[Model_User::column('password')], $values[$this->password_confirm]);
            }
    
            // Validation for passwords
            $extra_validation = Model_User::get_password_validation($values);
    
            return $this->values($values, $expected)->update($extra_validation);
        }
    
    
    
    } // End User Model
    

    I have not tested it yet, but I think it should work...

    the problem is in Model_User_Token, because it has a constructor...
    So, if redefine this constructor, then when I call parent::_construct(), the "old" constructor which uses hardcoded colum names it's executed anyway.
    I think I'm walking the wrong way, does somebody have a better solution?

    Thanks!

  • I'll to explain, briefly, my "solution":

    Create an associative array with the names of the columns in our DB:

    protected $_columns = array(
            'username' => 'columna_username',
            'password' => 'columna_password',
            'email' => 'columna_email', 
            'logins' => 'columna_logins',
            'last_login' => 'columna_last_login',       
        );
    

    Create a static function to access $_columns array (just because I think is nicer...)

    public static function column($key)
        {
            return $this->_columns[$key];
        }
    

    Now, instead of using 'username' we use Model_User::column('username')
    and instead of using
    $this->logins
    we use:
    $this->{Model_User::column('logins')}

    I hope that helps if to read the whole code is annoying.

  • I found 4 solutions for this problem (bypass __construct from Model_Auth_User_Token:
    1)

    // use this instead of parent::__construct($id) in Model_User_Token  
    ORM::__construct($id);
    

    2)

    // use this instead of parent::__construct($id) in Model_User_Token  
    call_user_func(array(get_parent_class(get_parent_class($this)), '__construct'), $id);  
    

    3)

    // don't use transparent extension and instead of:   
    class Model_User_Token extends Model_Auth_User_Token {
    // use
    class Model_User_Token extends ORM  
    

    4)

    // don't use extension at all, create a new Auth Module with my custom Schema and use that
    

    What do you recommend?
    (anyway, I feel this solution very ugly, maybe there's a better way to use custom Schema with Auth Module, with Zend_Auth we can create an adapter with our custom table names and column names)