Created
February 12, 2013 10:23
-
-
Save neo22s/4761414 to your computer and use it in GitHub Desktop.
remodel, PHP models for redis
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
//main class to extend usage | |
require 'remodel.php'; | |
//loading library predis: https://github.com/nrk/predis | |
require 'predis-0.8/autoload.php'; | |
Predis\Autoloader::register(); | |
///MODEL EXAMPLE | |
class Model_Post extends Remodel { | |
protected $_table_name = 'posts'; | |
protected $_primary_key = 'id_post'; | |
//NOT MANDATORY but recommended | |
protected $_fields = array( 'id_post', | |
'title', | |
'description', | |
'created', | |
'status', | |
); | |
} | |
//CREATE: | |
$post = new Model_Post(); | |
$post->title = 'test title'.time(); | |
$post->description = 'test description'; | |
$post->created = time(); | |
$post->status = 1; | |
$post->save(); | |
print_r($post->values()); | |
//GET an specific model | |
$post = new Model_Post(); | |
$post->load(1); | |
print_r($post->values()); | |
//UPDATE current needs to be loaded before | |
$post->title = 'title updated'; | |
$post->status = 0; | |
$post->save(); | |
print_r($post->values()); | |
//DELETE current | |
$post->delete(); | |
//GET many | |
$post = new Model_Post(); | |
$posts = $post->select(0,3); | |
foreach ($posts as $p) | |
print_r($p->values()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* | |
* Models for redis! | |
* | |
* @author Chema <chema@garridodiaz.com> | |
* @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]); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment