Skip to content

Instantly share code, notes, and snippets.

@taikimen
Created October 18, 2010 17:09
Show Gist options
  • Save taikimen/632600 to your computer and use it in GitHub Desktop.
Save taikimen/632600 to your computer and use it in GitHub Desktop.
My favorite Kohana-ORM extends class.
This gist provides 3 additional function for Kohana-ORM.
1) Cache column lists.
Kohana's original ORM execute "show column" query every call time.
I think that the column lists should be cached for long time like CakePHP.
2) Has many relation with Left Join query.
By Kohana's original ORM , has many relation query do like this.
--
$user->childs->find_all();
foreach( $model->childs as $child)
{
print $child->name;
}
--
But I want do like this.
--
$user->with('child')->find_all();
foreach($user as $row)
{
print $user->child->name;
}
3) Cache row.
add save_with_cache(), find_by_cache().
Cache storage is Plagable.
If you are using Memcached library, you can get caches by multi keys.
<?php defined('SYSPATH') or die('No direct script access.');
class ORM extends Kohana_ORM {
// date_created is the column used for storing the creation date.
// Use TRUE to store a timestamp
protected $_created_column = array('column' => 'created' ,'format' => 'Y-m-d H:i:s');
// date_modified is the column used for storing the modified date.
// In this case, a string specifying a date() format is used
protected $_updated_column = array('column' => 'modified' ,'format' => 'Y-m-d H:i:s');
protected $_reload_on_wakeup = FALSE;
protected $_use_cache_type = 'memcache';
protected $_use_cache_expire = 600;
protected $_sleep_properties = array();
/**
* Allows serialization of only the object data and state, to prevent
* "stale" objects being unserialized, which also requires less memory.
*
* @return array
*/
public function __sleep()
{
// Store only information about the object
$default = array('_object_name', '_object', '_changed', '_loaded', '_saved', '_sorting');
return array_merge($default,$this->_sleep_properties);
}
/**
* Returns the values of this object as an array, including any related one-one
* models that have already been loaded using with()
*
* @return array
*/
public function as_array()
{
$object = array();
foreach ($this->_object as $key => $val)
{
// Call __get for any user processing
$object[$key] = $this->__get($key);
}
foreach ($this->_related as $key => $model)
{
// Include any related objects that are already loaded
$object[$key] = $model->as_array();
}
foreach($this->_sleep_properties as $var )
{
if(! property_exists($this,$var))
{
continue;
}
if( is_subclass_of($this->$var,"ORM"))
{
$object[$var] = $this->$var->as_array();
}
elseif (is_array($this->$var))
{
$object[$var] = $this->$var;
}
}
return $object;
}
/**
* Saves the current object.
*
* @chainable
* @return ORM
*/
public function save()
{
if (empty($this->_changed))
return $this;
$data = array();
foreach ($this->_changed as $column)
{
// Compile changed data
$data[$column] = $this->_object[$column];
}
if ( ! $this->empty_pk() AND ! isset($this->_changed[$this->_primary_key]))
{
// Primary key isn't empty and hasn't been changed so do an update
if (is_array($this->_updated_column))
{
// Fill the updated column
$column = $this->_updated_column['column'];
$format = $this->_updated_column['format'];
$data[$column] = $this->_object[$column] = ($format === TRUE) ? time() : date($format);
}
$query = DB::update($this->_table_name)
->set($data)
->where($this->_primary_key, '=', $this->pk())
->execute($this->_db);
// Object has been saved
$this->_saved = TRUE;
}
else
{
if (is_array($this->_created_column))
{
// Fill the created column
$column = $this->_created_column['column'];
$format = $this->_created_column['format'];
$data[$column] = $this->_object[$column] = ($format === TRUE) ? time() : date($format);
}
if (is_array($this->_updated_column))
{
// Fill the updated column
$column = $this->_updated_column['column'];
$format = $this->_updated_column['format'];
$data[$column] = $this->_object[$column] = ($format === TRUE) ? time() : date($format);
}
$result = DB::insert($this->_table_name)
->columns(array_keys($data))
->values(array_values($data))
->execute($this->_db);
if ($result)
{
if ($this->empty_pk())
{
// Load the insert id as the primary key
// $result is array(insert_id, total_rows)
$this->_object[$this->_primary_key] = $result[0];
}
// Object is now loaded and saved
$this->_loaded = $this->_saved = TRUE;
}
}
if ($this->_saved === TRUE)
{
// All changes have been saved
$this->_changed = array();
}
return $this;
}
/**
* begin transaction
*/
public function begin(){
if($this->_db->in_tx === FALSE){
DB::query(DATABASE::UPDATE,'SET AUTOCOMMIT=0')->execute($this->_db);
DB::query(DATABASE::UPDATE,'BEGIN')->execute($this->_db);
}
$this->_db->in_tx = TRUE;
}
/**
* commit transaction
*/
public function commit() {
if($this->_db->in_tx === TRUE){
DB::query(DATABASE::UPDATE,'COMMIT')->execute($this->_db);
DB::query(DATABASE::UPDATE,'SET AUTOCOMMIT=1')->execute($this->_db);
}
$this->_db->in_tx = FALSE;
}
/**
* rollback transaction
*/
public function rollback(){
if($this->_db->in_tx === TRUE){
DB::query(DATABASE::UPDATE,'ROLLBACK')->execute($this->_db);
DB::query(DATABASE::UPDATE,'SET AUTOCOMMIT=1')->execute($this->_db);
}
$this->_db->in_tx = FALSE;
}
/**
* Binds another one-to-one object to this model. One-to-one objects
* can be nested using 'object1:object2' syntax
*
* @param string target model to bind to
* @return void
*/
public function with($target_path)
{
if (isset($this->_with_applied[$target_path]))
{
// Don't join anything already joined
return $this;
}
// Split object parts
$aliases = explode(':', $target_path);
$target = $this;
foreach ($aliases as $alias)
{
// Go down the line of objects to find the given target
$parent = $target;
$target = $parent->_related($alias);
if ( ! $target)
{
// Can't find related object
return $this;
}
}
// Target alias is at the end
$target_alias = $alias;
// Pop-off top alias to get the parent path (user:photo:tag becomes user:photo - the parent table prefix)
array_pop($aliases);
$parent_path = implode(':', $aliases);
if (empty($parent_path))
{
// Use this table name itself for the parent path
$parent_path = $this->_table_name;
}
else
{
if( ! isset($this->_with_applied[$parent_path]))
{
// If the parent path hasn't been joined yet, do it first (otherwise LEFT JOINs fail)
$this->with($parent_path);
}
}
// Add to with_applied to prevent duplicate joins
$this->_with_applied[$target_path] = TRUE;
// Use the keys of the empty object to determine the columns
foreach (array_keys($parent->_object) as $column)
{
$name = $parent_path.".".$column;
$alias = $column;
// Add the prefix so that load_result can determine the relationship
$this->select(array($name, $alias));
}
// Use the keys of the empty object to determine the columns
foreach (array_keys($target->_object) as $column)
{
$name = $target_path.'.'.$column;
$alias = $target_path.':'.$column;
// Add the prefix so that load_result can determine the relationship
$this->select(array($name, $alias));
}
if (isset($parent->_belongs_to[$target_alias]))
{
// Parent belongs_to target, use target's primary key and parent's foreign key
$join_col1 = $target_path.'.'.$target->_primary_key;
$join_col2 = $parent_path.'.'.$parent->_belongs_to[$target_alias]['foreign_key'];
}
elseif (isset($parent->_has_many[$target_alias]))
{
// Parent belongs_to target, use target's primary key and parent's foreign key
$join_col1 = $parent_path.'.'.$parent->_primary_key;
$join_col2 = $target_path.'.'.$parent->_has_many[$target_alias]['foreign_key'];
}
else
{
// Parent has_one target, use parent's primary key as target's foreign key
$join_col1 = $parent_path.'.'.$parent->_primary_key;
$join_col2 = $target_path.'.'.$parent->_has_one[$target_alias]['foreign_key'];
}
// Join the related object into the result
$this->join(array($target->_table_name, $target_path), 'LEFT')->on($join_col1, '=', $join_col2);
return $this;
}
/**
* Returns an ORM model for the given one-one related alias
*
* @param string alias name
* @return ORM
*/
protected function _related($alias)
{
if (isset($this->_related[$alias]))
{
return $this->_related[$alias];
}
elseif (isset($this->_has_one[$alias]))
{
return $this->_related[$alias] = ORM::factory($this->_has_one[$alias]['model']);
}
elseif (isset($this->_belongs_to[$alias]))
{
return $this->_related[$alias] = ORM::factory($this->_belongs_to[$alias]['model']);
}
elseif (isset($this->_has_many[$alias]))
{
return $this->_related[$alias] = ORM::factory($this->_has_many[$alias]['model']);
}
else
{
return FALSE;
}
}
/**
* Proxy method to Database list_columns.
*
* @return array
*/
public function list_columns()
{
$cache = Cache::instance('file');
$cache_id = __METHOD__."|".$this->_table_name;
if($ret = $cache->get($cache_id)){
return $ret;
}
// Proxy to database
$ret = $this->_db->list_columns($this->_table_name);
$cache->set($cache_id,$ret,60 * 60 * 24);
return $ret;
}
/**
* キャッシュからPKでfindする
* @param $id primary key
*/
public function find_by_cache($id)
{
$cache = Cache::instance($this->_use_cache_type);
$cache_id = $this->get_cache_id($id);
if($ret = $cache->get($cache_id)){
return $ret;
}
$ret = $this->find($id);
$cache->set($cache_id,$ret,CONFIG::$SHORT_CACHE_TIME);
return $ret;
}
/**
* PKでキャッシュをupdateする
* @param $id primary key
*/
public function save_with_cache()
{
$cache = Cache::instance($this->_use_cache_type);
$cache_id = $this->get_cache_id();
if($ret = $this->save()){
$cache->set($cache_id,$this,CONFIG::$SHORT_CACHE_TIME);
return $ret;
}
return $ret;
}
/**
* PK検索用のキャッシュIDを返却
* @return string $cache_id
*/
protected function get_cache_id($pk = null)
{
if(is_null($pk))
{
return $this->_table_name."|".$this->pk();
}
else
{
return $this->_table_name."|".$pk;
}
}
public function find_by_cache_map($id_array)
{
if(empty($id_array))
{
return array();
}
if(is_string($id_array))
{
$id_array = array($id_array);
}
$cache = Cache::instance('memcache');
$cache_id_c2i = array();
$cache_id_i2c = array();
$cache_id_list = array();
$cached = array();
foreach($id_array as $id)
{
$cache_id = $this->get_cache_id($id);
$cache_id_c2i[$cache_id] = $id;
$cache_id_i2c[$id] = $cache_id;
$cache_id_list[] = $cache_id;
$cached[$cache_id] = null;
}
// search cache
$cached_data = $cache->get_multi($cache_id_list,NULL);
if($cached_data)
{
$cached = array_merge($cached,$cached_data);
}
$remain_ids = array();
// if cache empty, re select
foreach($cached as $key => $data)
{
if(empty($data))
{
$remain_ids[] = $cache_id_c2i[$key];
}
}
$select_data = null;
if(!empty($remain_ids))
{
$to_cache_data = array();
$pk = $this->_primary_key;
$select_data = $this->where($pk,'IN',$remain_ids)->find_all()->as_array();
foreach($select_data as $saved)
{
$to_cache_data[$cache_id_i2c[$saved->$pk]] = $saved;
}
$cache->set_multi($to_cache_data,CONFIG::$SHORT_CACHE_TIME);
//print_r(array_keys($to_cache_data));
$cached = array_merge($cached,$to_cache_data);
}
$result = array();
foreach($cached as $key => $val)
{
$result[$cache_id_c2i[$key]] = $val;
}
return $result;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment