%PDF- %PDF-
| Direktori : /home1/lightco1/www/lightingrepublic.com.au/libraries/koowa/database/table/ |
| Current File : //home1/lightco1/www/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);
}
}