Created
October 18, 2010 17:09
-
-
Save taikimen/632600 to your computer and use it in GitHub Desktop.
My favorite Kohana-ORM extends class.
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
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. |
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 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