<?php 
/** 
 * 
 * Models for redis! 
 *  
 * @author      Chema <[email protected]> 
 * @package     Core 
 * @version     0.1 
 * @license     GPL v3 
 *  
 * @todo 
 * On update only save fields that are modified. 
 * Fields validation, integer, varchar, email etc... 
 * Create relations hasone , hasmany... 
 * Fields filters, on read, on save. 
 * if value is array: use a list 'TABLENAME:PK:SETNAME' and save it in a common list or set 
 */ 
 
class Remodel { 
 
    /** 
     * fields with values 
     * @var array 
     */ 
    protected $_data = array(); 
 
    /** 
     * fields that has this model, if empty everything is allowed, recommended to set it. 
     * @var array 
     */ 
    protected $_fields = array(); 
 
    /** 
     * redis instance 
     * @var Predis 
     */ 
    protected $_redis;  
 
    /** 
     * common name used as redis key 
     * @var string 
     */ 
    protected $_table_name; 
 
    /** 
     * @var  string  PrimaryKey field name 
     */ 
    protected $_primary_key; 
 
    /** 
     * redis keys constants to concat 
     * DONOT change this if you had data already in the REDIS 
     */ 
    const NEXT_PK   = '_pk_';   // 'TABLENAME:_pk_' => stores the pk counter 
    const ALL       = '_all_';  // 'TABLENAME:ALL' => stores all the ids for this table 
    const KSEP      = ':';      // Key Separator used for any KEY 
     
    /** 
     * create object and connect 
     * @param Predis\Client $redis        connection or pipe 
     * @param array $redis_config configuration 
     * @param integer $pk          PK id to load. 
     */ 
    public function __construct($redis = NULL, array $redis_config = NULL , $pk = NULL ) 
    { 
        if($redis!==NULL) 
        { 
            $this->_redis = $redis; 
        } 
        else 
        { 
            try  
            { 
                $this->_redis = new Predis\Client($redis_config); 
            } 
            catch (Exception $e)  
            { 
                throw new Exception("Error connecting to redis: ".print_r($e,1), 1); 
            } 
        }  
 
        if (is_numeric($pk)) 
            $this->load($pk); 
    } 
 
    /** 
     * Create a new model instance. 
     * 
     *     $model = Remodel::factory($name); 
     * 
     * @param   string   model name 
     * @return  Model 
     */ 
    public static function factory($name) 
    { 
        $class = 'Model_'.$name; 
        if(class_exists($class)) 
        { 
            return new $class; 
        } 
        else 
        { 
            throw new Exception('Factory model not found: '.$name, 1); 
        } 
    } 
 
    /** 
     * creates a new item pk 
     * @param  Redis $pipe usage of pipe to make it faster optional  
     * @return boolean  
     */ 
    public function create($pipe = NULL) 
    { 
        $conn = ($pipe!==NULL)? $pipe:$this->_redis; 
 
        //getting the primary key, first we increase so there¡s no repeated id's 
        $this->pk($this->get_last_pk(TRUE)); 
       
        //save the data at redis 
        if ($this->update($conn)) 
        {             
            //save the primary in a global set 
            $conn->sadd($this->_table_name.self::KSEP 
                            .self::ALL, $this->pk()); 
            //end pipe 
            return TRUE; 
        } 
        //if save not success decrease  
        else 
        { 
            $this->_redis->decr($this->_table_name.self::KSEP.self::NEXT_PK); 
            return FALSE; 
        } 
    } 
 
    /** 
     * update the current data at redis 
     * @param  Redis $pipe usage of pipe to make it faster optional  
     * @return boolean        
     */ 
    public function update($pipe = NULL) 
    {         
        $conn = ($pipe!==NULL)? $pipe:$this->_redis; 
 
        //we add the pk to the array 
        $data = array_merge(array( $this->_primary_key => $this->pk() ),$this->_data); 
 
        return $conn->hmset($this->_table_name.self::KSEP.$this->pk(), $data);      
    } 
     
   /** 
    * deletes a key from the redis 
    * @param  integer $pk optional 
    * @return boolean       
    */ 
    public function delete($pk = NULL) 
    { 
        //if isnumeric we try to be friendly and use it as the index 
        if (!is_numeric($pk)) 
            $pk = $this->pk(); 
                 
        //remove the KEY from redis 
        $ret = $this->_redis->del($this->_table_name.self::KSEP.$pk); 
 
        //delete the PK from the global list 
        if ($ret>0) 
            $ret = $this->_redis->srem($this->_table_name.self::KSEP.self::ALL, $this->pk()); 
         
        $this->unload(); 
 
        return ($ret>0)? TRUE : FALSE; 
    } 
     
    /** 
     * updates or creates, shortcut 
     * @param  Redis $pipe usage of pipe to make it faster optional 
     */ 
    public function save($pipe = NULL) 
    { 
        //if index exists calls update if doesn't exists create 
        ($this->loaded())? $this->update($pipe):$this->create($pipe); 
    } 
     
    /** 
     * Just tells you if there's some data populated in the model using the primary 
     * @return boolean 
     */ 
    public function loaded() 
    { 
        return (array_key_exists($this->_primary_key, $this->_data)); 
    } 
     
    /** 
     * Unloads any data 
     */ 
    public function unload() 
    { 
        unset($this->_data); 
    } 
 
    /** 
     * loads values 
     * @param  inteeger PK to load data   
     * @return boolean 
     */ 
    public function load($pk = NULL) 
    { 
        if (is_numeric($pk)) 
        { 
            $data = $this->_redis->hgetall($this->_table_name.self::KSEP.$pk); 
            if (!empty($data)) 
            { 
                $this->values($data); 
                return TRUE; 
            } 
        } 
        return FALSE; 
    } 
 
    /** 
     * loads/get an array of values into the model 
     * @param  array $data   
     * @return boolean/array 
     */ 
    public function values(array $data = NULL) 
    { 
        if (is_array($data)) 
        { 
            $this->_data = $data; 
            return TRUE; 
        } 
        else return $this->_data;         
    } 
 
    /** 
     * retrieves models for the specified range 
     * lrange over TABLENAME:ALL 
     * @param  integer/array $elements if array return those elements, if not = from 
     * @param  integer $stop  to 
     * @return array   remodel 
     */ 
    public function select($elements = 0, $stop = 9) 
    { 
        if (!is_array($elements)) 
            $elements = $this->get_all_pk($elements,$stop); 
 
        $ret = array(); 
        
        // return array of Models 
        if(count($elements) >= 1) 
        { 
            //@todo use of a pipe to improve load? 
            //get values in a pipe put them in array and push them to the model 
            $class = get_class($this);//using same model 
            foreach ($elements as $element) 
            { 
                $model = new $class; 
                $model->load($element); 
                if ($model->loaded())  
                    $ret[]= $model; 
                unset($model); 
            } 
        } 
 
        return $ret; 
    } 
 
    /** 
     * returns the amount of elements in the redis set for that table 
     * @param string set_name 
     * @return integer 
     */ 
    public function count($set_name = NULL) 
    {         
        //count all 
        if ($set_name === NULL) 
            return $this->_redis->scard($this->_table_name.self::KSEP.self::ALL); 
        else//count set 
            return $this->_redis->scard($this->_table_name.self::KSEP.$set_name);    
    } 
     
    /** 
     * set/get the primary key value of the object 
     * @return integer id 
     */ 
    public function pk($pk = NULL) 
    { 
        //acting as setter 
        if (is_numeric($pk)) 
            $this->_data[$this->_primary_key] = $pk; 
 
        return ($this->loaded())? $this->_data[$this->_primary_key] : FALSE; 
    } 
 
    /** 
     * retrieve the PK for the list of this table 
     * @param  integer $start from 
     * @param  integer $stop  to 
     * @return array  
     */ 
    public function get_all_pk($start = 0, $stop = 9) 
    { 
        //todo improve this!! 
        return $this->_redis->sort($this->_table_name.self::KSEP.self::ALL,array('by'=> 'nosort','limit' => array($start,$stop))); 
    } 
 
    /** 
     * retrieve the last PK used for the table 
     * @param bool $increase if set to true we increase the redis PK before. 
     * @return integer 
     */ 
    public function get_last_pk($increase = FALSE) 
    { 
        if ($increase === TRUE) 
            $this->_redis->incr($this->_table_name.self::KSEP.self::NEXT_PK); 
 
        return $this->_redis->get($this->_table_name.self::KSEP.self::NEXT_PK); 
    } 
         
    /** 
     * Magic methods to set get 
     */ 
    public function __set($name, $value) 
    { 
        //check if fields exists in the `model` 
        if ( empty($this->_fields) OR in_array($name, $this->_fields) ) 
        { 
            $this->_data[$name] = $value; 
        } 
        else throw new Exception($name.' does not exist in the model.', 1); 
         
    } 
 
    public function __get($name) 
    { 
        return (array_key_exists($name, $this->_data)) ? $this->_data[$name] : NULL; 
    } 
 
    public function __isset($name) 
    { 
        return isset($this->_data[$name]); 
    } 
 
    public function __unset($name) 
    { 
        unset($this->_data[$name]); 
    } 
     
}
 
 |