summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDracony <draconyster@gmail.com>2013-01-04 11:24:28 +0200
committerDracony <draconyster@gmail.com>2013-01-04 11:24:28 +0200
commitf011e1d14d442cb9572e777513c8972c76f3cfe5 (patch)
tree7bd4aec6b92e62dbf0921a3ada97bbf79bc23f64
parent6fac740b801bfdf36ec8079a34efa3e7c0a51db2 (diff)
downloadPHPixie-f011e1d14d442cb9572e777513c8972c76f3cfe5.zip
PHPixie-f011e1d14d442cb9572e777513c8972c76f3cfe5.tar.gz
PHPixie-f011e1d14d442cb9572e777513c8972c76f3cfe5.tar.bz2
Speed improvement to getting model name
-rw-r--r--modules/orm/classes/orm.php1158
1 files changed, 580 insertions, 578 deletions
diff --git a/modules/orm/classes/orm.php b/modules/orm/classes/orm.php
index 03549d1..3f2dc65 100644
--- a/modules/orm/classes/orm.php
+++ b/modules/orm/classes/orm.php
@@ -1,579 +1,581 @@
-<?php
-
-/**
- * ORM allows you to access database items and their relationships in an OOP manner,
- * it is easy to setup and makes a lot of use of naming convention.
- *
- * @method mixed limit(int $limit = null) Set number of rows to return.
- * Without arguments returns current limit, returns self otherwise.
- *
- * @method mixed offset(int $offset = null) Set the offset for the first row in result.
- * Without arguments returns current offset, returns self otherwise.
- *
- * @method mixed orderby(string $column, string $dir) Adds a column to ordering parameters
- *
- * @method mixed where(mixed $key, mixed $operator = null, mixed $val = null) behaves just like Query_Database::where()
- *
- * @see Query_Database::where()
- */
-class ORM {
-
- /**
- * Specifies which table the model will use, can be overridden
- * @var string
- * @access public
- */
- public $table = null;
-
- /**
- * Specifies which connection the model will use, can be overridden
- * but a model can have relationships only with models utilizing the same connection
- * @var string
- * @access public
- */
- public $connection = 'default';
-
- /**
- * Specifies which column is treated as PRIMARY KEY
- * @var string
- * @access public
- */
- public $id_field='id';
-
- /**
- * You can define 'Belongs to' relationships buy changing this array
- * @var array
- * @access protected
- */
- protected $belongs_to=array();
-
- /**
- * You can define 'Has one' relationships buy changing this array
- * @var array
- * @access protected
- */
- protected $has_one = array();
-
- /**
- * You can define 'Has many' relationships buy changing this array
- * @var array
- * @access protected
- */
- protected $has_many = array();
-
- /**
- * An instance of the database connection
- * @var DB
- * @access private
- */
- private $db;
-
- /**
- * Current row returned by the database
- * @var array
- * @access protected
- */
- protected $_row = array();
-
- /**
- * Associated query builder
- * @var Query_Database
- * @access public
- */
- public $query;
-
- /**
- * A flag whether the row was loaded from the database
- * @var boolean
- * @access private
- */
- private $_loaded = false;
-
- /**
- * The name of the model
- * @var string
- * @access public
- */
- public $model_name;
-
- /**
- * Cached properties
- * @var array
- * @access private
- */
- private $_cached = array();
-
- /**
- * Constructs the model. To use ORM it is enough to
- * just create a model like this:
- * <code>
- * class Fairy_Model extends ORM { }
- * </code>
- * By default it will assume that the name of your table
- * is the plural form of the models' name, the PRIMARY KEY is id,
- * and will use the 'default' connection. This behaviour is easy to be
- * changed by overriding $table, $id and $db properties.
- *
- * @return void
- * @access public
- * @ see $table
- * @ see $id
- * @ see $db
- */
- public function __construct() {
- $this->query = DB::instance($this->connection)->query('select');
- $this->model_name=strtolower(preg_replace('#_Model$#i', '', get_class($this)));
- if ($this->table == null)
- $this->table = ORM::plural($this->model_name);
- $this->query->table($this->table);
-
- foreach(array('belongs_to', 'has_one', 'has_many') as $rels) {
- $normalized=array();
- foreach($this->$rels as $key => $rel) {
- if (!is_array($rel)) {
- $key = $rel;
- $rel=array();
- }
- $normalized[$key]=$rel;
- if (!isset($rel['model'])) {
- $rel['model']=$normalized[$key]['model']=$rels=='has_many'?ORM::singular($key):$key;
- }
-
- $normalized[$key]['type']=$rels;
- if (!isset($rel['key']))
- $normalized[$key]['key'] = $rels != 'belongs_to'?($this->model_name.'_id'):$rel['model'].'_id';
-
- if ($rels == 'has_many' && isset($rel['through']))
- if (!isset($rel['foreign_key']))
- $normalized[$key]['foreign_key']=$rel['model'].'_id';
- }
- $this->$rels=$normalized;
-
- }
-
- }
-
- /**
- * Magic method for call Query_Database methods
- *
- * @param string $method Method to call
- * @param array $arguments Arguments passed to the method
- * @return mixed Returns self if parameters were passed. If no parameters where passed returns
- * current value for the associated parameter
- * @throws Exception If method doesn't exist
- * @access public
- */
- public function __call($method, $arguments) {
- if (!in_array($method, array('limit', 'offset', 'orderby', 'where')))
- throw new Exception("Method '{$method}' doesn't exist on .".get_class($this));
- $res = call_user_func_array(array($this->query, $method), $arguments);
- if(is_subclass_of($res,'Query_Database'))
- return $this;
- return $res;
- }
-
- /**
- * Finds all rows that meet set criteria.
- *
- * @return ORMResult Returns ORMResult that you can use in a 'foreach' loop.
- * @access public
- */
- public function find_all() {
- return new ORMResult(get_class($this), $res=$this->query->execute());
- }
-
- /**
- * Searches for the first row that meets set criteria. If no rows match it still returns an ORM object
- * but with its loaded() flag being False. calling save() on such an object will insert a new row.
- *
- * @return ORM Found item or new object of the current model but with loaded() flag being False
- * @access public
- */
- public function find() {
- $res=new ORMResult(get_class($this), $res=$this->query->limit(1)->execute());
- return $res->current();
- }
-
- /**
- * Counts all rows that meet set criteria. Ignores limit and offset.
- *
- * @return int Number of rows
- * @access public
- */
- public function count_all() {
- $query = clone $this->query;
- $query->type('count');
- return $query->execute();
-
- }
-
- /**
- * Checks if the item is considered to be loaded from the database
- *
- * @return boolean Returns True if the item was loaded
- * @access public
- */
- public function loaded() {
- return $this->_loaded;
- }
-
- /**
- * Returns the row associated with current ORM item as an associative array
- *
- * @return array Associative array representing the row
- * @access public
- */
- public function as_array() {
- return $this->_row;
- }
-
- /**
- * Returns a clone of query builder that is being used to set conditions.
- * It is useful for example if you let ORM manage building a complex query using it's relationship
- * system, then you get the clone of that query and alter it to your liking,
- * so there is no need to writing relationship joins yourself.
- *
- * @return Query_Database A clone of the current query builder
- * @access public
- */
- public function query() {
- return clone $this->query;
- }
-
- /**
- * You can override this method to return additional properties that you would like to use
- * in your model. One advantage for using this instead of just overriding __get() is that
- * in this way the properties also get cached.
- *
- * @param string $property The name of the property to get
- * @return void
- * @access public
- */
- public function get($property) {
-
- }
-
- /**
- * Magic method that allows accessing row columns as properties and also facilitates
- * access to relationships and custom properties defined in get() method.
- * If a relationship is being accessed, it will return an ORM model of the related table
- * and automatically alter its query so that all your previously set conditions will remain
-
- * @param string $column Name of the column, property or relationship to get
- * @return mixed
- * @access public
- * @throws Exception If neither property nor a relationship with such name is found
- */
- public function __get($column) {
- if (array_key_exists($column,$this->_row))
- return $this->_row[$column];
- if (array_key_exists($column,$this->_cached))
- return $this->_cached[$column];
- if (($val = $this->get($column))!==null) {
- $this->_cached[$column] = $val;
- return $val;
- }
- $relations = array_merge($this->has_one, $this->has_many, $this->belongs_to);
-
- if ($target = Misc::arr($relations, $column, false)) {
- $model = ORM::factory($target['model']);
- $model->query = clone $this->query;
- if ($this->loaded())
- $model->query->where($this->id_field,$this->_row[$this->id_field]);
- if ($target['type']=='has_many'&&isset($target['through'])) {
- $lastAlias = $model->query->lastAlias();
- $throughAlias=$model->query->addAlias();
- $newAlias = $model->query->addAlias();
- $model->query->join(array($target['through'], $throughAlias), array(
- $lastAlias.'.'.$this->id_field,
- $throughAlias.'.'.$target['key'],
- ),'inner');
- $model->query->join(array($model->table, $newAlias), array(
- $throughAlias.'.'.$target['foreign_key'],
- $newAlias.'.'.$model->id_field,
- ),'inner');
- }else{
- $lastAlias = $model->query->lastAlias();
- $newAlias = $model->query->addAlias();
- if ($target['type'] == 'belongs_to') {
- $model->query->join(array($model->table, $newAlias), array(
- $lastAlias.'.'.$target['key'],
- $newAlias.'.'.$model->id_field,
- ),'inner');
- }else {
- $model->query->join(array($model->table, $newAlias), array(
- $lastAlias.'.'.$this->id_field,
- $newAlias.'.'.$target['key'],
- ), 'inner');
- }
- }
- $model->query->fields(array("$newAlias.*"));
- if ($target['type'] != 'has_many' && $model->loaded() ) {
- $model = $model->find();
- $this->_cached[$column]=$model;
- }
- return $model;
- }
-
- throw new Exception("Property {$column} not found on {$this->model_name} model.");
- }
-
- /**
- * Magic method to update record values when set as properties or to add an ORM item to
- * a relation. By assigning an ORM object to a relationship a relationship is created between the
- * current item and the passed one Using properties this way is a shortcut to the add() method.
- *
- * @param string $column Column or relationship name
- * @param mixed $val Column value or an ORM item to be added to a relation
- * @return void
- * @access public
- * @see add()
- */
- public function __set($column, $val) {
- $relations = array_merge($this->has_one, $this->has_many, $this->belongs_to);
- if (array_key_exists($column,$relations)){
- $this->add($column, $val);
- }else{
- $this->_row[$column] = $val;
- }
- $this->_cached=array();
- }
-
- /**
- * Create a relationship between current item and an other one
- *
- * @param string $relation Name of the relationship
- * @param ORM $model ORM item to create a relationship with
- * @return void
- * @access public
- * @throws Exception Exception If relationship is not defined
- * @throws Exception Exception If current item is not in the database yet (isn't considered loaded())
- * @throws Exception Exception If passed item is not in the database yet (isn't considered loaded())
- */
- public function add($relation, $model) {
-
- if (!$this->loaded())
- throw new Exception("Model must be loaded before you try adding relationships to it. Probably you haven't saved it.");
- if (!$model->loaded())
- throw new Exception("Model must be loaded before added to a relationship. Probably you haven't saved it.");
-
- $rels = array_merge($this->has_one, $this->has_many,$this->belongs_to);
- $rel = Misc::arr($rels, $relation, false);
- if (!$rel)
- throw new Exception("Model doesn't have a '{$relation}' relation defined");
-
- if ($rel['type']=='belongs_to') {
- $key=$rel['key'];
- $this->$key = $model->_row[$this->id_field];
- $this->save();
- }elseif (isset($rel['through'])) {
- $exists = DB::instance($this->connection)->query('count')
- ->table($rel['through'])
- ->where(array(
- array($rel['key'],$this->_row[$this->id_field]),
- array($rel['foreign_key'],$model->_row[$model->id_field])
- ))
- ->execute();
- if(!$exists)
- DB::instance($this->connection)->query('insert')
- ->table($rel['through'])
- ->data(array(
- $rel['key'] => $this->_row[$this->id_field],
- $rel['foreign_key'] =>$model->_row[$model->id_field]
- ))
- ->execute();
- }else {
- $key=$rel['key'];
- $model->$key = $this->_row[$this->id_field];
- $model->save();
- }
- $this->_cached=array();
- }
-
- /**
- * Removes a relationship between current item and the passed one
- *
- * @param string $relation Name of the relationship
- * @param ORM $model ORM item to remove relationship with. Can be omitted for 'belongs_to' relationships
- * @return void
- * @access public
- * @throws Exception Exception If realtionship is not defined
- * @throws Exception Exception If current item is not in the database yet (isn't considered loaded())
- * @throws Exception Exception If passed item is not in the database yet (isn't considered loaded())
- */
- public function remove($relation, $model=false) {
-
- if (!$this->loaded())
- throw new Exception("Model must be loaded before you try removing relationships from it.");
-
- $rels = array_merge($this->has_one, $this->has_many,$this->belongs_to);
- $rel = Misc::arr($rels, $relation, false);
- if (!$rel)
- throw new Exception("Model doesn't have a '{$relation}' relation defined");
-
- if ($rel['type']!='belongs_to'&&(!$model||!$model->loaded()))
- throw new Exception("Model must be loaded before being removed from a has_one or has_many relationship.");
- if ($rel['type']=='belongs_to') {
- $key=$rel['key'];
- $this->$key = null;
- $this->save();
- }elseif (isset($rel['through'])) {
- $exists = DB::instance($this->connection)->query('delete')
- ->table($rel['through'])
- ->where(array(
- array($rel['key'],$this->_row[$this->id_field]),
- array($rel['foreign_key'],$model->_row[$model->id_field])
- ))
- ->execute();
- }else {
- $key=$rel['key'];
- $model->$key = null;
- $model->save();
- }
- $this->_cached=array();
- }
-
- /**
- * Deletes current item from the database
- *
- * @return void
- * @access public
- * @throws Exception If the item is not in the database, e.g. is not loaded()
- */
- public function delete() {
- if (!$this->loaded())
- throw new Exception("Cannot delete an item that wasn't selected from database");
- DB::instance($this->connection)->query('delete')
- ->table($this->table)
- ->where($this->id_field, $this->_row[$this->id_field])
- ->execute();
- $this->_cached=array();
- }
-
- /**
- * Deletes all items that meet set conditions. Use in the same way
- * as you would a find_all() method.
- *
- * @return ORM Returns self
- * @access public
- */
- public function delete_all() {
- $query = clone $this->query;
- $query->type('delete');
- $query->execute();
- return $this;
- }
-
- /**
- * Saves the item back to the database. If item is loaded() it will result
- * in an update, otherwise a new row will be inserted
- *
- * @return ORM Returns self
- * @access public
- */
- public function save() {
- if (isset($this->_row[$this->id_field])) {
- $query = DB::instance($this->connection)->query('update')
- ->table($this->table)
- ->where($this->id_field,$this->_row[$this->id_field]);
- }else {
- $query = DB::instance($this->connection)->query('insert')
- ->table($this->table);
- }
- $query->data($this->_row);
- $query->execute();
-
- if (isset($this->_row[$this->id_field])) {
- $id=$this->_row[$this->id_field];
- }else {
- $id=DB::instance($this->connection)->get_insert_id();
- }
- $row =(array) DB::instance($this->connection)->query('select')
- ->table($this->table)
- ->where($this->id_field, $id)->execute()->current();
- $this->values($row,true);
- return $this;
- }
-
-
- /**
- * Batch updates item columns using an associative array
- *
- * @param array $row Associative array of key => value pairs
- * @param boolean $set_loaded Flag to consider the ORM item loaded. Useful if you selected
- * the row from the database and want to wrap it in ORM
- * @return ORM Returns self
- * @access public
- */
- public function values($row, $set_loaded = false) {
- $this->_row = array_merge($this->_row, $row);
- if ($set_loaded)
- $this->_loaded = true;
- $this->_cached=array();
- return $this;
- }
-
- /**
- * Initializes ORM model by name, and optionally fetches an item by id
- *
- * @param string $name Model name
- * @param mixed $id If set ORM will try to load the item with this id from the database
- * @return ORM ORM model, either empty or preloaded
- * @access public
- * @static
- */
- public static function factory($name,$id=null){
- $model = $name.'_Model';
- $model=new $model;
- if ($id != null)
- return $model->where($model->id_field, $id)->find()
- ->data(array($model->id_field,$id));
- return $model;
- }
-
- /**
- * Gets plural form of a noun
- *
- * @param string $str Noun to get a plural form of
- * @return string Plural form
- * @access private
- * @static
- */
- private static function plural($str){
- $regexes=array(
- '/^(.*?[sxz])$/i' => '\\1es',
- '/^(.*?[^aeioudgkprt]h)$/i' => '\\1es',
- '/^(.*?[^aeiou])y$/i'=>'\\1ies',
- );
- foreach($regexes as $key=>$val){
- $str = preg_replace($key, $val, $str,-1, $count);
- if ($count)
- return $str;
- }
- return $str.'s';
- }
-
- /**
- * Gets singular form of a noun
- *
- * @param string $str Noun to get singular form of
- * @return string Singular form of the noun
- * @access private
- * @static
- */
- private static function singular($str){
- $regexes=array(
- '/^(.*?us)$/i' => '\\1',
- '/^(.*?[sxz])es$/i' => '\\1',
- '/^(.*?[^aeioudgkprt]h)es$/i' => '\\1',
- '/^(.*?[^aeiou])ies$/i' => '\\1y',
- '/^(.*?)s$/'=>'\\1'
- );
- foreach($regexes as $key=>$val){
- $str = preg_replace($key, $val, $str,-1, $count);
- if ($count)
- return $str;
- }
- return $str;
- }
+<?php
+
+/**
+ * ORM allows you to access database items and their relationships in an OOP manner,
+ * it is easy to setup and makes a lot of use of naming convention.
+ *
+ * @method mixed limit(int $limit = null) Set number of rows to return.
+ * Without arguments returns current limit, returns self otherwise.
+ *
+ * @method mixed offset(int $offset = null) Set the offset for the first row in result.
+ * Without arguments returns current offset, returns self otherwise.
+ *
+ * @method mixed orderby(string $column, string $dir) Adds a column to ordering parameters
+ *
+ * @method mixed where(mixed $key, mixed $operator = null, mixed $val = null) behaves just like Query_Database::where()
+ *
+ * @see Query_Database::where()
+ */
+class ORM {
+
+ /**
+ * Specifies which table the model will use, can be overridden
+ * @var string
+ * @access public
+ */
+ public $table = null;
+
+ /**
+ * Specifies which connection the model will use, can be overridden
+ * but a model can have relationships only with models utilizing the same connection
+ * @var string
+ * @access public
+ */
+ public $connection = 'default';
+
+ /**
+ * Specifies which column is treated as PRIMARY KEY
+ * @var string
+ * @access public
+ */
+ public $id_field='id';
+
+ /**
+ * You can define 'Belongs to' relationships buy changing this array
+ * @var array
+ * @access protected
+ */
+ protected $belongs_to=array();
+
+ /**
+ * You can define 'Has one' relationships buy changing this array
+ * @var array
+ * @access protected
+ */
+ protected $has_one = array();
+
+ /**
+ * You can define 'Has many' relationships buy changing this array
+ * @var array
+ * @access protected
+ */
+ protected $has_many = array();
+
+ /**
+ * An instance of the database connection
+ * @var DB
+ * @access private
+ */
+ private $db;
+
+ /**
+ * Current row returned by the database
+ * @var array
+ * @access protected
+ */
+ protected $_row = array();
+
+ /**
+ * Associated query builder
+ * @var Query_Database
+ * @access public
+ */
+ public $query;
+
+ /**
+ * A flag whether the row was loaded from the database
+ * @var boolean
+ * @access private
+ */
+ private $_loaded = false;
+
+ /**
+ * The name of the model
+ * @var string
+ * @access public
+ */
+ public $model_name;
+
+ /**
+ * Cached properties
+ * @var array
+ * @access private
+ */
+ private $_cached = array();
+
+ /**
+ * Constructs the model. To use ORM it is enough to
+ * just create a model like this:
+ * <code>
+ * class Fairy_Model extends ORM { }
+ * </code>
+ * By default it will assume that the name of your table
+ * is the plural form of the models' name, the PRIMARY KEY is id,
+ * and will use the 'default' connection. This behaviour is easy to be
+ * changed by overriding $table, $id and $db properties.
+ *
+ * @return void
+ * @access public
+ * @ see $table
+ * @ see $id
+ * @ see $db
+ */
+ public function __construct() {
+ $this->query = DB::instance($this->connection)->query('select');
+ $this->model_name = strtolower(get_class($this));
+ if (substr($this->model_name, -6) == '_model')
+ $this->model_name=substr($this->model_name,0,-6);
+ if ($this->table == null)
+ $this->table = ORM::plural($this->model_name);
+ $this->query->table($this->table);
+
+ foreach(array('belongs_to', 'has_one', 'has_many') as $rels) {
+ $normalized=array();
+ foreach($this->$rels as $key => $rel) {
+ if (!is_array($rel)) {
+ $key = $rel;
+ $rel=array();
+ }
+ $normalized[$key]=$rel;
+ if (!isset($rel['model'])) {
+ $rel['model']=$normalized[$key]['model']=$rels=='has_many'?ORM::singular($key):$key;
+ }
+
+ $normalized[$key]['type']=$rels;
+ if (!isset($rel['key']))
+ $normalized[$key]['key'] = $rels != 'belongs_to'?($this->model_name.'_id'):$rel['model'].'_id';
+
+ if ($rels == 'has_many' && isset($rel['through']))
+ if (!isset($rel['foreign_key']))
+ $normalized[$key]['foreign_key']=$rel['model'].'_id';
+ }
+ $this->$rels=$normalized;
+
+ }
+
+ }
+
+ /**
+ * Magic method for call Query_Database methods
+ *
+ * @param string $method Method to call
+ * @param array $arguments Arguments passed to the method
+ * @return mixed Returns self if parameters were passed. If no parameters where passed returns
+ * current value for the associated parameter
+ * @throws Exception If method doesn't exist
+ * @access public
+ */
+ public function __call($method, $arguments) {
+ if (!in_array($method, array('limit', 'offset', 'orderby', 'where')))
+ throw new Exception("Method '{$method}' doesn't exist on .".get_class($this));
+ $res = call_user_func_array(array($this->query, $method), $arguments);
+ if(is_subclass_of($res,'Query_Database'))
+ return $this;
+ return $res;
+ }
+
+ /**
+ * Finds all rows that meet set criteria.
+ *
+ * @return ORMResult Returns ORMResult that you can use in a 'foreach' loop.
+ * @access public
+ */
+ public function find_all() {
+ return new ORMResult(get_class($this), $res=$this->query->execute());
+ }
+
+ /**
+ * Searches for the first row that meets set criteria. If no rows match it still returns an ORM object
+ * but with its loaded() flag being False. calling save() on such an object will insert a new row.
+ *
+ * @return ORM Found item or new object of the current model but with loaded() flag being False
+ * @access public
+ */
+ public function find() {
+ $res=new ORMResult(get_class($this), $res=$this->query->limit(1)->execute());
+ return $res->current();
+ }
+
+ /**
+ * Counts all rows that meet set criteria. Ignores limit and offset.
+ *
+ * @return int Number of rows
+ * @access public
+ */
+ public function count_all() {
+ $query = clone $this->query;
+ $query->type('count');
+ return $query->execute();
+
+ }
+
+ /**
+ * Checks if the item is considered to be loaded from the database
+ *
+ * @return boolean Returns True if the item was loaded
+ * @access public
+ */
+ public function loaded() {
+ return $this->_loaded;
+ }
+
+ /**
+ * Returns the row associated with current ORM item as an associative array
+ *
+ * @return array Associative array representing the row
+ * @access public
+ */
+ public function as_array() {
+ return $this->_row;
+ }
+
+ /**
+ * Returns a clone of query builder that is being used to set conditions.
+ * It is useful for example if you let ORM manage building a complex query using it's relationship
+ * system, then you get the clone of that query and alter it to your liking,
+ * so there is no need to writing relationship joins yourself.
+ *
+ * @return Query_Database A clone of the current query builder
+ * @access public
+ */
+ public function query() {
+ return clone $this->query;
+ }
+
+ /**
+ * You can override this method to return additional properties that you would like to use
+ * in your model. One advantage for using this instead of just overriding __get() is that
+ * in this way the properties also get cached.
+ *
+ * @param string $property The name of the property to get
+ * @return void
+ * @access public
+ */
+ public function get($property) {
+
+ }
+
+ /**
+ * Magic method that allows accessing row columns as properties and also facilitates
+ * access to relationships and custom properties defined in get() method.
+ * If a relationship is being accessed, it will return an ORM model of the related table
+ * and automatically alter its query so that all your previously set conditions will remain
+
+ * @param string $column Name of the column, property or relationship to get
+ * @return mixed
+ * @access public
+ * @throws Exception If neither property nor a relationship with such name is found
+ */
+ public function __get($column) {
+ if (array_key_exists($column,$this->_row))
+ return $this->_row[$column];
+ if (array_key_exists($column,$this->_cached))
+ return $this->_cached[$column];
+ if (($val = $this->get($column))!==null) {
+ $this->_cached[$column] = $val;
+ return $val;
+ }
+ $relations = array_merge($this->has_one, $this->has_many, $this->belongs_to);
+
+ if ($target = Misc::arr($relations, $column, false)) {
+ $model = ORM::factory($target['model']);
+ $model->query = clone $this->query;
+ if ($this->loaded())
+ $model->query->where($this->id_field,$this->_row[$this->id_field]);
+ if ($target['type']=='has_many'&&isset($target['through'])) {
+ $lastAlias = $model->query->lastAlias();
+ $throughAlias=$model->query->addAlias();
+ $newAlias = $model->query->addAlias();
+ $model->query->join(array($target['through'], $throughAlias), array(
+ $lastAlias.'.'.$this->id_field,
+ $throughAlias.'.'.$target['key'],
+ ),'inner');
+ $model->query->join(array($model->table, $newAlias), array(
+ $throughAlias.'.'.$target['foreign_key'],
+ $newAlias.'.'.$model->id_field,
+ ),'inner');
+ }else{
+ $lastAlias = $model->query->lastAlias();
+ $newAlias = $model->query->addAlias();
+ if ($target['type'] == 'belongs_to') {
+ $model->query->join(array($model->table, $newAlias), array(
+ $lastAlias.'.'.$target['key'],
+ $newAlias.'.'.$model->id_field,
+ ),'inner');
+ }else {
+ $model->query->join(array($model->table, $newAlias), array(
+ $lastAlias.'.'.$this->id_field,
+ $newAlias.'.'.$target['key'],
+ ), 'inner');
+ }
+ }
+ $model->query->fields(array("$newAlias.*"));
+ if ($target['type'] != 'has_many' && $model->loaded() ) {
+ $model = $model->find();
+ $this->_cached[$column]=$model;
+ }
+ return $model;
+ }
+
+ throw new Exception("Property {$column} not found on {$this->model_name} model.");
+ }
+
+ /**
+ * Magic method to update record values when set as properties or to add an ORM item to
+ * a relation. By assigning an ORM object to a relationship a relationship is created between the
+ * current item and the passed one Using properties this way is a shortcut to the add() method.
+ *
+ * @param string $column Column or relationship name
+ * @param mixed $val Column value or an ORM item to be added to a relation
+ * @return void
+ * @access public
+ * @see add()
+ */
+ public function __set($column, $val) {
+ $relations = array_merge($this->has_one, $this->has_many, $this->belongs_to);
+ if (array_key_exists($column,$relations)){
+ $this->add($column, $val);
+ }else{
+ $this->_row[$column] = $val;
+ }
+ $this->_cached=array();
+ }
+
+ /**
+ * Create a relationship between current item and an other one
+ *
+ * @param string $relation Name of the relationship
+ * @param ORM $model ORM item to create a relationship with
+ * @return void
+ * @access public
+ * @throws Exception Exception If relationship is not defined
+ * @throws Exception Exception If current item is not in the database yet (isn't considered loaded())
+ * @throws Exception Exception If passed item is not in the database yet (isn't considered loaded())
+ */
+ public function add($relation, $model) {
+
+ if (!$this->loaded())
+ throw new Exception("Model must be loaded before you try adding relationships to it. Probably you haven't saved it.");
+ if (!$model->loaded())
+ throw new Exception("Model must be loaded before added to a relationship. Probably you haven't saved it.");
+
+ $rels = array_merge($this->has_one, $this->has_many,$this->belongs_to);
+ $rel = Misc::arr($rels, $relation, false);
+ if (!$rel)
+ throw new Exception("Model doesn't have a '{$relation}' relation defined");
+
+ if ($rel['type']=='belongs_to') {
+ $key=$rel['key'];
+ $this->$key = $model->_row[$this->id_field];
+ $this->save();
+ }elseif (isset($rel['through'])) {
+ $exists = DB::instance($this->connection)->query('count')
+ ->table($rel['through'])
+ ->where(array(
+ array($rel['key'],$this->_row[$this->id_field]),
+ array($rel['foreign_key'],$model->_row[$model->id_field])
+ ))
+ ->execute();
+ if(!$exists)
+ DB::instance($this->connection)->query('insert')
+ ->table($rel['through'])
+ ->data(array(
+ $rel['key'] => $this->_row[$this->id_field],
+ $rel['foreign_key'] =>$model->_row[$model->id_field]
+ ))
+ ->execute();
+ }else {
+ $key=$rel['key'];
+ $model->$key = $this->_row[$this->id_field];
+ $model->save();
+ }
+ $this->_cached=array();
+ }
+
+ /**
+ * Removes a relationship between current item and the passed one
+ *
+ * @param string $relation Name of the relationship
+ * @param ORM $model ORM item to remove relationship with. Can be omitted for 'belongs_to' relationships
+ * @return void
+ * @access public
+ * @throws Exception Exception If realtionship is not defined
+ * @throws Exception Exception If current item is not in the database yet (isn't considered loaded())
+ * @throws Exception Exception If passed item is not in the database yet (isn't considered loaded())
+ */
+ public function remove($relation, $model=false) {
+
+ if (!$this->loaded())
+ throw new Exception("Model must be loaded before you try removing relationships from it.");
+
+ $rels = array_merge($this->has_one, $this->has_many,$this->belongs_to);
+ $rel = Misc::arr($rels, $relation, false);
+ if (!$rel)
+ throw new Exception("Model doesn't have a '{$relation}' relation defined");
+
+ if ($rel['type']!='belongs_to'&&(!$model||!$model->loaded()))
+ throw new Exception("Model must be loaded before being removed from a has_one or has_many relationship.");
+ if ($rel['type']=='belongs_to') {
+ $key=$rel['key'];
+ $this->$key = null;
+ $this->save();
+ }elseif (isset($rel['through'])) {
+ $exists = DB::instance($this->connection)->query('delete')
+ ->table($rel['through'])
+ ->where(array(
+ array($rel['key'],$this->_row[$this->id_field]),
+ array($rel['foreign_key'],$model->_row[$model->id_field])
+ ))
+ ->execute();
+ }else {
+ $key=$rel['key'];
+ $model->$key = null;
+ $model->save();
+ }
+ $this->_cached=array();
+ }
+
+ /**
+ * Deletes current item from the database
+ *
+ * @return void
+ * @access public
+ * @throws Exception If the item is not in the database, e.g. is not loaded()
+ */
+ public function delete() {
+ if (!$this->loaded())
+ throw new Exception("Cannot delete an item that wasn't selected from database");
+ DB::instance($this->connection)->query('delete')
+ ->table($this->table)
+ ->where($this->id_field, $this->_row[$this->id_field])
+ ->execute();
+ $this->_cached=array();
+ }
+
+ /**
+ * Deletes all items that meet set conditions. Use in the same way
+ * as you would a find_all() method.
+ *
+ * @return ORM Returns self
+ * @access public
+ */
+ public function delete_all() {
+ $query = clone $this->query;
+ $query->type('delete');
+ $query->execute();
+ return $this;
+ }
+
+ /**
+ * Saves the item back to the database. If item is loaded() it will result
+ * in an update, otherwise a new row will be inserted
+ *
+ * @return ORM Returns self
+ * @access public
+ */
+ public function save() {
+ if (isset($this->_row[$this->id_field])) {
+ $query = DB::instance($this->connection)->query('update')
+ ->table($this->table)
+ ->where($this->id_field,$this->_row[$this->id_field]);
+ }else {
+ $query = DB::instance($this->connection)->query('insert')
+ ->table($this->table);
+ }
+ $query->data($this->_row);
+ $query->execute();
+
+ if (isset($this->_row[$this->id_field])) {
+ $id=$this->_row[$this->id_field];
+ }else {
+ $id=DB::instance($this->connection)->get_insert_id();
+ }
+ $row =(array) DB::instance($this->connection)->query('select')
+ ->table($this->table)
+ ->where($this->id_field, $id)->execute()->current();
+ $this->values($row,true);
+ return $this;
+ }
+
+
+ /**
+ * Batch updates item columns using an associative array
+ *
+ * @param array $row Associative array of key => value pairs
+ * @param boolean $set_loaded Flag to consider the ORM item loaded. Useful if you selected
+ * the row from the database and want to wrap it in ORM
+ * @return ORM Returns self
+ * @access public
+ */
+ public function values($row, $set_loaded = false) {
+ $this->_row = array_merge($this->_row, $row);
+ if ($set_loaded)
+ $this->_loaded = true;
+ $this->_cached=array();
+ return $this;
+ }
+
+ /**
+ * Initializes ORM model by name, and optionally fetches an item by id
+ *
+ * @param string $name Model name
+ * @param mixed $id If set ORM will try to load the item with this id from the database
+ * @return ORM ORM model, either empty or preloaded
+ * @access public
+ * @static
+ */
+ public static function factory($name,$id=null){
+ $model = $name.'_Model';
+ $model=new $model;
+ if ($id != null)
+ return $model->where($model->id_field, $id)->find()
+ ->data(array($model->id_field,$id));
+ return $model;
+ }
+
+ /**
+ * Gets plural form of a noun
+ *
+ * @param string $str Noun to get a plural form of
+ * @return string Plural form
+ * @access private
+ * @static
+ */
+ private static function plural($str){
+ $regexes=array(
+ '/^(.*?[sxz])$/i' => '\\1es',
+ '/^(.*?[^aeioudgkprt]h)$/i' => '\\1es',
+ '/^(.*?[^aeiou])y$/i'=>'\\1ies',
+ );
+ foreach($regexes as $key=>$val){
+ $str = preg_replace($key, $val, $str,-1, $count);
+ if ($count)
+ return $str;
+ }
+ return $str.'s';
+ }
+
+ /**
+ * Gets singular form of a noun
+ *
+ * @param string $str Noun to get singular form of
+ * @return string Singular form of the noun
+ * @access private
+ * @static
+ */
+ private static function singular($str){
+ $regexes=array(
+ '/^(.*?us)$/i' => '\\1',
+ '/^(.*?[sxz])es$/i' => '\\1',
+ '/^(.*?[^aeioudgkprt]h)es$/i' => '\\1',
+ '/^(.*?[^aeiou])ies$/i' => '\\1y',
+ '/^(.*?)s$/'=>'\\1'
+ );
+ foreach($regexes as $key=>$val){
+ $str = preg_replace($key, $val, $str,-1, $count);
+ if ($count)
+ return $str;
+ }
+ return $str;
+ }
} \ No newline at end of file