dba = $dba; $this->definition = $definition; if(!empty($data)) { $this->saved = TRUE; } $this->data = $data; } /** * Quick accessor for the value of the primary key. */ final public function getId() { return isset($this->data[$this->definition->getPrimaryKey()]) ? $this->data[$this->definition->getPrimaryKey()] : FALSE; } /** * Get the value of a row from the record. First it will look in the dirty array, then the data (saved) * array, then the cache from functions, then it will check if a $key 'func_name' function exists as * 'getFuncName', and if so, cache that result. * @param string $key The table column name or cache result to get. * @return mixed * @see VK_Overload * @see VK_DatabaseRecord::__set(), VK_DatabaseRecord::__isset(), VK_DatabaseRecord::__unset() */ final public function __get($key = '') { $ret = NULL; // dirty first, important if(isset($this->dirty[$key])) { $ret = $this->dirty[$key]; } else if(isset($this->data[$key])) { $ret = $this->data[$key]; } else if(isset($this->cache[$key])) { $ret = $this->cache[$key]; } else { $method_name = 'get'. camelize($key); // call a function if(is_callable($this, $method_name)) { $ret = $this->cache[$key] = $this->$method_name(); // get a relation } else if($relation = $this->definition->getRelation($key)) { echo "\n\n". get_class($relation) ."\n\n"; $finder = $relation->getModel()->getFinder($relation->getModelId()); $func = $relation instanceof VK_ToOneRelation ? 'findByQuery' : 'findAllByQuery'; $ret = $this->cache[$key] = $finder->$func($relation->getQuery(), array($this->getId())); } } return $ret; } /** * Call a relation so that extra parameters can be passed to it. */ public function __call($key, array $args = array()) { if(!isset($this->cache[$key]) && isset($args[0]) && $relation = $this->definition->getRelation($key)) { call_user_func_array(array($relation, 'setSelect'), $args); $finder = $relation->getModel()->getFinder($relation->getModelId()); $func = $relation instanceof ToOneRelation ? 'findByQuery' : 'findAllByQuery'; $this->cache[$key] = $finder->$func($relation->getQuery(), array($this->getId())); } return isset($this->cache[$key]) ? $this->cache[$key] : NULL; } /** * Set a value to the array of dirty data. * @param string $key The key (column name) to set the the dirty array * @param string $val The value to set the the dirty array for this column * @see VK_Overload * @see VK_DatabaseRecord::__get(), VK_DatabaseRecord::__isset(), VK_DatabaseRecord::__unset() */ final public function __set($key = '', $val) { $this->dirty[$key] = $val; } /** * Check if a value for a column name exists * @param string $key The key (column name) check * @todo Do I want to have to pre-fetch here? It's the only way for it to work with the template engine :( * @see VK_Overload * @see VK_DatabaseRecord::__get(), VK_DatabaseRecord::__set(), VK_DatabaseRecord::__unset() */ final public function __isset($key = '') { //return isset($this->dirty[$key]) || isset($this->data[$key]) || isset($this->cache[$key]); return $this->__get($key) !== NULL; } /** * Unset the value for a column name * @param string $key The key (column name) to unset * @see VK_Overload * @see VK_DatabaseRecord::__get(), VK_DatabaseRecord::__set(), VK_DatabaseRecord::__isset() */ final public function __unset($key = '') { unset($this->dirty[$key], $this->data[$key]); } /** * Bind an array to the dirty values. */ final public function bindArray($array = array()) { if(is_array($array)) { $this->dirty = array_merge($this->dirty, $array); } else if($array instanceof DatabaseRecord) { foreach($array as $key => $val) { $this->$key = $val; } } } /** * Get a database finder. */ final public function getFinder($id) { return $this->definition->getModel()->getFinder($id); } /** * Overrideable function (in custom record classes) to execute before a record is * saved. This is particularly useful if you want to do any sort of operations on the * values of columns before saving, eg: special validation/cleaning. */ public function preSave() { } /** * Overrideable function (in custom record classes) to execute after a record has * been saved. I haven't thought of a use case for this, but because preSave exists, * I thought: "why not postSave?", and so it is here. */ public function postSave() { } /** * Validate the dirty fields. * @see VK_ModelDefinition::validateField() * @internal */ final private function validate() { foreach($this->dirty as $field => $val) { if(FALSE !== ($val = $this->definition->validateField($field, $val))) { $this->dirty[$field] = $val; } else { unset($this->dirty[$field]); } } } /** * Save a record to the database. If the record already exists in the database, then this * will perform an UPDATE query, otherwise it will perform an INSERT query. */ final public function save() { // call the pre-save hook $this->preSave(); $insert_vals = array(); // validate the dirty things against the model $this->validate(); // get dirty things $keys = array_keys($this->dirty); $vals = array_values($this->dirty); // update if($this->saved) { $sql = 'UPDATE '. $this->definition->getTable('name') .' SET '; $sep = ''; foreach($keys as $key) { $sql .= $sep . $key .'=?'; $sep = ','; } $sql .= ' WHERE '. $this->definition->getPrimaryKey() .'=?'; $vals[] = $this->getId(); $this->dba->update($sql, $vals); // insert } else { $this->dba->update($this->dba->buildInsert($this->definition->getTable('name'), $keys, $vals), $vals); if($this->definition->getPrimaryKey() !== NULL) { $this->data[$this->definition->getPrimaryKey()] = $this->dba->insertId(); } } $this->saved = TRUE; $this->data = array_merge($this->data, $this->dirty); $this->dirty = array(); // call the post-save hook $this->postSave(); return TRUE; } /** * Delete a record from the database if it exists there already. */ final public function delete() { if($this->saved) { if($this->definition->getPrimaryKey() !== NULL) { $this->dba->update('DELETE FROM '. $this->definition->getTable('name') .' WHERE '. $this->definition->getPrimaryKey() .'=?', array($this->getId())); } else { // hrm. } } } /** * Iterate through the dirty/data arrays. */ public function getIterator() { return new ArrayIterator(array_merge($this->data, $this->dirty)); } } /** * A class it iterate over a result set. Each iteration will return a VK_DatabaseRecord * decorator class for the row. This class extends IteratorIterator because the * database result sets passed in implement Iterator. * @see VK_DatabaseRecord, IteratorIterator, DatabaseResultSet */ class VK_DatabaseRecordSet extends IteratorIterator implements VK_RecordSet, Countable { /** * A reference to the current database abstraction layer * @see Database * @internal */ private $dba; /** * A reference to this tables model definition * @see DatabaseModelDefinition * @internal */ private $definition; /** * An array of cached record classes. The record classes are cached because * if one decides to iterate over this result set multiple times then each * time we want to return to them the same record class for each database row. * @internal */ private $cache = array(); /** * Constructor, bring in the result set, the database layer and the model definition. * @param mixed $result A reference to a DatabaseResultSet * @param mixed $dba A reference to the current database abstraction layer * @param mixed $model A reference to the this tables model definition * @see DatabaseResultSet, Database, ModelDefinition * @internal */ final public function __construct(DatabaseResultSet $result, DatabaseDriver $dba, DatabaseModelDefinition $definition) { $this->dba = $dba; $this->definition = $definition; parent::__construct($result); } /** * The current function of the iterator, called in each loop. This function returns * a cached version of the VK_DatabaseRecord decorator class for the current row in the database * result set. * @return mixed A cached instance of VK_DatabaseRecord * @see VK_DatabaseRecord * @internal */ public function current() { if(!isset($this->cache[$this->key()])) { $record = $this->definition->getName() .'Record'; $record = class_exists($record, FALSE) ? $record : 'VK_DatabaseRecord'; $this->cache[$this->key()] = new $record(parent::current(), $this->dba, $this->definition); } return $this->cache[$this->key()]; } /** * Return the number of rows in the internal result set * @see DatabaseResultSet::numRows() */ final public function numRows() { return $this->getInnerIterator()->numRows(); } /** * Return the number of rows. */ final public function count() { return $this->numRows(); } /** * Delete all items in this record set, also cause a cascade delete if necessary. */ final public function delete() { } }