Skip to content

Instantly share code, notes, and snippets.

@tanakahisateru
Created August 4, 2011 06:24
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tanakahisateru/1124597 to your computer and use it in GitHub Desktop.
Save tanakahisateru/1124597 to your computer and use it in GitHub Desktop.
Model assosiation support for CodeIgniter
<?php if ( ! defined('BASEPATH')) exit('Direct script access not allowed');
/**
* This library bakes related object(s) into object field (or array key) using CI's ActiveRecord.
*
* @author Hisateru Tanaka
*
* Annual issue:
* Explicit sql programing is too redundant and too difficult to describe about relations.
*
* $this->db->where('id', $row->parent_id);
* $r = $this->db->get('parents_table');
* $row->parent = $r->row();
*
* The solution:
* Implicit sql building from RDB constraint helps our coding to be smarter.
*
* Usage:
* $this->load->('relations');
* $this->relations->ref($row, 'parent', 'parents_table', 'parent_id');
*
* [before]
* stdClass Object
* (
* ...
* [parent_id] => 1
* )
*
* [after]
* stdClass Object
* (
* ...
* [parent_id] => 1,
* [parent] => stdClass Object
* (
* [id] => 1
* ...
* )
* )
*
* Supported features:
* - Many to One : ref()
* - One to Many : has_many()
* - Many to Many : medium() and has_many_ref()
* - Execution for all rows
* - Both of Object and Array row type supported
* - Automatic removal of redundant forward key
* - Custom SQL sentence using CI's ActiveRecord
*
* Remarks:
* This library is not a O/R mapping framework! Below features are not supported.
* - Lazy fetching
* - Automatic transaction scope management
* - Instance pooling and caching
* - SQL optimization
* - Predefined inclusive mapping
*
* But one important advantage than huge O/R mapper is that it doesn't take any startup cost.
* You don't have to pay any more for performance and configuration than now.
*/
class Relations {
var $result_type = 'object';
var $remove_fk = FALSE;
var $queries = array();
var $queries_medium = array();
var $medium_def = NULL;
var $remain_fixture = FALSE;
function _initdb(){
if(!isset($this->db)){
$CI =& get_instance();
$CI->load->database();
$this->db =& $CI->db;
}
}
function _clear_fixtures() {
if(!$this->remain_fixture) {
$this->queries = array();
$this->queries_medium = array();
$this->medium_def = NULL;
}
}
function _do_arquery(&$queries) {
foreach($queries as $eq) {
eval('$this->db->' . $eq . ';');
}
}
function &_get_from(&$obj, $field)
{
if(is_object($obj)) {
return $obj->$field;
}
else if(is_array($obj)) {
return $obj[$field];
}
else {
return NULL;
}
}
function _set_to(&$obj, $field, &$value)
{
if(is_object($obj)) {
$obj->$field = $value;
}
else if(is_array($obj)) {
$obj[$field] = $value;
}
}
function _del_field(&$obj, $field)
{
if(is_object($obj) && isset($obj->$field)) {
unset($obj->$field);
}
else if(is_array($obj) && array_key_exists($field, $obj)) {
unset($obj[$field]);
}
}
/*-- preparation functions ----------------------------------------------------*/
/**
* Sets result row type as 'object' or 'array'.
* This setting remains until next call of result_as.
*/
function result_as($type)
{
$this->result_type = $type;
}
/**
* Revoves forward key which used to connect instances each other.
* This setting remains until next call of replace_with_key.
*/
function replace_with_key($flag=TRUE)
{
$this->remove_fk = $flag;
}
/**
* Appends ActiveRecord command executed before relation baking.
* This setting would be cleared on the next call of execution function.
* @param command would be sent to "eval" fnction with "$this->db->" prefix.
*/
function arquery($command)
{
$this->queries[] = $command;
}
/**
* Shortcut for $this->arquery('select("...")');
* Recommended: This is safer than arquery.
*/
function select($select = '*', $protect_identifiers = TRUE)
{
$this->arquery('select("' . $select . '",' . ($protect_identifiers ? 'TRUE' : 'FALSE') . ')');
}
/**
* Shortcut for $this->arquery('order_by("...")');
* Recommended: This is safer than arquery.
*/
function order_by($orderby, $direction = '')
{
$this->arquery('order_by("' . $orderby . '","' . $direction . '")');
}
/**
* Appends ActiveRecord command executed before fetching medium table.
* This setting would be cleared on the next call of execution function.
*/
function medium_arquery($command){
$this->queries_medium[] = $command;
}
/**
* Defines a medium table specification for next relation balking.
* This setting would be cleared on the next call of execution function.
*/
function medium($table, $fk_to_mine, $fk_to_yours) {
$this->medium_def = array($table, $fk_to_mine, $fk_to_yours);
}
/*-- execution functions ----------------------------------------------------*/
/**
* This function injects a related object into a row.
* It provides Many to One relation.
*/
function ref(&$obj, $field, $table, $fk_mine, $pk_yours='id')
{
$this->_initdb();
$this->_do_arquery($this->queries);
$q = $this->db->get_where($table, array($pk_yours => $this->_get_from($obj, $fk_mine)));
$this->_set_to($obj, $field, $q->row(0, $this->result_type));
if($this->remove_fk) {
$this->_del_field($obj, $fk_mine);
}
$this->_clear_fixtures();
}
/**
* This is array version of ref.
*/
function ref_all(&$obj_arr, $field, $table, $fk_mine, $pk_yours='id')
{
$this->remain_fixture = TRUE;
foreach(array_keys($obj_arr) as $i) {
$this->ref($obj_arr[$i], $field, $table, $fk_mine, $pk_yours);
}
$this->remain_fixture = FALSE;
$this->_clear_fixtures();
}
/**
* This function injects directly related object(s) as array into a row.
* It provides One to Many relation.
*/
function has_many(&$obj, $field, $table, $fk_yours, $pk_mine='id')
{
$this->_initdb();
$this->_do_arquery($this->queries);
$q = $this->db->get_where($table, array($fk_yours => $this->_get_from($obj, $pk_mine)));
$r = $q->result($this->result_type);
if($this->remove_fk) {
foreach(array_keys($r) as $i) {
$this->_del_field($r[$i], $fk_yours);
}
}
$this->_set_to($obj, $field, $r);
$this->_clear_fixtures();
}
/**
* This is array versioned of has_many.
*/
function has_many_all(&$obj_arr, $field, $table, $fk_yours, $pk_mine='id')
{
$this->remain_fixture = TRUE;
foreach(array_keys($obj_arr) as $i) {
$this->has_many($obj_arr[$i], $field, $table, $fk_yours, $pk_mine);
}
$this->remain_fixture = FALSE;
$this->_clear_fixtures();
}
/**
* This function injects medium table referencing object(s) as array into a row.
* It provides Many to Many relation.
*/
function has_many_ref(&$obj, $field, $table, $pk_mine='id', $pk_yours='id')
{
$this->_initdb();
$this->_do_arquery($this->queries_medium);
list($medium_table, $fk_to_mine, $fk_to_yours) = $this->medium_def;
$this->db->select($fk_to_yours);
$q = $this->db->get_where($medium_table, array($fk_to_mine => $this->_get_from($obj, $pk_mine)));
$m = $q->result();
$rels = array();
foreach($m as $rel) {
$rels[] = $rel->$fk_to_yours;
}
$this->_do_arquery($this->queries);
$q = $this->db->where_in($pk_yours, $rels);
$q = $this->db->get($table);
$this->_set_to($obj, $field, $q->result($this->result_type));
if(!$this->remain_fixture) {
$this->_clear_fixtures();
}
}
/**
* This is array version of has_many_ref.
*/
function has_many_ref_all(&$obj_arr, $field, $table, $pk_mine='id', $pk_yours='id')
{
$this->remain_fixture = TRUE;
foreach(array_keys($obj_arr) as $i) {
$this->has_many_ref($obj_arr[$i], $field, $table, $pk_mine, $pk_yours);
}
$this->remain_fixture = FALSE;
$this->_clear_fixtures();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment