%PDF- %PDF-
Direktori : /home1/lightco1/public_html/lightingrepublic.com.au/libraries/koowa/database/table/ |
Current File : //home1/lightco1/public_html/lightingrepublic.com.au/libraries/koowa/database/table/abstract.php |
<?php /** * @version $Id$ * @package Koowa_Database * @subpackage Table * @copyright Copyright (C) 2007 - 2012 Johan Janssens. All rights reserved. * @license GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> * @link http://www.nooku.org */ /** * Abstract Table Class * * Parent class to all tables. * * @author Johan Janssens <johan@nooku.org> * @package Koowa_Database * @subpackage Table * @uses KMixinClass * @uses KFilter */ abstract class KDatabaseTableAbstract extends KObject { /** * Real name of the table in the db schema * * @var string */ protected $_name; /** * Base name of the table in the db schema * * @var string */ protected $_base; /** * Name of the identity column in the table * * @var string */ protected $_identity_column; /** * Array of column mappings by column name * * @var array */ protected $_column_map = array(); /** * Database adapter * * @var object */ protected $_database = false; /** * Default values for this table * * @var array */ protected $_defaults; /** * Object constructor * * @param object An optional KConfig object with configuration options. */ public function __construct(KConfig $config = null) { //If no config is passed create it if(!isset($config)) $config = new KConfig(); parent::__construct($config); $this->_name = $config->name; $this->_base = $config->base; $this->_database = $config->database; //Check if the table exists if(!$info = $this->getSchema()) { throw new KDatabaseTableException('Table '.$this->_name.' does not exist'); } // Set the identity column if(!isset($config->identity_column)) { foreach ($this->getColumns(true) as $column) { if($column->autoinc) { $this->_identity_column = $column->name; break; } } } else $this->_identity_column = $config->identity_column; //Set the default column mappings $this->_column_map = $config->column_map ? $config->column_map->toArray() : array(); if(!isset( $this->_column_map['id']) && isset($this->_identity_column)) { $this->_column_map['id'] = $this->_identity_column; } // Set the column filters if(!empty($config->filters)) { foreach($config->filters as $column => $filter) { $this->getColumn($column, true)->filter = KConfig::unbox($filter); } } // Mixin a command chain $this->mixin(new KMixinCommandchain($config->append(array('mixer' => $this)))); // Set the table behaviors if(!empty($config->behaviors)) { $this->addBehavior($config->behaviors); } } /** * Initializes the options for the object * * Called from {@link __construct()} as a first step of object instantiation. * * @param object An optional KConfig object with configuration options. * @return void */ protected function _initialize(KConfig $config) { $package = $this->getIdentifier()->package; $name = $this->getIdentifier()->name; $config->append(array( 'database' => $this->getService('koowa:database.adapter.mysqli'), 'name' => empty($package) ? $name : $package.'_'.$name, 'column_map' => null, 'filters' => array(), 'behaviors' => array(), 'identity_column' => null, 'command_chain' => new KCommandChain(), 'dispatch_events' => false, 'enable_callbacks' => false, ))->append( array('base' => $config->name) ); parent::_initialize($config); } /** * Get the database adapter * * @return KDatabaseAdapterAbstract */ public function getDatabase() { return $this->_database; } /** * Set the database adapter * * @param object A KDatabaseAdapterAbstract * @return KDatabaseTableAbstract */ public function setDatabase(KDatabaseAdapterAbstract $database) { $this->_database = $database; return $this; } /** * Test the connected status of the table * * @return boolean Returns TRUE if we have a reference to a live KDatabaseAdapterAbstract object. */ public function isConnected() { return (bool) $this->getDatabase(); } /** * Gets the table schema name without the table prefix * * @return string */ public function getName() { return $this->_name; } /** * Gets the base table name without the table prefix * * If the table type is 'VIEW' the base name will be the name of the base * table that is connected to the view. If the table type is 'BASE' this * function will return the same as {@link getName} * * @return string */ public function getBase() { return $this->_base; } /** * Gets the primary key(s) of the table * * @return array An asscociate array of fields defined in the primary key */ public function getPrimaryKey() { $keys = array(); $columns = $this->getColumns(true); foreach ($columns as $name => $description) { if($description->primary) { $keys[$name] = $description; } } return $keys; } /** * Check if a behavior exists * * @param string The name of the behavior * @return boolean TRUE if the behavior exists, FALSE otherwise */ public function hasBehavior($behavior) { return isset($this->getSchema()->behaviors[$behavior]); } /** * Register one or more behaviors to the table * * @param array Array of one or more behaviors to add. * @return KDatabaseTableAbstract */ public function addBehavior($behaviors) { $behaviors = (array) KConfig::unbox($behaviors); foreach($behaviors as $behavior) { if (!($behavior instanceof KDatabaseBehaviorInterface)) { $behavior = $this->getBehavior($behavior); } //Add the behavior $this->getSchema()->behaviors[$behavior->getIdentifier()->name] = $behavior; $this->getCommandChain()->enqueue($behavior); } return $this; } /** * Get a behavior by identifier * * @param * @return KControllerBehaviorAbstract */ public function getBehavior($behavior, $config = array()) { if(!($behavior instanceof KServiceIdentifier)) { //Create the complete identifier if a partial identifier was passed if(is_string($behavior) && strpos($behavior, '.') === false ) { $identifier = clone $this->getIdentifier(); $identifier->path = array('database', 'behavior'); $identifier->name = $behavior; } else $identifier = $this->getIdentifier($behavior); } if(!isset($this->getSchema()->behaviors[$identifier->name])) { $behavior = $this->getService($identifier, array_merge($config, array('mixer' => $this))); //Check the behavior interface if(!($behavior instanceof KDatabaseBehaviorInterface)) { throw new KDatabaseTableException("Database behavior $identifier does not implement KDatabaseBehaviorInterface"); } } else $behavior = $this->getSchema()->behaviors[$identifier->name]; return $behavior; } /** * Gets the behaviors of the table * * @return array An asscociate array of table behaviors, keys are the behavior names */ public function getBehaviors() { return $this->getSchema()->behaviors; } /** * Gets the schema of the table * * @return object|null Returns a KDatabaseSchemaTable object or NULL if the table doesn't exists * @throws KDatabaseTableException */ public function getSchema() { $result = null; if($this->isConnected()) { try { $result = $this->_database->getTableSchema($this->getBase()); } catch(KDatabaseException $e) { throw new KDatabaseTableException($e->getMessage()); } } return $result; } /** * Get a column by name * * @param boolean If TRUE, get the column information from the base table. Default is FALSE. * @return KDatabaseColumn Returns a KDatabaseSchemaColumn object or NULL if the * column does not exist */ public function getColumn($columnname, $base = false) { $columns = $this->getColumns($base); return isset($columns[$columnname]) ? $columns[$columnname] : null; } /** * Gets the columns for the table * * @param boolean If TRUE, get the column information from the base table. Default is FALSE. * @return array Associative array of KDatabaseSchemaColumn objects * @throws KDatabaseTableException */ public function getColumns($base = false) { //Get the table name $name = $base ? $this->getBase() : $this->getName(); //Get the columns from the schema $columns = $this->getSchema($name)->columns; return $this->mapColumns($columns, true); } /** * Table map method * * This functions maps the column names to those in the table schema * * @param array|string An associative array of data to be mapped, or a column name * @param boolean If TRUE, perform a reverse mapping * @return array|string The mapped data or column name */ public function mapColumns($data, $reverse = false) { $map = $reverse ? array_flip($this->_column_map) : $this->_column_map; $result = null; if(is_array($data)) { $result = array(); foreach($data as $column => $value) { if(is_string($column)) { //Map the key if(isset($map[$column])) { $column = $map[$column]; } } else { //Map the value if (isset($map[$value])) { $value = $map[$value]; } } $result[$column] = $value; } } if(is_string($data)) { $result = $data; if(isset($map[$data])) { $result = $map[$data]; } } return $result; } /** * Gets the identitiy column of the table. * * @return string */ public function getIdentityColumn() { $result = null; if(isset($this->_identity_column)) { $result = $this->_identity_column; } return $result; } /** * Gets the unqiue columns of the table * * @return array An asscociate array of unique table columns by column name */ public function getUniqueColumns() { $result = array(); $columns = $this->getColumns(true); foreach($columns as $name => $description) { if($description->unique) { $result[$name] = $description; } } return $result; } /** * Get default values for all columns * * @return array */ public function getDefaults() { if(!isset($this->_defaults)) { $defaults = array(); $columns = $this->getColumns(); foreach($columns as $name => $description) { $defaults[$name] = $description->default; } $this->_defaults = $defaults; } return $this->_defaults; } /** * Get a default by name * * @return mixed Returns the column default value or NULL if the * column does not exist */ public function getDefault($columnname) { $defaults = $this->getDefaults(); return isset($defaults[$columnname]) ? $defaults[$columnname] : null; } /** * Get an instance of a row object for this table * * @param array An optional associative array of configuration settings. * @return KDatabaseRowInterface */ public function getRow(array $options = array()) { $identifier = clone $this->getIdentifier(); $identifier->path = array('database', 'row'); $identifier->name = KInflector::singularize($this->getIdentifier()->name); //The row default options $options['table'] = $this; $options['identity_column'] = $this->mapColumns($this->getIdentityColumn(), true); return $this->getService($identifier, $options); } /** * Get an instance of a rowset object for this table * * @param array An optional associative array of configuration settings. * @return KDatabaseRowInterface */ public function getRowset(array $options = array()) { $identifier = clone $this->getIdentifier(); $identifier->path = array('database', 'rowset'); //The rowset default options $options['table'] = $this; $options['identity_column'] = $this->mapColumns($this->getIdentityColumn(), true); return $this->getService($identifier, $options); } /** * Table select method * * The name of the resulting row(set) class is based on the table class name * eg Com<Mycomp>Table<Tablename> -> Com<Mycomp>Row(set)<Tablename> * * This function will return an empty rowset if called without a parameter. * * @param mixed KDatabaseQuery, query string, array of row id's, or an id or null * @param integer The database fetch style. Default FETCH_ROWSET. * @return KDatabaseRow or KDatabaseRowset depending on the mode. By default will * return a KDatabaseRowset */ public function select( $query = null, $mode = KDatabase::FETCH_ROWSET) { //Create query object if(is_numeric($query) || is_string($query) || (is_array($query) && is_numeric(key($query)))) { $key = $this->getIdentityColumn(); $values = (array) $query; $query = $this->_database->getQuery() ->where($key, 'IN', $values); } if(is_array($query) && !is_numeric(key($query))) { $columns = $this->mapColumns($query); $query = $this->_database->getQuery(); foreach($columns as $column => $value) { $query->where($column, 'IN', $value); } } if($query instanceof KDatabaseQuery) { if(!is_null($query->columns) && !count($query->columns)) { $query->select('*'); } if(!count($query->from)) { $query->from($this->getName().' AS tbl'); } } //Create commandchain context $context = $this->getCommandContext(); $context->operation = KDatabase::OPERATION_SELECT; $context->query = $query; $context->table = $this->getBase(); $context->mode = $mode; if($this->getCommandChain()->run('before.select', $context) !== false) { //Fetch the data based on the fecthmode if($context->query) { $data = $this->_database->select($context->query, $context->mode, $this->getIdentityColumn()); //Map the columns if (($context->mode != KDatabase::FETCH_FIELD) || ($context->mode != KDatabase::FETCH_FIELD_LIST)) { if($context->mode % 2) { foreach($data as $key => $value) { $data[$key] = $this->mapColumns($value, true); } } else $data = $this->mapColumns(KConfig::unbox($data), true); } } switch($context->mode) { case KDatabase::FETCH_ROW : { $context->data = $this->getRow(); if(isset($data) && !empty($data)) { $context->data->setData($data, false)->setStatus(KDatabase::STATUS_LOADED); } break; } case KDatabase::FETCH_ROWSET : { $context->data = $this->getRowset(); if(isset($data) && !empty($data)) { $context->data->addData($data, false); } break; } default : $context->data = $data; } $this->getCommandChain()->run('after.select', $context); } return KConfig::unbox($context->data); } /** * Count table rows * * @param mixed KDatabaseQuery object or query string or null to count all rows * @return int Number of rows */ public function count($query = null) { //Count using the identity column if (is_scalar($query)) { $key = $this->getIdentityColumn(); $query = array($key => $query); } //Create query object if(is_array($query) && !is_numeric(key($query))) { $columns = $this->mapColumns($query); $query = $this->_database->getQuery(); foreach($columns as $column => $value) { $query->where($column, '=', $value); } } if($query instanceof KDatabaseQuery) { $query->count(); if(!count($query->from)) { $query->from($this->getName().' AS tbl'); } } $result = (int) $this->select($query, KDatabase::FETCH_FIELD); return $result; } /** * Table insert method * * @param object A KDatabaseRow object * @return bool|integer Returns the number of rows inserted, or FALSE if insert query was not executed. */ public function insert( KDatabaseRowInterface $row ) { //Create commandchain context $context = $this->getCommandContext(); $context->operation = KDatabase::OPERATION_INSERT; $context->data = $row; $context->table = $this->getBase(); $context->query = null; $context->affected = false; if($this->getCommandChain()->run('before.insert', $context) !== false) { //Filter the data and remove unwanted columns $data = $this->filter($context->data->getData(), true); //Get the data and apply the column mappings $data = $this->mapColumns($data); //Execute the insert query $context->affected = $this->_database->insert($context->table, $data); if($context->affected !== false) { if(((integer) $context->affected) > 0) { if($this->getIdentityColumn()) { $data[$this->getIdentityColumn()] = $this->_database->getInsertId(); } //Reverse apply the column mappings and set the data in the row $context->data->setData($this->mapColumns($data, true), false) ->setStatus(KDatabase::STATUS_CREATED); } else $context->data->setStatus(KDatabase::STATUS_FAILED); } $this->getCommandChain()->run('after.insert', $context); } return $context->affected; } /** * Table update method * * @param object A KDatabaseRow object * @return boolean|integer Returns the number of rows updated, or FALSE if insert query was not executed. */ public function update( KDatabaseRowInterface $row) { //Create commandchain context $context = $this->getCommandContext(); $context->operation = KDatabase::OPERATION_UPDATE; $context->data = $row; $context->table = $this->getBase(); $context->query = null; $context->affected = false; if($this->getCommandChain()->run('before.update', $context) !== false) { //Create where statement $query = $this->_database->getQuery(); //@TODO : Gracefully handle error if not all primary keys are set in the row foreach($this->getPrimaryKey() as $key => $column) { $query->where($column->name, '=', $this->filter(array($key => $context->data->$key), true)); } //Filter the data and remove unwanted columns $data = $this->filter($context->data->getData(true), true); //Get the data and apply the column mappings $data = $this->mapColumns($data); //Execute the update query $context->affected = $this->_database->update($context->table, $data, $query); if($context->affected !== false) { if(((integer) $context->affected) > 0) { //Reverse apply the column mappings and set the data in the row $context->data->setData($this->mapColumns($data, true), false) ->setStatus(KDatabase::STATUS_UPDATED); } else $context->data->setStatus(KDatabase::STATUS_FAILED); } //Set the query in the context $context->query = $query; $this->getCommandChain()->run('after.update', $context); } return $context->affected; } /** * Table delete method * * @param object A KDatabaseRow object * @return bool|integer Returns the number of rows deleted, or FALSE if delete query was not executed. */ public function delete( KDatabaseRowInterface $row ) { //Create commandchain context $context = $this->getCommandContext(); $context->operation = KDatabase::OPERATION_DELETE; $context->table = $this->getBase(); $context->data = $row; $context->query = null; $context->affected = false; if($this->getCommandChain()->run('before.delete', $context) !== false) { $query = $this->_database->getQuery(); //Create where statement foreach($this->getPrimaryKey() as $key => $column) { $query->where($column->name, '=', $context->data->$key); } //Execute the delete query $context->affected = $this->_database->delete($context->table, $query); //Set the query in the context if($context->affected !== false) { if(((integer) $context->affected) > 0) { $context->query = $query; $context->data->setStatus(KDatabase::STATUS_DELETED); } else $context->data->setStatus(KDatabase::STATUS_FAILED); } $this->getCommandChain()->run('after.delete', $context); } return $context->affected; } /** * Lock the table. * * return boolean True on success, false otherwise. */ public function lock() { $result = null; // Create commandchain context. $context = $this->getCommandContext(); $context->table = $this->getBase(); if($this->getCommandChain()->run('before.lock', $context) !== false) { if($this->isConnected()) { try { $context->result = $this->_database->lockTable($this->getBase(), $this->getName()); } catch(KDatabaseException $e) { throw new KDatabaseTableException($e->getMessage()); } } $this->getCommandChain()->run('after.lock', $context); } return $context->result; } /** * Unlock the table. * * return boolean True on success, false otherwise. */ public function unlock() { $result = null; // Create commandchain context. $context = $this->getCommandContext(); $context->table = $this->getBase(); if($this->getCommandChain()->run('before.unlock', $context) !== false) { if($this->isConnected()) { try { $context->result = $this->_database->unlockTable(); } catch(KDatabaseException $e) { throw new KDatabaseTableException($e->getMessage()); } } $this->getCommandChain()->run('after.unlock', $context); } return $context->result; } /** * Table filter method * * This function removes extra columns based on the table columns taking any table mappings into * account and filters the data based on each column type. * * @param boolean If TRUE, get the column information from the base table. Default is TRUE. * @param array An associative array of data to be filtered * @return array The filtered data array */ public function filter($data, $base = true) { settype($data, 'array'); //force to array // Filter out any extra columns. $data = array_intersect_key($data, $this->getColumns($base)); // Filter data based on column type foreach($data as $key => $value) { $data[$key] = $this->getColumn($key, $base)->filter->sanitize($value); } return $data; } /** * Search the behaviors to see if this table behaves as. * * Function is also capable of checking is a behavior has been mixed succesfully * using is[Behavior] function. If the behavior exists the function will return * TRUE, otherwise FALSE. * * @param string The function name * @param array The function arguments * @throws BadMethodCallException If method could not be found * @return mixed The result of the function */ public function __call($method, $arguments) { // If the method is of the form is[Bahavior] handle it. $parts = KInflector::explode($method); if($parts[0] == 'is' && isset($parts[1])) { if($this->hasBehavior(strtolower($parts[1]))) { return true; } return false; } return parent::__call($method, $arguments); } }