drupal-civicrm/sites/all/modules/civicrm/packages/DB/Table/Database.php
2018-01-14 13:10:16 +00:00

3496 lines
131 KiB
PHP

<?php
// vim: set et ts=4 sw=4 fdm=marker:
/**
* DB_Table_Database relational database abstraction class
*
* PHP versions 4 and 5
*
* LICENSE:
*
* Copyright (c) 1997-2007, Paul M. Jones <pmjones@php.net>
* David C. Morse <morse@php.net>
* Mark Wiesemann <wiesemann@php.net>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * The names of the authors may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @category Database
* @package DB_Table
* @author David C. Morse <morse@php.net>
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @version CVS: $Id: Database.php,v 1.15 2007/12/13 16:52:14 wiesemann Exp $
* @link http://pear.php.net/package/DB_Table
*/
// {{{ Error code constants
/**
* Parameter is not a DB/MDB2 object
*/
define('DB_TABLE_DATABASE_ERR_DB_OBJECT', -201);
/**
* Error in addTable, parameter $table_obj is not a DB_Table object
*/
define('DB_TABLE_DATABASE_ERR_DBTABLE_OBJECT', -202);
/**
* Error for table name that does not exist in the database
*/
define('DB_TABLE_DATABASE_ERR_NO_TBL', -203);
/**
* Error for table name parameter that is not a string
*/
define('DB_TABLE_DATABASE_ERR_TBL_NOT_STRING', -204);
/**
* Error in getCol for a non-existent column name
*/
define('DB_TABLE_DATABASE_ERR_NO_COL', -205);
/**
* Error in getForeignCol for a non-existent foreign key column
*/
define('DB_TABLE_DATABASE_ERR_NO_FOREIGN_COL', -206);
/**
* Error for column name that is not a string
*/
define('DB_TABLE_DATABASE_ERR_COL_NOT_STRING', -207);
/**
* Error in addTable for multiple primary keys
*/
define('DB_TABLE_DATABASE_ERR_MULT_PKEY', -208);
/**
* Error in addRef for a non-existent foreign key table
*/
define('DB_TABLE_DATABASE_ERR_NO_FTABLE', -209);
/**
* Error in addRef for non-existence referenced table
*/
define('DB_TABLE_DATABASE_ERR_NO_RTABLE', -210);
/**
* Error in addRef for null referenced key in a table with no primary key
*/
define('DB_TABLE_DATABASE_ERR_NO_PKEY', -211);
/**
* Error in addRef for an invalid foreign key, neither string nor array
*/
define('DB_TABLE_DATABASE_ERR_FKEY', -212);
/**
* Error in addRef for referenced key that is not a string, string foreign key
*/
define('DB_TABLE_DATABASE_ERR_RKEY_NOT_STRING', -213);
/**
* Error in addRef for referenced key that is not an array, array foreign key
*/
define('DB_TABLE_DATABASE_ERR_RKEY_NOT_ARRAY', -214);
/**
* Error in addRef for wrong number of columns in referenced key
*/
define('DB_TABLE_DATABASE_ERR_RKEY_COL_NUMBER', -215);
/**
* Error in addRef for non-existence foreign key (referencing) column
*/
define('DB_TABLE_DATABASE_ERR_NO_FCOL', -216);
/**
* Error in addRef for non-existence referenced column
*/
define('DB_TABLE_DATABASE_ERR_NO_RCOL', -217);
/**
* Error in addRef for referencing and referenced columns of different types
*/
define('DB_TABLE_DATABASE_ERR_REF_TYPE', -218);
/**
* Error in addRef for multiple references from one table to another
*/
define('DB_TABLE_DATABASE_ERR_MULT_REF', -219);
/**
* Error due to invalid ON DELETE action name
*/
define('DB_TABLE_DATABASE_ERR_ON_DELETE_ACTION', -220);
/**
* Error due to invalid ON UPDATE action name
*/
define('DB_TABLE_DATABASE_ERR_ON_UPDATE_ACTION', -221);
/**
* Error in addLink due to missing required reference
*/
define('DB_TABLE_DATABASE_ERR_NO_REF_LINK', -222);
/**
* Error in validCol for a column name that does not exist in the datase
*/
define('DB_TABLE_DATABASE_ERR_NO_COL_DB', -223);
/**
* Error in validCol for column name that does not exist in the specified table
*/
define('DB_TABLE_DATABASE_ERR_NO_COL_TBL', -224);
/**
* Error in a buildSQL or select* method for an undefined key of $this->sql
*/
define('DB_TABLE_DATABASE_ERR_SQL_UNDEF', -225);
/**
* Error in a buildSQL or select* method for a key of $this->sql that is
* not a string
*/
define('DB_TABLE_DATABASE_ERR_SQL_NOT_STRING', -226);
/**
* Error in buildFilter due to invalid match type
*/
define('DB_TABLE_DATABASE_ERR_MATCH_TYPE', -227);
/**
* Error in buildFilter due to invalid key for full match
*/
define('DB_TABLE_DATABASE_ERR_DATA_KEY', -228);
/**
* Error in buildFilter due to invalid key for full match
*/
define('DB_TABLE_DATABASE_ERR_FILT_KEY', -229);
/**
* Error in buildFilter due to invalid key for full match
*/
define('DB_TABLE_DATABASE_ERR_FULL_KEY', -230);
/**
* Error in insert for a failed foreign key constraint
*/
define('DB_TABLE_DATABASE_ERR_FKEY_CONSTRAINT', -231);
/**
* Error in delete due to a referentially triggered 'restrict' action
*/
define('DB_TABLE_DATABASE_ERR_RESTRICT_DELETE', -232);
/**
* Error in update due to a referentially triggered 'restrict' action
*/
define('DB_TABLE_DATABASE_ERR_RESTRICT_UPDATE', -233);
/**
* Error in fromXML for table with multiple auto_increment columns
*/
define('DB_TABLE_DATABASE_ERR_XML_MULT_AUTO_INC', -234);
/**
* Error in autoJoin, column and tables parameter both null
*/
define('DB_TABLE_DATABASE_ERR_NO_COL_NO_TBL', -235);
/**
* Error in autoJoin for ambiguous column name
*/
define('DB_TABLE_DATABASE_ERR_COL_NOT_UNIQUE', -236);
/**
* Error in autoJoin for non-unique set of join conditions
*/
define('DB_TABLE_DATABASE_ERR_AMBIG_JOIN', -237);
/**
* Error in autoJoin for failed construction of join
*/
define('DB_TABLE_DATABASE_ERR_FAIL_JOIN', -238);
/**
* Error in fromXML for PHP 4 (this function requires PHP 5)
*/
define('DB_TABLE_DATABASE_ERR_PHP_VERSION', -239);
/**
* Error parsing XML string in fromXML
*/
define('DB_TABLE_DATABASE_ERR_XML_PARSE', -240);
// }}}
// {{{ Includes
/**
* DB_Table_Base base class
*/
require_once 'DB/Table/Base.php';
/**
* DB_Table table abstraction class
*/
require_once 'DB/Table.php';
/**
* The PEAR class for errors
*/
require_once 'PEAR.php';
// }}}
// {{{ Error messages
/**
* US-English default error messages. If you want to internationalize, you can
* set the translated messages via $GLOBALS['_DB_TABLE_DATABASE']['error'].
* You can also use DB_Table_Database::setErrorMessage(). Examples:
*
* <code>
* (1) $GLOBALS['_DB_TABLE_DATABASE']['error'] = array(
* DB_TABLE_DATABASE_ERR_.. => '...',
* DB_TABLE_DATABASE_ERR_.. => '...');
* (2) DB_Table_Database::setErrorMessage(DB_TABLE_DATABASE_ERR_.., '...');
* DB_Table_Database::setErrorMessage(DB_TABLE_DATABASE_ERR_.., '...');
* (3) DB_Table_Database::setErrorMessage(array(
* DB_TABLE_DATABASE_ERR_.. => '...');
* DB_TABLE_DATABASE_ERR_.. => '...');
* (4) $obj =& new DB_Table();
* $obj->setErrorMessage(DB_TABLE_DATABASE_ERR_.., '...');
* $obj->setErrorMessage(DB_TABLE_DATABASE_ERR_.., '...');
* (5) $obj =& new DB_Table();
* $obj->setErrorMessage(array(DB_TABLE_DATABASE_ERR_.. => '...');
* DB_TABLE_DATABASE_ERR_.. => '...');
* </code>
*
* For errors that can occur with-in the constructor call (i.e. e.g. creating
* or altering the database table), only the code from examples (1) to (3)
* will alter the default error messages early enough. For errors that can
* occur later, examples (4) and (5) are also valid.
*/
$GLOBALS['_DB_TABLE_DATABASE']['default_error'] = array(
DB_TABLE_DATABASE_ERR_DB_OBJECT =>
'Invalid DB/MDB2 object parameter. Function',
DB_TABLE_DATABASE_ERR_NO_TBL =>
'Table does not exist in database. Method, Table =',
DB_TABLE_DATABASE_ERR_TBL_NOT_STRING =>
'Table name parameter is not a string in method',
DB_TABLE_DATABASE_ERR_NO_COL =>
'In getCol, non-existent column name parameter',
DB_TABLE_DATABASE_ERR_NO_FOREIGN_COL =>
'In getForeignCol, non-existent column name parameter',
DB_TABLE_DATABASE_ERR_COL_NOT_STRING =>
'Column name parameter is not a string in method',
DB_TABLE_DATABASE_ERR_DBTABLE_OBJECT =>
'Parameter of addTable is not a DB_Table object',
DB_TABLE_DATABASE_ERR_MULT_PKEY =>
'Multiple primary keys in one table detected in addTable. Table',
DB_TABLE_DATABASE_ERR_NO_FTABLE =>
'Foreign key reference from non-existent table in addRef. Reference',
DB_TABLE_DATABASE_ERR_NO_RTABLE =>
'Reference to a non-existent referenced table in addRef. Reference',
DB_TABLE_DATABASE_ERR_NO_PKEY =>
'Missing primary key of referenced table in addRef. Reference',
DB_TABLE_DATABASE_ERR_FKEY =>
'Foreign / referencing key is not a string or array in addRef',
DB_TABLE_DATABASE_ERR_RKEY_NOT_STRING =>
'Foreign key is a string, referenced key is not a string in addRef',
DB_TABLE_DATABASE_ERR_RKEY_NOT_ARRAY =>
'Foreign key is an array, referenced key is not an array in addRef',
DB_TABLE_DATABASE_ERR_RKEY_COL_NUMBER =>
'Wrong number of columns in referencing key in addRef',
DB_TABLE_DATABASE_ERR_NO_FCOL =>
'Nonexistent foreign / referencing key column in addRef. Reference',
DB_TABLE_DATABASE_ERR_NO_RCOL =>
'Nonexistent referenced key column in addRef. Reference',
DB_TABLE_DATABASE_ERR_REF_TYPE =>
'Different referencing and referenced column types in addRef. Reference',
DB_TABLE_DATABASE_ERR_MULT_REF =>
'Multiple references between two tables in addRef. Reference',
DB_TABLE_DATABASE_ERR_ON_DELETE_ACTION =>
'Invalid ON DELETE action. Reference',
DB_TABLE_DATABASE_ERR_ON_UPDATE_ACTION =>
'Invalid ON UPDATE action. Reference',
DB_TABLE_DATABASE_ERR_NO_REF_LINK =>
'Error in addLink due to missing required reference(s)',
DB_TABLE_DATABASE_ERR_NO_COL_DB =>
'In validCol, column name does not exist in database. Column',
DB_TABLE_DATABASE_ERR_NO_COL_TBL =>
'In validCol, column does not exist in specified table. Column',
DB_TABLE_DATABASE_ERR_SQL_UNDEF =>
'Query string is not a key of $sql property array. Key is',
DB_TABLE_DATABASE_ERR_SQL_NOT_STRING =>
'Query is neither an array nor a string',
DB_TABLE_DATABASE_ERR_MATCH_TYPE =>
'Invalid match parameter of buildFilter',
DB_TABLE_DATABASE_ERR_DATA_KEY =>
'Invalid data_key in buildFilter, neither string nor array',
DB_TABLE_DATABASE_ERR_FILT_KEY =>
'Incompatible data_key and filter_key in buildFilter',
DB_TABLE_DATABASE_ERR_FULL_KEY =>
'Invalid key value in buildFilter: Mixed null and not null',
DB_TABLE_DATABASE_ERR_FKEY_CONSTRAINT =>
'Foreign key constraint failure: Key does not reference any rows',
DB_TABLE_DATABASE_ERR_RESTRICT_DELETE =>
'Referentially trigger restrict of delete from table',
DB_TABLE_DATABASE_ERR_RESTRICT_UPDATE =>
'Referentially trigger restrict of update of table',
DB_TABLE_DATABASE_ERR_NO_COL_NO_TBL =>
'No columns or tables provided as parameters to autoJoin',
DB_TABLE_DATABASE_ERR_COL_NOT_UNIQUE =>
'Ambiguous column name in autoJoin. Column',
DB_TABLE_DATABASE_ERR_AMBIG_JOIN =>
'Ambiguous join in autoJoin, during join of table',
DB_TABLE_DATABASE_ERR_FAIL_JOIN =>
'Failed join in autoJoin, failed to join table',
DB_TABLE_DATABASE_ERR_PHP_VERSION =>
'PHP 5 is required for fromXML method. Interpreter version is',
DB_TABLE_DATABASE_ERR_XML_PARSE =>
'Error parsing XML in fromXML method'
);
// merge default and user-defined error messages
if (!isset($GLOBALS['_DB_TABLE_DATABASE']['error'])) {
$GLOBALS['_DB_TABLE_DATABASE']['error'] = array();
}
foreach ($GLOBALS['_DB_TABLE_DATABASE']['default_error'] as $code => $message) {
if (!array_key_exists($code, $GLOBALS['_DB_TABLE_DATABASE']['error'])) {
$GLOBALS['_DB_TABLE_DATABASE']['error'][$code] = $message;
}
}
// }}}
// {{{ DB_Table_Database
/**
* Relational database abstraction class
*
* DB_Table_Database is an abstraction class for a relational database.
* It is a layer built on top of DB_Table, in which each table in a
* database is represented as an instance of DB_Table. It provides:
*
* - an object-oriented representation of the database schema
* - automated construction of SQL commands for simple joins
* - an API for insert, update, and select commands very similar
* to that of DB_Table, with optional emulation of standard SQL
* foreign key integrity checks and referential triggered actions
* such as cascading deletes.
* - Serialization and unserialization of the database schema via
* either php serialization or XML, using the MDB2 XML schema.
*
* @category Database
* @package DB_Table
* @author David C. Morse <morse@php.net>
* @version Release: 1.5.6
* @link http://pear.php.net/package/DB_Table
*/
class DB_Table_Database extends DB_Table_Base
{
// {{{ properties
/**
* Name of the database
*
* @var string
* @access public
*/
var $name = null;
/**
* Associative array of DB_Table object references. Keys are table names.
*
* Associative array in which keys are table names, values are references to
* DB_Table objects. Each referenced DB_Table object represents one table in
* the database.
*
* @var array
* @access private
*/
var $_table = array();
/**
* Array in which keys are table names, values are DB_Table subclass names.
*
* See the getTableSubclass() method docblock for further details.
*
* @var array
* @access private
*/
var $_table_subclass = array();
/**
* Path to directory containing DB_Table subclass declaration files
*
* See the setTableSubclassPath() method docblock for further details.
*
* @var string
* @access private
*/
var $_table_subclass_path = '';
/**
* Array in which keys are table names, values are primary keys.
*
* Each primary key value may be a column name string, a sequential array of
* column name strings, or null.
*
* See the getPrimaryKey() method docblock for details.
*
* @var array
* @access private
*/
var $_primary_key = array();
/**
* Associative array that maps column names keys to table names.
*
* Each key is the name string of a column in the database. Each value
* is a numerical array containing the names of all tables that contain
* a column with that name.
*
* See the getCol() method docblock for details.
*
* @var array
* @access private
*/
var $_col = array();
/**
* Associative array that maps names of foreign key columns to table names
*
* Each key is the name string of a foreign key column. Each value is a
* sequential array containing the names of all tables that contain a
* foreign key column with that name.
*
* See the getForeignCol() method docblock for further details.
*
* @var array
* @access private
*/
var $_foreign_col = array();
/**
* Two-dimensional associative array of foreign key references.
*
* Keys are pairs of table names (referencing table first, referenced
* table second). Each value is an array containing information about
* the referencing and referenced keys, and about any referentially
* triggered actions (e.g., cascading delete).
*
* See the getRef() docblock for further details.
*
* @var array
* @access private
*/
var $_ref = array();
/**
* Array in which each key is the names of a referenced tables, each value
* an sequential array containing names of referencing tables.
*
* See the docblock for the getRefTo() method for further discussion.
*
* @var array
* @access private
*/
var $_ref_to = array();
/**
* Two-dimensional associative array of linking tables.
*
* Two-dimensional associative array in which pairs of keys are names
* of pairs of tables that are linked by one or more linking/association
* table. Each value is an array containing the names of all table that
* link the tables specified by the pair of keys. A linking table is a
* table that creates a many-to-many relationship between two linked
* tables, via foreign key references from the linking table to the two
* linked tables. The $_link property is used by the autoJoin() method
* to join tables that are related only through such a linking table.
*
* See the getLink() method docblock for further details.
*
* @var array
* @access private
*/
var $_link = array();
/**
* Take on_update actions if $_act_on_update is true
*
* By default, on_update actions are enabled ($_act_on_update = true)
*
* @var boolean
* @access private
*/
var $_act_on_update = true;
/**
* Take on_delete actions if $_act_on_delete is true
*
* By default, on_delete actions are enabled ($_act_on_delete = true)
*
* @var boolean
* @access private
*/
var $_act_on_delete = true;
/**
* Validate foreign keys before insert or update if $_check_fkey is true
*
* By default, validation is disabled ($_check_fkey = false)
*
* @var boolean
* @access private
*/
var $_check_fkey = false;
/**
* If the column keys in associative array return sets are fixed case
* (all upper or lower case) this property should be set true.
*
* The column keys in rows of associative array return sets may either
* preserve capitalization of the column names or they may be fixed case,
* depending on the options set in the backend (DB/MDB2) and on phptype.
* If these column names are returned with a fixed case (either upper
* or lower), $_fix_case must be set true in order for php emulation of
* ON DELETE and ON UPDATE actions to work correctly. Otherwise, the
* $_fix_case property should be false (the default).
*
* The choice between mixed or fixed case column keys may be made by using
* using the setFixCase() method, which resets both the behavior of the
* backend and the $_fix_case property. It may also be changed by using the
* setOption() method of the DB or MDB2 backend object to directly set the
* DB_PORTABILITY_LOWERCASE or MDB2_PORTABILITY_FIX_CASE bits of the
* DB/MDB2 'portability' option.
*
* By default, DB returns mixed case and MDB2 returns lower case.
*
* @see DB_Table_Database::setFixCase()
* @see DB::setOption()
* @see MDB2::setOption()
*
* @var boolean
* @access private
*/
var $_fix_case = false;
// }}}
// {{{ Methods
// {{{ function DB_Table_Database(&$db, $name)
/**
* Constructor
*
* If an error is encountered during instantiation, the error
* message is stored in the $this->error property of the resulting
* object. See $error property docblock for a discussion of error
* handling.
*
* @param object &$db DB/MDB2 database connection object
* @param string $name the database name
* @return object DB_Table_Database
* @access public
*/
function __construct(&$db, $name)
{
// Is $db an DB/MDB2 object or null?
if (is_a($db, 'db_common')) {
$this->backend = 'db';
$this->fetchmode = DB_FETCHMODE_ORDERED;
} elseif (is_a($db, 'mdb2_driver_common')) {
$this->backend = 'mdb2';
$this->fetchmode = MDB2_FETCHMODE_ORDERED;
} else {
$code = DB_TABLE_DATABASE_ERR_DB_OBJECT ;
$text = $GLOBALS['_DB_TABLE_DATABASE']['error'][$code]
. ' DB_Table_Database';
$this->error = PEAR::throwError($text, $code);
return;
}
$this->db =& $db;
$this->name = $name;
$this->_primary_subclass = 'DB_TABLE_DATABASE';
$this->setFixCase(false);
}
// }}}
// {{{ function setDBconnection(&$db)
/**
* Set DB/MDB2 connection instance for database and all tables
*
* Assign a reference to the DB/MDB2 object $db to $this->db, set
* $this->backend to 'db' or 'mdb2', and set the same pair of
* values for the $db and $backend properties of every DB_Table
* object in the database.
*
* @param object &$db DB/MDB2 connection object
* @return boolean True on success (PEAR_Error on failure)
*
* @throws PEAR_Error if
* $db is not a DB or MDB2 object(DB_TABLE_DATABASE_ERR_DB_OBJECT)
*
* @access public
*/
function setDBconnection(&$db)
{
// Is the first argument a DB/MDB2 object ?
if (is_subclass_of($db, 'DB_Common')) {
$backend = 'db';
} elseif (is_subclass_of($db, 'MDB2_Driver_Common')) {
$backend = 'mdb2';
} else {
return $this->throwError(
DB_TABLE_DATABASE_ERR_DB_OBJECT,
"setDBconnection");
}
// Set db and backend for database and all of its tables
$this->db =& $db;
$this->backend = $backend;
foreach ($this->_table as $name => $table) {
$table->db =& $db;
$table->backend = $backend;
}
return true;
}
// }}}
// {{{ function setActOnDelete($flag = true)
/**
* Turns on (or off) automatic php emulation of SQL ON DELETE actions
*
* @param bool $flag True to enable action, false to disable
* @return void
* @access public
*/
function setActOnDelete($flag = true)
{
if ($flag) {
$this->_act_on_delete = true;
} else {
$this->_act_on_delete = false;
}
}
// }}}
// {{{ function setActOnUpdate($flag = true)
/**
* Turns on (or off) automatic php emulation of ON UPDATE actions
*
* @param bool $flag True to enable action, false to disable
* @return void
* @access public
*/
function setActOnUpdate($flag = true)
{
if ($flag) {
$this->_act_on_update = true;
} else {
$this->_act_on_update = false;
}
}
// }}}
// {{{ function setCheckFKey($flag = true)
/**
* Turns on (or off) validation of foreign key values on insert and update
*
* @param bool $flag True to enable foreign key validation, false to disable
* @return void
* @access public
*/
function setCheckFKey($flag = true)
{
if ($flag) {
$this->_check_fkey = true;
} else {
$this->_check_fkey = false;
}
}
// }}}
// {{{ function setFixCase($flag = false)
/**
* Sets backend option such that column keys in associative array return
* sets are converted to fixed case, if true, or mixed case, if false.
*
* Sets the DB/MDB2 'portability' option, and sets $this->_fix_case = $flag.
* Because it sets an option in the underlying DB/MDB2 connection object,
* this effects the behavior of all objects that share the connection.
*
* @param bool $flag True for fixed lower case, false for mixed
* @return void
* @access public
*/
function setFixCase($flag = false)
{
$flag = (bool) $flag;
$option = $this->db->getOption('portability');
if ($this->backend == 'db') {
$option = $option | DB_PORTABILITY_LOWERCASE;
if (!$flag) {
$option = $option ^ DB_PORTABILITY_LOWERCASE;
}
} else {
$option = $option | MDB2_PORTABILITY_FIX_CASE;
if (!$flag) {
$option = $option ^ MDB2_PORTABILITY_FIX_CASE;
}
}
$this->db->setOption('portability', $option);
$this->_fix_case = $flag;
}
// }}}
// {{{ function &getDBInstance()
/**
* Return reference to $this->db DB/MDB2 object wrapped by $this
*
* @return object Reference to DB/MDB2 object
* @access public
*/
function &getDBInstance()
{
return $this->db;
}
// }}}
// {{{ function getTable($name = null)
/**
* Returns all or part of $_table property array
*
* If $name is absent or null, return entire $_table property array.
* If $name is a table name, return $this->_table[$name] DB_Table object
* reference
*
* The $_table property is an associative array in which keys are table
* name strings and values are references to DB_Table objects. Each of
* the referenced objects represents one table in the database.
*
* @param string $name Name of table
* @return mixed $_table property, or one element of $_table
* (PEAR_Error on failure)
*
* @throws PEAR_Error if:
* - $name is not a string ( DB_TABLE_DATABASE_ERR_TBL_NOT_STRING )
* - $name is not valid table name ( DB_TABLE_DATABASE_ERR_NO_TBL )
*
* @access public
*/
function getTable($name = null)
{
if (is_null($name)) {
return $this->_table;
} elseif (is_string($name)) {
if (isset($this->_table[$name])) {
return $this->_table[$name];
} else {
return $this->throwError(
DB_TABLE_DATABASE_ERR_NO_TBL,
"getTable, $name");
}
} else {
return $this->throwError(
DB_TABLE_DATABASE_ERR_TBL_NOT_STRING,
"getTable");
}
}
// }}}
// {{{ function getPrimaryKey($name = null)
/**
* Returns all or part of the $_primary_key property array
*
* If $name is null, return the $this->_primary_key property array
* If $name is a table name, return $this->_primary_key[$name]
*
* The $_primary_key property is an associative array in which each key
* a table name, and each value is the primary key of that table. Each
* primary key value may be a column name string, a sequential array of
* column name strings (for a multi-column key), or null (if no primary
* key has been declared).
*
* @param string $name Name of table
* @return mixed $this->primary_key array or $this->_primary_key[$name]
* (PEAR_Error on failure)
*
* @throws PEAR_Error if:
* - $name is not a string ( DB_TABLE_DATABASE_ERR_TBL_NOT_STRING )
* - $name is not valid table name ( DB_TABLE_DATABASE_ERR_NO_TBL )
*
* @access public
*/
function getPrimaryKey($name = null)
{
if (is_null($name)) {
return $this->_primary_key;
} elseif (is_string($name)) {
if (isset($this->_primary_key[$name])) {
return $this->_primary_key[$name];
} else {
return $this->throwError(
DB_TABLE_DATABASE_ERR_NO_TBL,
"getPrimaryKey, $name");
}
} else {
return $this->throwError(
DB_TABLE_DATABASE_ERR_TBL_NOT_STRING,
"getPrimaryKey");
}
}
// }}}
// {{{ function getTableSubclass($name = null)
/**
* Returns all or part of the $_table_subclass property array
*
* If $name is null, return the $this->_table_subclass property array
* If $name is a table name, return $this->_table_subclass[$name]
*
* The $_table_subclass property is an associative array in which each key
* is a table name string, and each value is the name of the corresponding
* subclass of DB_Table. The value is null if the table is an instance of
* DB_Table itself.
*
* Subclass names are set within the addTable method by applying the
* built in get_class() function to a DB_Table object. The class names
* returned by get_class() are stored unmodified. In PHP 4, get_class
* converts all class names to lower case. In PHP 5, it preserves the
* capitalization of the name used in the class definition.
*
* For autoloading of class definitions to work properly in the
* __wakeup() method, the base name of each subclass definition
* file (excluding the .php extension) should thus be a identical
* to the class name in PHP 5, and a lower case version of the
* class name in PHP 4 or
*
* @param string $name Name of table
* @return mixed $_table_subclass array or $this->_table_subclass[$name]
* (PEAR_Error on failure)
*
* @throws PEAR_Error if:
* - $name is not a string ( DB_TABLE_DATABASE_TBL_NOT_STRING )
* - $name is not valid table name ( DB_TABLE_DATABASE_NO_TBL )
*
* @access public
*
@ @see DB_Table_Database::__wakeup()
*/
function getTableSubclass($name = null)
{
if (is_null($name)) {
return $this->_table_subclass;
} elseif (is_string($name)) {
if (isset($this->_table_subclass[$name])) {
return $this->_table_subclass[$name];
} else {
return $this->throwError(
DB_TABLE_DATABASE_ERR_NO_TBL,
"getTableSubclass, $name");
}
} else {
return $this->throwError(
DB_TABLE_DATABASE_ERR_TBL_NOT_STRING,
"getTableSubclass");
}
}
// }}}
// {{{ function getCol($column_name = null)
/**
* Returns all or part of the $_col property array
*
* If $column_name is null, return $_col property array
* If $column_name is valid, return $_col[$column_name] subarray
*
* The $_col property is an associative array in which each key is the
* name of a column in the database, and each value is a numerical array
* containing the names of all tables that contain a column with that
* name.
*
* @param string $column_name a column name string
* @return mixed $this->_col property array or $this->_col[$column_name]
* (PEAR_Error on failure)
*
* @throws PEAR_Error if:
* - $column_name is not a string (DB_TABLE_DATABASE_ERR_COL_NOT_STRING)
* - $column_name is not valid column name (DB_TABLE_DATABASE_NO_COL)
*
* @access public
*/
function getCol($column_name = null)
{
if (is_null($column_name)) {
return $this->_col;
} elseif (is_string($column_name)) {
if (isset($this->_col[$column_name])) {
return $this->_col[$column_name];
} else {
return $this->throwError(
DB_TABLE_DATABASE_ERR_NO_COL,
"'$column_name'");
}
} else {
return $this->throwError(
DB_TABLE_DATABASE_ERR_COL_NOT_STRING,
'getCol');
}
}
// }}}
// {{{ function getForeignCol($column_name = null)
/**
* Returns all or part of the $_foreign_col property array
*
* If $column_name is null, return $this->_foreign_col property array
* If $column_name is valid, return $this->_foreign_col[$column_name]
*
* The $_foreign_col property is an associative array in which each
* key is the name string of a foreign key column, and each value is a
* sequential array containing the names of all tables that contain a
* foreign key column with that name.
*
* If a column $column in a referencing table $ftable is part of the
* foreign key for references to two or more different referenced tables
* tables, the name $ftable will also appear multiple times in the array
* $this->_foreign_col[$column].
*
* Returns a PEAR_Error with the following DB_TABLE_DATABASE_* error
* codes if:
* - $column_name is not a string ( _COL_NOT_STRING )
* - $column_name is not valid foreign column name ( _NO_FOREIGN_COL )
*
* @param string column name string for foreign key column
* @return array $_foreign_col property array
* @access public
*/
function getForeignCol($column_name = null)
{
if (is_null($column_name)) {
return $this->_foreign_col;
} elseif (is_string($column_name)) {
if (isset($this->_foreign_col[$column_name])) {
return $this->_foreign_col[$column_name];
} else {
return $this->throwError(
DB_TABLE_DATABASE_ERR_NO_FOREIGN_COL,
$column_name);
}
} else {
return $this->throwError(
DB_TABLE_DATABASE_ERR_COL_NOT_STRING,
'getForeignCol');
}
}
// }}}
// {{{ function getRef($table1 = null, $table2 = null)
/**
* Returns all or part of the $_ref two-dimensional property array
*
* Returns $this->_ref 2D property array if $table1 and $table2 are null.
* Returns $this->_ref[$table1] subarray if only $table2 is null.
* Returns $this->_ref[$table1][$table2] if both parameters are present.
*
* Returns null if $table1 is a table that references no others, or
* if $table1 and $table2 are both valid table names, but there is no
* reference from $table1 to $table2.
*
* The $_ref property is a two-dimensional associative array in which
* the keys are pairs of table names, each value is an array containing
* information about referenced and referencing keys, and referentially
* triggered actions (if any). An element of the $_ref array is of the
* form $ref[$ftable][$rtable] = $reference, where $ftable is the name
* of a referencing (or foreign key) table and $rtable is the name of
* a corresponding referenced table. The value $reference is an array
* $reference = array($fkey, $rkey, $on_delete, $on_update) in which
* $fkey and $rkey are the foreign (or referencing) and referenced
* keys, respectively: Foreign key $fkey of table $ftable references
* key $rkey of table $rtable. The values of $fkey and $rkey must either
* both be valid column name strings for columns of the same type, or
* they may both be sequential arrays of column name names, with equal
* numbers of columns of corresponding types, for multi-column keys. The
* $on_delete and $on_update values may be either null or string values
* that indicate actions to be taken upon deletion or updating of a
* referenced row (e.g., cascading deletes). A null value of $on_delete
* or $on_update indicates that no referentially triggered action will
* be taken. See addRef() for further details about allowed values of
* these action strings.
*
* @param string $table1 name of referencing table
* @param string $table2 name of referenced table
* @return mixed $ref property array, sub-array, or value
*
* @throws a PEAR_Error if:
* - $table1 or $table2 is not a string (.._DATABASE_ERR_TBL_NOT_STRING)
* - $table1 or $table2 is not a table name (.._DATABASE_ERR_NO_TBL)
*
* @access public
*/
function getRef($table1 = null, $table2 = null)
{
if (is_null($table1)) {
return $this->_ref;
} elseif (is_string($table1)) {
if (isset($this->_ref[$table1])) {
if (is_null($table2)) {
return $this->_ref[$table1];
} elseif (is_string($table2)) {
if (isset($this->_ref[$table1][$table2])) {
return $this->_ref[$table1][$table2];
} else {
if (isset($this->_table[$table2])) {
// Valid table names but no references to
return null;
} else {
// Invalid table name
return $this->throwError(
DB_TABLE_DATABASE_ERR_NO_TBL,
"getRef, $table2");
}
}
} else {
return $this->throwError(
DB_TABLE_DATABASE_ERR_TBL_NOT_STRING,
"getRef");
}
} else {
if (isset($this->_table[$table1])) {
// Valid table name, but no references from
return null;
} else {
// Invalid table name
return $this->throwError(
DB_TABLE_DATABASE_ERR_NO_TBL,
"getRef, $table1");
}
}
} else {
return $this->throwError(
DB_TABLE_DATABASE_ERR_TBL_NOT_STRING,
"getRef");
}
}
// }}}
// {{{ function getRefTo($table_name = null)
/**
* Returns all or part of the $_ref_to property array
*
* Returns $this->_ref_to property array if $table_name is null.
* Returns $this->_ref_to[$table_name] if $table_name is not null.
*
* The $_ref_to property is an associative array in which each key
* is the name of a referenced table, and each value is a sequential
* array containing the names of all tables that contain foreign keys
* that reference that table. Each element is thus of the form
* $_ref_to[$rtable] = array($ftable1, $ftable2,...), where
* $ftable1, $ftable2, ... are the names of tables that reference
* the table named $rtable.
*
* @param string $table_name name of table
* @return mixed $_ref_to property array or subarray
* (PEAR_Error on failure)
*
* @throws PEAR_Error if:
* - $table_name is not a string ( .._DATABASE_ERR_TBL_NOT_STRING )
* - $table_name is not a table name ( .._DATABASE_ERR_NO_TBL )
*
* @access public
*/
function getRefTo($table_name = null)
{
if (is_null($table_name)) {
return $this->_ref_to;
} elseif (is_string($table_name)) {
if (isset($this->_ref_to[$table_name])) {
return $this->_ref_to[$table_name];
} else {
if (isset($this->_table[$table_name])) {
// Valid table name, but no references to
return null;
} else {
// Invalid table name
return $this->throwError(
DB_TABLE_DATABASE_ERR_NO_TBL,
"getRefTo, $table_name");
}
}
} else {
return $this->throwError(
DB_TABLE_DATABASE_ERR_TBL_NOT_STRING,
"getRefTo");
}
}
// }}}
// {{{ function getLink($table1 = null, $table2 = null)
/**
* Returns all or part of the $link two-dimensional property array
*
* Returns $this->_link 2D property array if $table1 and $table2 are null.
* Returns $this->_link[$table1] subarray if only $table2 is null.
* Returns $this->_link[$table1][$table2] if both parameters are present.
*
* Returns null if $table1 is a valid table with links to no others, or
* if $table1 and $table2 are both valid table names but there is no
* link between them.
*
* The $_link property is a two-dimensional associative array with
* elements of the form $this->_link[$table1][$table2] = array($link1, ...),
* in which the value is an array containing the names of all tables
* that `link' tables named $table1 and $table2, and thereby create a
* many-to-many relationship between these two tables.
*
* The $_link property is used in the autoJoin method to join tables
* that are related by a many-to-many relationship via a linking table,
* rather than via a direct foreign key reference. A table that is
* declared to be linking table for tables $table1 and $table2 must
* contain foreign keys that reference both of these tables.
*
* Each binary link in a database is listed twice in $_link, in
* $_link[$table1][$table2] and in $_link[$table2][$table1]. If a
* linking table contains foreign key references to N tables, with
* N > 2, each of the resulting binary links is listed separately.
* For example, a table with references to 3 tables A, B, and C can
* create three binary links (AB, AC, and BC) and six entries in the
* link property array (i.e., in $_link[A][B], $_link[B][A], ... ).
*
* Linking tables may be added to the $_link property by using the
* addLink method or deleted using the delLink method. Alternatively,
* all possible linking tables can be identified and added to the
* $_link array at once by the addAllLinks() method.
*
* @param string $table1 name of linked table
* @param string $table2 name of linked table
* @return mixed $_link property array, sub-array, or value
*
* @throws PEAR_Error:
* - $table1 or $table2 is not a string (..DATABASE_ERR_TBL_NOT_STRING)
* - $table1 or $table2 is not a table name (..DATABASE_ERR_NO_TBL)
*
* @access public
*/
function getLink($table1 = null, $table2 = null)
{
if (is_null($table1)) {
return $this->_link;
} elseif (is_string($table1)) {
if (isset($this->_link[$table1])) {
if (is_null($table2)) {
return $this->_link[$table1];
} elseif (is_string($table2)) {
if (isset($this->_link[$table1][$table2])) {
return $this->_link[$table1][$table2];
} else {
if (isset($this->_table[$table2])) {
// Valid table names, but no links
return null;
} else {
// Invalid 2nd table name string
return $this->throwError(
DB_TABLE_DATABASE_ERR_NO_TBL,
"getLink, $table2");
}
}
} else {
return $this->throwError(
DB_TABLE_DATABASE_ERR_TBL_NOT_STRING,
"getLink");
}
} else {
if (isset($this->_table[$table1])) {
// Valid first table name, but no links
return null;
} else {
// Invalid 1st table name string
return $this->throwError(
DB_TABLE_DATABASE_ERR_NO_TBL,
"getLink, $table1");
}
}
} else {
return $this->throwError(
DB_TABLE_DATABASE_ERR_TBL_NOT_STRING,
"getLink");
}
}
// }}}
// {{{ function setTableSubclassPath($path)
/**
* Sets path to a directory containing DB_Table subclass definitions.
*
* This method sets the $_table_subclass_path string property. The value of
* this property is the path to the directory containing DB_Table subclass
* definitions, without a trailing directory separator.
*
* This path may be used by the __wakeup(), if necessary, in an attempt to
* autoload class definitions when unserializing a DB_Table_Database object
* and its child DB_Table objects. If a DB_Table subclass $subclass_name
* has not been defined when it is needed in DB_Table_Database::__wakeup(),
* to unserialize an instance of this class, the __wakeup() method attempts
* to include a class definition file from this directory, as follows:
* <code>
* $dir = $this->_table_subclass_path;
* require_once $dir . '/' . $subclass . '.php';
* </code>
* See the getTableSubclass() docblock for a discusion of capitalization
* conventions in PHP 4 and 5 for subclass file names.
*
* @param string $path path to directory containing class definitions
* @return void
* @access public
*
* @see DB_Table_Database::getTableSubclass()
*/
function setTableSubclassPath($path)
{
$this->_table_subclass_path = $path;
}
// }}}
// {{{ function addTable(&$table_obj)
/**
* Adds a table to the database.
*
* Creates references between $this DB_Table_Database object and
* the child DB_Table object, by adding a reference to $table_obj
* to the $this->_table array, and setting $table_obj->database =
* $this.
*
* Adds the primary key to $this->_primary_key array. The relevant
* element of $this->_primary_key is set to null if no primary key
* index is declared. Returns an error if more than one primary key
* is declared.
*
* Returns true on success, and PEAR error on failure. Returns the
* following DB_TABLE_DATABASE_ERR_* error codes if:
* - $table_obj is not a DB_Table ( _DBTABLE_OBJECT )
* - more than one primary key is defined ( _ERR_MULT_PKEY )
*
* @param object &$table_obj the DB_Table object (reference)
* @return boolean true on success (PEAR_Error on failure)
* @access public
*/
function addTable(&$table_obj)
{
// Check that $table_obj is a DB_Table object
// Identify subclass name, if any
if (is_subclass_of($table_obj, 'DB_Table')) {
$subclass = get_class($table_obj);
} elseif (is_a($table_obj, 'DB_Table')) {
$subclass = null;
} else {
return $this->throwError(
DB_TABLE_DATABASE_ERR_DBTABLE_OBJECT);
}
// Identify table name and table object (sub)class name
$table = $table_obj->table;
// Set $this->_primary_key[$table]
$this->_primary_key[$table] = null;
foreach ($table_obj->idx as $idx_name => $idx_def) {
if ($idx_def['type'] == 'primary') {
if (is_null($this->_primary_key[$table])) {
$this->_primary_key[$table] = $idx_def['cols'];
} else {
// More than one primary key defined in the table
unset($this->_primary_key[$table]);
return $this->throwError(
DB_TABLE_DATABASE_ERR_MULT_PKEY, $table);
}
}
}
// Add references between $this parent and child table object
$this->_table[$table] =& $table_obj;
$table_obj->setDatabaseInstance($this);
// Add subclass name (if any) to $this->_table_subclass
$this->_table_subclass[$table] = $subclass;
// Set shared properties
$table_obj->db =& $this->db;
$table_obj->backend = $this->backend;
$table_obj->fetchmode = $this->fetchmode;
// Add all columns to $_col property
foreach ($table_obj->col as $key => $def) {
if (!isset($this->_col[$key])) {
$this->_col[$key] = array();
}
$this->_col[$key][] = $table;
}
return true;
}
// }}}
// {{{ function deleteTable($table)
/**
* Deletes a table from $this database object.
*
* Removes all dependencies on $table from the database model. The table
* is removed from $_table and $_primary_key properties. Its columns are
* removed from the $_col and $_foreign_col properties. References to
* and from the table are removed from the $_ref, $_ref_to, and $_link
* properties. Referencing columns are removed from $_foreign_col.
*
* @param string $table name of table to be deleted
* @return void
* @access public
*/
function deleteTable($table)
{
if (isset($this->_table[$table])) {
$table_obj =& $this->_table[$table];
} else {
return;
}
// Remove reference to database from table object
$null_instance = null;
$table_obj->setDatabaseInstance($null_instance);
// Remove columns from $_col and $_foreign_col property arrays
foreach ($table_obj->col as $column => $def) {
$key = array_search($table, $this->_col[$column]);
if (is_integer($key)) {
unset($this->_col[$column][$key]);
if (count($this->_col[$column]) == 0) {
unset($this->_col[$column]);
} else {
$new = array_values($this->_col[$column]);
$this->_col[$column] = $new;
}
}
if (isset($this->_foreign_col[$column])) {
$key = array_search($table, $this->_foreign_col[$column]);
if (is_integer($key)) {
unset($this->_foreign_col[$column][$key]);
if (count($this->_foreign_col[$column]) == 0) {
unset($this->_foreign_col[$column]);
} else {
$new = array_values($this->_foreign_col[$column]);
$this->_foreign_col[$column] = $new;
}
}
}
}
// Remove all references involving the deleted table.
// Corresponding links are removed from $this->_link by deleteRef
// Referencing columns are removed from $this->_foreign_col by deleteRef
foreach ($this->_ref as $ftable => $referenced) {
foreach ($referenced as $rtable => $ref) {
if ($ftable == $table || $rtable == $table) {
$this->deleteRef($ftable, $rtable);
}
}
}
// Remove table from $this->_table and $this->_primary_key
unset($this->_table[$table]);
unset($this->_primary_key[$table]);
}
// }}}
// {{{ function addRef($ftable, $fkey, $rtable, [$rkey], [$on_delete], [$on_update])
/**
* Adds a foreign key reference to the database.
*
* Adds a reference from foreign key $fkey of table $ftable to
* referenced key $rkey of table named $rtable to the $this->_ref
* property. The values of $fkey and $rkey (if not null) may either
* both be column name strings (for single column keys) or they
* may both be numerically indexed arrays of corresponding column
* names (for multi-column keys). If $rkey is null (the default),
* the referenced key taken to be the primary key of $rtable, if
* any.
*
* The $on_delete and $on_update parameters may be either be null,
* or may have string values 'restrict', 'cascade', 'set null', or
* 'set default' that indicate referentially triggered actions to be
* taken deletion or updating of referenced row in $rtable. Each of
* these actions corresponds to a standard SQL action (e.g., cascading
* delete) that may be taken upon referencing rows of table $ftable
* when a referenced row of $rtable is deleted or updated. A PHP
* null value for either parameter (the default) signifies that no
* such action will be taken upon deletion or updating.
*
* There may no more than one reference from a table to another, though
* reference may contain multiple columns.
*
* Returns true on success, and PEAR error on failure. Returns the
* following DB_TABLE_DATABASE_ERR_* error codes if:
* - $ftable does not exist ( _NO_FTABLE )
* - $rtable does not exist ( _NO_RTABLE )
* - $rkey is null and $rtable has no primary key ( _NO_PKEY )
* - $fkey is neither a string nor an array ( _FKEY )
* - $rkey is not a string, $fkey is a string ( _RKEY_NOT_STRING )
* - $rkey is not an array, $fkey is an array ( _RKEY_NOT_ARRAY )
* - A column of $fkey does not exist ( _NO_FCOL )
* - A column of $rkey does not exist ( _NO_RCOL )
* - A column of $fkey and $rkey have different types ( _REF_TYPE )
* - A reference from $ftable to $rtable already exists ( _MULT_REF )
*
* @param string $ftable name of foreign/referencing table
* @param mixed $fkey foreign key in referencing table
* @param string $rtable name of referenced table
* @param mixed $rkey referenced key in referenced table
* @param string $on_delete action upon delete of a referenced row.
* @param string $on_update action upon update of a referenced row.
* @return boolean true on success (PEAR_Error on failure)
* @access public
*/
function addRef($ftable, $fkey, $rtable, $rkey = null,
$on_delete = null, $on_update = null)
{
// Check existence of $ftable is a key in $this->_table.
if (isset($this->_table[$ftable])) {
$ftable_obj =& $this->_table[$ftable];
} else {
return $this->throwError(
DB_TABLE_DATABASE_ERR_NO_FTABLE,
"$ftable => $rtable");
}
// Check existence of referenced table
if (isset($this->_table[$rtable])) {
$rtable_obj =& $this->_table[$rtable];
} else {
return $this->throwError(
DB_TABLE_DATABASE_ERR_NO_RTABLE,
"$ftable => $rtable");
}
// If referenced key is null, set it to the primary key
if (!$rkey) {
if (isset($this->_primary_key[$rtable])) {
$rkey = $this->_primary_key[$rtable];
} else {
// Error: null referenced key and no primary key
return $this->throwError(
DB_TABLE_DATABASE_ERR_NO_PKEY,
"$ftable => $rtable");
}
}
// Check $fkey and $rkey types and compatibility
if (is_string($fkey)) {
if (!is_string($rkey)) {
return $this->throwError(
DB_TABLE_DATABASE_ERR_RKEY_NOT_STRING,
"$ftable => $rtable");
}
if (!isset($ftable_obj->col[$fkey])) {
return $this->throwError(
DB_TABLE_DATABASE_ERR_NO_FCOL,
"$ftable.$fkey => $rtable.$rkey");
}
if (!isset($rtable_obj->col[$rkey])) {
return $this->throwError(
DB_TABLE_DATABASE_ERR_NO_RCOL,
"$ftable.$fkey => $rtable.$rkey");
}
$ftype = $ftable_obj->col[$fkey]['type'];
$rtype = $rtable_obj->col[$rkey]['type'];
if (!($rtype == $ftype)) {
return $this->throwError(
DB_TABLE_DATABASE_ERR_REF_TYPE,
"$ftable.$fkey => $rtable.$rkey");
}
} elseif (is_array($fkey)) {
if (!is_array($rkey)) {
return $this->throwError(
DB_TABLE_DATABASE_ERR_RKEY_NOT_ARRAY,
"$ftable => $rtable");
}
if (!(count($fkey) == count($rkey))) {
return $this->throwError(
DB_TABLE_DATABASE_ERR_RKEY_COL_NUMBER,
"$ftable => $rtable");
}
for ($i=0 ; $i < count($rkey) ; $i++) {
$fcol = $fkey[$i];
$rcol = $rkey[$i];
if (!isset($ftable_obj->col[$fcol])) {
return $this->throwError(
DB_TABLE_DATABASE_ERR_NO_FCOL,
"$ftable.$fcol => $rtable.$rcol");
}
if (!isset($rtable_obj->col[$rcol])) {
return $this->throwError(
DB_TABLE_DATABASE_ERR_NO_RCOL,
"$ftable.$fcol => $rtable.$rcol");
}
$ftype = $ftable_obj->col[$fcol]['type'];
$rtype = $rtable_obj->col[$rcol]['type'];
if (!($rtype == $ftype)) {
return $this->throwError(
DB_TABLE_DATABASE_ERR_REF_TYPE,
"$ftable.$fcol => $rtable.$rcol");
}
}
} else {
return $this->throwError(
DB_TABLE_DATABASE_ERR_FKEY,
"$ftable => $rtable");
}
// Check validity of on_delete and on_update actions
$valid_actions =
array(null, 'cascade', 'set null', 'set default', 'restrict');
if (!in_array($on_delete, $valid_actions)) {
return $this->throwError(
DB_TABLE_DATABASE_ERR_ON_DELETE_ACTION,
"$ftable => $rtable");
}
if (!in_array($on_update, $valid_actions)) {
return $this->throwError(
DB_TABLE_DATABASE_ERR_ON_UPDATE_ACTION,
"$ftable => $rtable");
}
// Add reference to $this->_ref;
$ref = array(
'fkey' => $fkey,
'rkey' => $rkey,
'on_delete' => $on_delete,
'on_update' => $on_update);
if (!isset($this->_ref[$ftable])) {
$this->_ref[$ftable] = array();
} else {
if (isset($this->_ref[$ftable][$rtable])) {
// Multiple references from $ftable to $rtable
return $this->throwError(
DB_TABLE_DATABASE_ERR_MULT_REF,
"$ftable => $rtable");
}
}
$this->_ref[$ftable][$rtable] = $ref;
// Add referencing table $ftable to $ref_to property
if (!isset($this->_ref_to[$rtable])) {
$this->_ref_to[$rtable] = array();
}
$this->_ref_to[$rtable][] = $ftable;
// Add foreign key columns to $this->_foreign_col
if (is_string($fkey)) {
if (!isset($this->_foreign_col[$fkey])) {
$this->_foreign_col[$fkey] = array();
}
$this->_foreign_col[$fkey][] = $ftable;
} elseif (is_array($fkey)) {
foreach ($fkey as $fcol) {
if (!isset($this->_foreign_col[$fcol])) {
$this->_foreign_col[$fcol] = array();
}
$this->_foreign_col[$fcol][] = $ftable;
}
}
// Normal completion
return true;
}
// }}}
// {{{ function deleteRef($ftable, $rtable)
/**
* Deletes one reference from database model
*
* Removes reference from referencing (foreign key) table named
* $ftable to referenced table named $rtable. Unsets relevant elements
* of the $ref, $_ref_to, and $_link property arrays, and removes the
* foreign key columns of $ftable from the $_foreign_col property.
*
* Does nothing, silently, if no such reference exists, i.e., if
* $this->_ref[$ftable][$rtable] is not set.
*
* @param $ftable name of referencing (foreign key) table
* @param $rtable name of referenced table
* @return void
* @access public
*/
function deleteRef($ftable, $rtable)
{
// Delete from $_ref property
if (isset($this->_ref[$ftable])) {
if (isset($this->_ref[$ftable][$rtable])) {
$fkey = $this->_ref[$ftable][$rtable]['fkey'];
unset($this->_ref[$ftable][$rtable]);
} else {
// No such reference, abort silently
return;
}
}
// Remove foreign key columns from $foreign_col property
if (isset($fkey)) {
if (is_string($fkey)) {
$fkey = array($fkey);
}
foreach ($fkey as $column) {
if (isset($this->_foreign_col[$column])) {
$key = array_search($ftable,
$this->_foreign_col[$column]);
if (is_integer($key)) {
unset($this->_foreign_col[$column][$key]);
if (count($this->_foreign_col[$column]) == 0) {
unset($this->_foreign_col[$column]);
} else {
$new = array_values($this->_foreign_col[$column]);
$this->_foreign_col[$column] = $new;
}
}
}
}
}
// Delete from $_ref_to property
if (isset($this->_ref_to[$rtable])) {
$key = array_search($ftable, $this->_ref_to[$rtable]);
// Unset element
unset($this->_ref_to[$rtable][$key]);
if (count($this->_ref_to[$rtable]) == 0) {
unset($this->_ref_to[$rtable]);
} else {
// Redefine numerical keys of remaining elements
$ref_to = array_values($this->_ref_to[$rtable]);
$this->_ref_to[$rtable] = $ref_to;
}
}
// Delete all relevant links from $_link property
if (isset($this->_link[$rtable])) {
foreach ($this->_link[$rtable] as $table2 => $links) {
if (in_array($ftable, $links)) {
$this->deleteLink($rtable, $table2, $ftable);
}
}
}
}
// }}}
// {{{ function setOnDelete($ftable, $rtable, $action)
/**
* Modifies the on delete action for one foreign key reference.
*
* Modifies the value of the on_delete action associated with a reference
* from $ftable to $rtable. The parameter action may be one of the action
* strings 'cascade', 'restrict', 'set null', or 'set default', or it may
* be php null. A null value of $action indicates that no action should be
* taken upon deletion of a referenced row.
*
* Returns true on success, and PEAR error on failure. Returns the error
* code DB_TABLE_DATABASE_ERR_REF_TRIG_ACTION if $action is a neither a
* valid action string nor null. Returns true, and does nothing, if
* $this->_ref[$ftable][$rtable] is not set.
*
* @param string $ftable name of referencing (foreign key) table
* @param string $rtable name of referenced table
* @param string $action on delete action (action string or null)
* @return boolean true on normal completion (PEAR_Error on failure)
* @access public
*/
function setOnDelete($ftable, $rtable, $action)
{
$valid_actions =
array(null, 'cascade', 'set null', 'set default', 'restrict');
if (isset($this->_ref[$ftable])) {
if (isset($this->_ref[$ftable][$rtable])) {
if (!in_array($action, $valid_actions)) {
return $this->throwError(
DB_TABLE_DATABASE_ERR_REF_ON_DELETE_ACTION,
"$ftable => $rtable");
}
$this->_ref[$ftable][$rtable]['on_delete'] = $action;
}
}
return true;
}
// }}}
// {{{ function setOnUpdate($ftable, $rtable, $action)
/**
* Modifies on update action for one foreign key reference.
*
* Similar to setOnDelete. See setOnDelete for further details.
*
* @param string $ftable name of referencing (foreign key) table
* @param string $rtable name of referenced table
* @param array $action on update action (action string or null)
* @return boolean true on normal completion (PEAR_Error on failure)
* @access public
*/
function setOnUpdate($ftable, $rtable, $action)
{
$valid_actions =
array(null, 'cascade', 'set null', 'set default', 'restrict');
if (isset($this->_ref[$ftable])) {
if (isset($this->_ref[$ftable][$rtable])) {
if (!in_array($action, $valid_actions)) {
return $this->throwError(
DB_TABLE_DATABASE_ERR_REF_ON_UPDATE_ACTION,
"$ftable => $rtable");
}
$this->_ref[$ftable][$rtable]['on_update'] = $action;
}
}
return true;
}
// }}}
// {{{ function addLink($table1, $table2, $link)
/**
* Identifies a linking/association table that links two others
*
* Adds table name $link to $this->_link[$table1][$table2] and
* to $this->_link[$table2][$table1].
*
* Returns true on success, and PEAR error on failure. Returns the
* following DB_TABLE_DATABASE_ERR_* error codes if:
* - $ftable does not exist ( _NO_FTABLE )
* - $rtable does not exist ( _NO_RTABLE )
*
* @param string $table1 name of 1st linked table
* @param string $table2 name of 2nd linked table
* @param string $link name of linking/association table.
* @return boolean true on success (PEAR_Error on failure)
* @access public
*/
function addLink($table1, $table2, $link)
{
// Check for existence of all three tables
if (is_string($table1)) {
if (!isset($this->_table[$table1])) {
return $this->throwError(
DB_TABLE_DATABASE_ERR_NO_TBL,
"addLink, $table1");
}
} else {
return $this->throwError(
DB_TABLE_DATABASE_ERR_NO_TBL,
"addLink, $table1");
}
if (!isset($this->_table[$table2])) {
return $this->throwError(
DB_TABLE_DATABASE_ERR_NO_TBL,
"addLink, $table2");
}
if (!isset($this->_table[$link])) {
return $this->throwError(
DB_TABLE_DATABASE_ERR_NO_TBL,
"addLink, $link");
}
if (!isset($this->_ref[$link])) {
return $this->throwError(
DB_TABLE_DATABASE_ERR_NO_REF_LINK,
"$link => $table1, $table2");
} else {
if (!isset($this->_ref[$link][$table1])) {
return $this->throwError(
DB_TABLE_DATABASE_ERR_NO_REF_LINK,
"$link => $table1");
}
if (!isset($this->_ref[$link][$table2])) {
return $this->throwError(
DB_TABLE_DATABASE_ERR_NO_REF_LINK,
"$link => $table2");
}
}
// Add $this_link[$table1][$table2]
if (!key_exists($table1, $this->_link)) {
$this->_link[$table1] = array();
}
if (!key_exists($table2, $this->_link[$table1])) {
$this->_link[$table1][$table2] = array();
}
$this->_link[$table1][$table2][] = $link;
// Add $this_link[$table2][$table1]
if (!key_exists($table2, $this->_link)) {
$this->_link[$table2] = array();
}
if (!key_exists($table1, $this->_link[$table2])) {
$this->_link[$table2][$table1] = array();
}
$this->_link[$table2][$table1][] = $link;
}
// }}}
// {{{ function addAllLink()
/**
* Adds all possible linking tables to the $_link property array
*
* Identifies all potential linking tables in the datbase, and adds
* them all to the $_link property. Table $link is taken to be a
* link between tables $table1 and $table2 if it contains foreign
* key references to both $table1 and $table2.
*
* @return void
* @access public
*/
function addAllLinks()
{
foreach ($this->_table as $link => $link_obj) {
if (isset($this->_ref[$link])) {
$ref = $this->_ref[$link];
$n = count($ref);
$names = array_keys($ref);
if ($n > 1) {
$is_link = true;
} else {
$is_link = false;
}
if ($is_link) {
if ($n == 2) {
$table1 = $names[0];
$table2 = $names[1];
$this->addLink($table1, $table2, $link);
} elseif ($n > 2) {
for ($i=1 ; $i < $n; $i++) {
for ($j=0 ; $j < $i; $j++) {
$table1 = $names[$j];
$table2 = $names[$i];
$this->addLink($table1, $table2, $link);
}
}
}
}
}
}
}
// }}}
// {{{ function deleteLink($table1, $table2, $link = null)
/**
* Removes a link between two tables from the $_link property
*
* If $link is not null, remove table $link from the list of links
* between $table1 and $table2, if present. If $link is null, delete
* all links between $table1 and $table2.
*
* @param string $table1 name of 1st linked table
* @param string $table2 name of 2nd linked table
* @param string $link name of linking table
* @return void
* @access public
*/
function deleteLink($table1, $table2, $link = null)
{
if (isset($this->_link[$table1])) {
if (isset($this->_link[$table1][$table2])) {
if ($link) {
// Find numerical key of $link in _link[$table1][$table2]
$key = array_search($link, $this->_link[$table1][$table2]);
if (is_integer($key)) {
unset($this->_link[$table1][$table2][$key]);
if (count($this->_link[$table1][$table2]) == 0) {
unset($this->_link[$table1][$table2]);
unset($this->_link[$table2][$table1]);
if (count($this->_link[$table1]) == 0) {
unset($this->_link[$table1]);
}
if (count($this->_link[$table2]) == 0) {
unset($this->_link[$table2]);
}
} else {
// Reset remaining indices sequentially from zero
$new = array_values($this->_link[$table1][$table2]);
$this->_link[$table1][$table2] = $new;
$this->_link[$table2][$table1] = $new;
}
}
} else {
unset($this->_link[$table1][$table2]);
unset($this->_link[$table2][$table1]);
if (count($this->_link[$table1]) == 0) {
unset($this->_link[$table1]);
}
if (count($this->_link[$table2]) == 0) {
unset($this->_link[$table2]);
}
}
}
}
}
// }}}
// {{{ function validCol($col, $from = null)
/**
* Validates and (if necessary) disambiguates a column name.
*
* The parameter $col is a string may be either a column name or
* a column name qualified by a table name, using the SQL syntax
* "$table.$column". If $col contains a table name, and is valid,
* an array($table, $column) is returned. If $col is not qualified
* by a column name, an array array($table, $column) is returned,
* in which $table is either the name of one table, or an array
* containing the names of two or more tables containing a column
* named $col.
*
* The $from parameter, if present, is a numerical array of
* names of tables with which $col should be associated, if no
* explicit table name is provided, and if possible. If one
* or more of the tables in $from contains a column $col, the
* returned table or set of tables is restricted to those in
* array $from.
*
* If the table name remains ambiguous after testing for tables in
* the $from set, and $col is not a foreign key in one or more of
* the remaining tables, the returned table or set of tables is
* restricted to those in which $col is not a foreign key.
*
* Returns a PEAR_Error with the following DB_TABLE_DATABASE_ERR_* error
* codes if:
* - column $col does not exist in the database ( _NO_COL_DB )
* - column $col does not exist in the specified table ( _NO_COL_TBL )
*
* @param string $col column name, optionally qualified by a table name
* @param array $from array of tables from which $col should be chosen,
* if possible.
* @return array array($table, $column), or PEAR_Error on failure
* $column is a string, $table is a string or array
* @access public
*/
function validCol($col, $from = null)
{
$col = explode('.',trim($col));
if (count($col) == 1) {
// Parameter $col is a column name with no table name
$column = $col[0];
// Does $column exist in database ?
if (!isset($this->_col[$column])) {
return $this->throwError(
DB_TABLE_DATABASE_ERR_NO_COL_DB,
"$column");
}
$table = $this->_col[$column];
// If $table is not unique, try restricting to arrays in $from
if (count($table) > 1 && $from) {
$ptable = array_intersect($table, $from);
if (count($ptable) > 0) {
$table = array_values($ptable);
}
}
// If count($table)>1, try excluding foreign key columns
if (count($table) > 1 && isset($this->_foreign_col[$column])) {
$ptable = array_diff($table, $this->_foreign_col[$column]);
if (count($ptable) > 0) {
$table = array_values($ptable);
}
}
// If only one table remains, set $table = table name string
if (count($table) == 1) {
$table = $table[0];
}
} elseif (count($col) == 2) {
// parameter $col is qualified by a table name
$table = $col[0];
$column = $col[1];
if (isset($this->_table[$table])) {
$table_obj =& $this->_table[$table];
$col_array = $table_obj->col;
if (!isset($col_array[$column])) {
return $this->throwError(
DB_TABLE_DATABASE_ERR_NO_COL_TBL,
"$table.$column");
}
} else {
return $this->throwError(
DB_TABLE_DATABASE_ERR_NO_TBL, "validCol, $table");
}
}
return array($table, $column);
}
// }}}
// {{{ function createTables($flag = 'safe')
/**
* Creates all the tables in a database in a RDBMS
*
* Note: this method creates all the tables in a database, but does
* NOT create the parent database or set it to the current or default
* database -- the database must exist before the method is called.
*
* If creation of any table fails, the method immediately returns the
* PEAR error returned by DB_Table::create($flag).
*
* @param mixed $flag The automatic database creation mode, which is
* applied to each table in the database. It can have
* values:
* - 'safe' to create a table only if it does not exist
* - 'drop' to drop and recreate any existing table
with the same name
*
* @return boolean true on sucess (PEAR_Error on failure of any table)
* @access public
*
* @see DB_Table::create()
*/
function createTables($flag = 'safe')
{
foreach ($this->_table as $name => $table) {
$result = $table->create($flag);
if (PEAR::isError($result)) {
return $result;
}
}
return true;
}
// }}}
// {{{ function validForeignKeys($table_name, $data)
/**
* Check validity of any foreign key values in associative array $data
* containing values to be inserted or updated in table $table_name.
*
* Returns true if each foreign key in $data matches a row in the
* referenced table, or if there are no foreign key columns in $data.
* Returns a PEAR_Error if any foreign key column in associative array
* $data (which may contain a full or partial row of $table_name), does
* not match the the value of the referenced column in any row of the
* referenced table.
*
* @param $table_name name of the referencing table containing $data
* @param @data associative array containing all or part of a row
* of data of $table_name, with column name keys.
* @return bool true if all foreign keys are valid, returns PEAR_Error
* if foreign keys are invalid or if an error is thrown
* by a required query
*
* @throws PEAR error if:
* - Error thrown by _buildFKeyFilter method (bubbles up)
* - Error thrown by select method for required query (bubbles up)
*
* @access public
*/
function validForeignKeys($table_name, $data)
{
if (isset($this->_ref[$table_name])) {
foreach ($this->_ref[$table_name] as $rtable_name => $ref) {
$fkey = $ref['fkey'];
$rkey = $ref['rkey'];
$rtable_obj =& $this->_table[$rtable_name];
// Construct select where clause for referenced rows,
// $filter = '' if $data contains no foreign key columns,
$filter = $this->_buildFKeyFilter($data, $fkey, $rkey);
if (PEAR::isError($filter)) {
return $filter;
}
// If inserted data contain FK columns referenced by rtable,
// select referenced row of rtable, return error if none is
// found
if ($filter) {
$sql = array('select'=> '*',
'from' => $rtable_name,
'where' => $filter);
$referenced_rows = $this->select($sql);
// Check for failed query
if (PEAR::isError($referenced_rows)) {
return $referenced_rows;
}
// Check for failed foreign key constraint
if (count($referenced_rows) == 0) {
return $this->throwError(
DB_TABLE_DATABASE_ERR_FKEY_CONSTRAINT);
}
}
}
}
return true;
}
// }}}
// {{{ function insert($table_name, $data)
/**
* Inserts a single table row
*
* Wrapper for insert method of the corresponding DB_Table object.
*
* Data will be validated before insertion using validForeignKey(),
* if foreign key validation in enabled.
*
* @param string $table_name Name of table into which to insert data
* @param array $data Associative array, in which each key is a column
* name and each value is that column's value.
* This is the data that will be inserted into
* the table. Data is checked against the column
* names and data types for validity.
* @return boolean true on success (PEAR_Error on failure)
* @access public
*/
function insert($table_name, $data)
{
// Dereference table object
if (isset($this->_table[$table_name])) {
$table_obj =& $this->_table[$table_name];
} else {
return $this->throwError(
DB_TABLE_DATABASE_ERR_NO_TBL,
"insert, $table_name");
}
// Insert into $table_obj
$result = $table_obj->insert($data);
// Return value: true or PEAR_Error
if (PEAR::isError($result)) {
return $result;
} else {
return true;
}
}
// }}}
// {{{ function autoValidInsert($flag = true)
/**
* Turns on or off automatic validation of inserted data for all tables
*
* @param bool $flag true to turn on auto-validation, false to turn off.
* @return void
* @access public
*/
function autoValidInsert($flag = true)
{
foreach ($this->_table as $table_obj) {
$table_obj->autoValidInsert($flag);
}
}
// }}}
// {{{ function update($table_name, $data, $where)
/**
* Updates all row(s) of table that match a custom where clause.
*
* Wrapper for insert method of the corresponding DB_Table object.
*
* Data will be validated before insertion using validForeignKey(),
* if foreign key validation in enabled.
*
* Implements any required ON UPDATE actions on tables that
* reference updated columns, if on update actions are enabled.
*
* @param string $table_name name of table to update
* @param array $data associative array in which keys are names of
* columns to be updated values are new values.
* @param string $where SQL WHERE clause that limits the set of
* records to update.
* @return boolean true on success (PEAR_Error on failure)
* @access public
*/
function update($table_name, $data, $where)
{
// Dereference table object
if (isset($this->_table[$table_name])) {
$table_obj =& $this->_table[$table_name];
} else {
return $this->throwError(
DB_TABLE_DATABASE_ERR_NO_TBL,
"update, $table_name");
}
// Apply update
$result = $table_obj->update($data, $where);
// Return value: true or PEAR_Error
if (PEAR::isError($result)) {
return $result;
} else {
return true;
}
}
// }}}
// {{{ function autoValidUpdate($flag = true)
/**
* Turns on (or off) automatic validation of updated data for all tables.
*
* @param bool $flag true to turn on auto-validation, false to turn off
* @return void
* @access public
*/
function autoValidUpdate($flag = true)
{
foreach ($this->_table as $table_obj) {
$table_obj->autoValidUpdate($flag);
}
}
// }}}
// {{{ function onUpdateAction(&$table_obj, $data, $where)
/**
* Implements any ON UPDATE actions triggered by updating of rows of
* $table_obj that match logical condition $where.
*
* This method is called by the DB_Table::update() method if the table
* has a parent DB_Table_Database object, and if ON UPDATE actions are
* enabled in the database object. It is called indirectly by the
* DB_Table_Database::delete() method, which is simply a wrapper for
* the DB_Table method.
*
* @param object &$table_obj Reference to a DB_Table object
* @param array $data Data to updated, column name keys, data values
* @param string $where SQL logical condition for updated rows
* @return boolean true on success (PEAR_Error on failure)
* @access public
*/
function onUpdateAction(&$table_obj, $data, $where)
{
$table_name = $table_obj->table;
if ($this->_act_on_update and isset($this->_ref_to[$table_name])) {
$update_rows = null;
foreach ($this->_ref_to[$table_name] as $ftable_name) {
$ref = $this->_ref[$ftable_name][$table_name];
$action = isset($ref['on_update']) ? $ref['on_update'] : null;
if (is_null($action)) {
continue;
}
$rtable_obj =& $this->_table[$table_name];
$ftable_obj =& $this->_table[$ftable_name];
$fkey = $ref['fkey'];
$rkey = $ref['rkey'];
// Check if any column(s) of referenced $rkey are updated
$rkey_updated = false;
foreach ($data as $key => $value) {
if (is_string($rkey)){
if ($key == $rkey) {
$rkey_updated = true;
break;
}
} else {
if (in_array($key, $rkey)) {
$rkey_updated = true;
break;
}
}
}
// If $rkey is not updated, continue to next referencing table
if (!$rkey_updated) {
continue;
}
// Select rows to be updated, if not done previously
if ($update_rows === null) {
if ($this->backend == 'mdb2') {
$fetchmode_assoc = MDB2_FETCHMODE_ASSOC;
} else {
$fetchmode_assoc = DB_FETCHMODE_ASSOC;
}
$sql = array('select' => '*',
'from' => $table_name,
'where' => $where,
'fetchmode' => $fetchmode_assoc);
$update_rows = $this->select($sql);
if (PEAR::isError($update_rows)) {
return $update_rows;
}
}
// Construct $fdata array if cascade, set null, or set default
$fdata = null;
if ($action == 'cascade') {
if (is_string($rkey)) {
if (array_key_exists($rkey, $data)) {
$fdata = array($fkey => $data[$rkey]);
}
} else {
$fdata = array();
for ($i=0; $i < count($rkey); $i++) {
$rcol = $rkey[$i];
$fcol = $fkey[$i];
if (array_key_exists($rcol, $data)) {
$fdata[$fcol] = $data[$rcol];
}
}
if (count($fdata) == 0) {
$fdata = null;
}
}
} elseif ($action == 'set null' or $action == 'set default') {
if (is_string($fkey)) {
if ($action == 'set default') {
$value = isset($ftable_obj->col[$fkey]['default'])
? $ftable_obj->col[$fkey]['default'] : null;
} else {
$value = null;
}
$fdata = array($fkey => $value);
} else {
$fdata = array();
foreach ($fkey as $fcol) {
if ($action == 'set default') {
$value = isset($ftable_obj->col[$fcol]['default'])
? $ftable_obj->col[$fcol]['default'] : null;
} else {
$value = null;
}
$fdata[$fcol] = $value;
}
if (count($fdata) == 0) {
$fdata = null;
}
}
} elseif ($action == 'restrict') {
$fdata = true;
} elseif ($action == null) {
$fdata = null;
} else {
return $this->throwError(
DB_TABLE_DATABASE_ERR_ON_UPDATE_ACTION,
"$ftable_name => $table_name");
}
if (!is_null($fdata)) {
// Loop over rows to be updated from $table
foreach ($update_rows as $update_row) {
// If necessary, restore case of column names
if ($this->_fix_case) {
$cols = array_keys($table_obj->col);
$update_row = $this->_replaceKeys($update_row, $cols);
}
// Construct filter for rows that reference $update_row
$filter = $this->_buildFKeyFilter($update_row,
$rkey, $fkey);
// Apply action to foreign/referencing rows
if ($action == 'restrict') {
$sql = array('select'=>'*',
'from' => $ftable_name,
'where' => $filter);
$frows = $this->select($sql);
if (PEAR::isError($frows)) {
return $frows;
}
if (count($frows) > 0) {
return $this->throwError(
DB_TABLE_DATABASE_ERR_RESTRICT_UPDATE,
$table_name);
}
} else {
// If 'cascade', 'set null', or 'set default',
// then update the referencing foreign key.
// Note: Turn off foreign key validity check
// during update, then restore original value
$check_fkey = $this->_check_fkey;
$this->_check_fkey = false;
$result = $this->update($ftable_name, $fdata,
$filter);
$this->_check_fkey = $check_fkey;
if (PEAR::isError($result)) {
return $result;
}
}
} // foreach ($update_row)
} // if (!is_null($fdata))
} // foreach loop over referencing tables
} // end if
// Normal completion
return true;
}
// }}}
// {{{ function autoRecast($flag = true)
/**
* Turns on (or off) automatic recasting of insert and update data
* for all tables
*
* @param bool $flag True to automatically recast insert and update
* data, in all tables, false to not do so.
* @return void
* @access public
*/
function autoRecast($flag = true)
{
foreach ($this->_table as $table_obj) {
$table_obj->autoRecast($flag);
}
}
// }}}
// {{{ function autoInc($flag = true)
/**
* Turns on (or off) php implementation of auto-incrementing on insertion
* for all tables
*
* @param bool $flag True to turn on auto-incrementing, false to turn off
* @return void
* @access public
*/
function autoInc($flag = true)
{
foreach ($this->_table as $table_obj) {
$table_obj->auto_inc = $flag;
}
}
// }}}
// {{{ function delete($table_name, $where)
/**
* Deletes all row(s) of table that match a custom where clause.
*
* Wrapper for insert method of the corresponding DB_Table object.
*
* Implements any required ON DELETE action on tables that reference
* deleted rows, if on delete actions are enabled.
*
* @param string $table_name name of table from which to delete
* @param string $where SQL WHERE clause that limits the set
* of records to delete
* @return boolean true on success (PEAR_Error on failure)
* @access public
*/
function delete($table_name, $where)
{
// Dereference table object
if (isset($this->_table[$table_name])) {
$table_obj =& $this->_table[$table_name];
} else {
return $this->throwError(
DB_TABLE_DATABASE_ERR_NO_TBL,
"delete, $table_name");
}
// Delete from $table_obj
$result = $table_obj->delete($where);
// Return value: true or PEAR_Error
if (PEAR::isError($result)) {
return $result;
} else {
return true;
}
}
// }}}
// {{{ function onDeleteAction(&$table_obj, $where)
/**
* Implements ON DELETE actions triggered by deletion of rows of
* $table_obj that match logical condition $where.
*
* This method is called by the DB_Table::delete() method if the table
* has a parent DB_Table_Database object, and if ON DELETE actions are
* enabled in the database object. It is called indirectly by the
* DB_Table_Database::delete() method, which is simply a wrapper for
* the DB_Table method.
*
* @param object &$table_obj Reference to a DB_Table object
* @param string $where SQL logical condition for deleted rows
* @return boolean true on success (PEAR_Error on failure)
* @access public
*/
function onDeleteAction(&$table_obj, $where)
{
$table_name = $table_obj->table;
if ($this->_act_on_delete and isset($this->_ref_to[$table_name])) {
$delete_rows = null;
foreach ($this->_ref_to[$table_name] as $ftable_name) {
$ref = $this->_ref[$ftable_name][$table_name];
$action = $ref['on_delete'];
if (is_null($action)) {
continue;
}
$ftable_obj =& $this->_table[$ftable_name];
$rtable_obj =& $this->_table[$table_name];
$fkey = $ref['fkey'];
$rkey = $ref['rkey'];
// Select rows to be deleted, if not done previously
if ($delete_rows === null) {
if ($this->backend == 'mdb2') {
$fetchmode_assoc = MDB2_FETCHMODE_ASSOC;
} else {
$fetchmode_assoc = DB_FETCHMODE_ASSOC;
}
$sql = array('select' => '*',
'from' => $table_name,
'where' => $where,
'fetchmode' => $fetchmode_assoc);
$delete_rows = $this->select($sql);
if (PEAR::isError($delete_rows)) {
return $delete_rows;
}
}
// If set null or set default, construct update $fdata
// $fdata contains data for updating referencing rows
if ($action == 'set null' or $action == 'set default') {
if (is_string($fkey)) {
if ($action == 'set default') {
$value = isset($ftable_obj->col[$fkey]['default'])
? $ftable_obj->col[$fkey]['default'] : null;
} else {
$value = null;
}
$fdata = array($fkey => $value);
} else {
$fdata = array();
foreach ($fkey as $fcol) {
if ($action == 'set default') {
$value = isset($ftable_obj->col[$fcol]['default'])
? $ftable_obj->col[$fcol]['default'] : null;
} else {
$value = null;
}
$fdata[$fcol] = $value;
}
}
}
// Loop over rows to be deleted from $table_name
foreach ($delete_rows as $delete_row) {
// If necessary, restore case of $delete_row column names
if ($this->_fix_case) {
$cols = array_keys($table_obj->col);
$delete_row = $this->_replaceKeys($delete_row, $cols);
}
// Construct filter for referencing rows in $ftable_name
$filter = $this->_buildFKeyFilter($delete_row,
$rkey, $fkey);
// Apply action for one deleted row
if ($action == 'restrict') {
// Select for referencing rows throw error if found
$sql = array('select'=>'*',
'from' => $ftable_name,
'where' => $filter);
$frows = $this->select($sql);
if (PEAR::isError($frows)) {
return $frows;
}
if (count($frows) > 0) {
return $this->throwError(
DB_TABLE_DATABASE_ERR_RESTRICT_DELETE,
$table_name);
}
} elseif ($action == 'cascade') {
// Delete referencing rows
// Note: Recursion on delete
$result = $this->delete($ftable_name, $filter);
if (PEAR::isError($result)) {
return $result;
}
} elseif ($action == 'set null' OR $action == 'set default') {
// Update referencing rows, using $fdata
// Note: Turn off foreign key validity check during
// update of referencing key to null or default, then
// restore $this->_check_fkey to original value
$check_fkey = $this->_check_fkey;
$this->_check_fkey = false;
$result = $this->update($ftable_name, $fdata, $filter);
$this->_check_fkey = $check_fkey;
#$result = $ftable_obj->update($fdata, $filter);
if (PEAR::isError($result)) {
return $result;
}
} else {
// Invalid $action name, throw Error
return $this->throwError(
DB_TABLE_DATABASE_ERR_ON_DELETE_ACTION,
"$ftable_name => $table_name");
}
} // end foreach ($delete_rows)
} // end foreach ($this->_ref_to[...] as $ftable_name)
} // end if
// Normal completion
return true;
}
// }}}
// {{{ function _replaceKeys($data, $keys)
/**
* Returns array in which keys of associative array $data are replaced
* by values of sequential array $keys.
*
* This function is used by the onDeleteAction() and onUpdateAction()
* methods to restore the case of column names in associative arrays
* that are returned from an automatically generated query "SELECT *
* FROM $table WHERE ...", when these column name keys are returned
* with a fixed case. In this usage, $keys is a sequential array of
* the names of all columns in $table.
*
* @param array $data associative array
* @param array $key numerical array of replacement key names
* @return array associative array in which keys of $data have been
* replaced by the values of array $keys.
* @access private
*/
function _replaceKeys($data, $keys)
{
$new_data = array();
$i = 0;
foreach ($data as $old_key => $value) {
$new_key = $keys[$i];
$new_data[$new_key] = $value;
$i = $i + 1;
}
return $new_data;
}
// }}}
// {{{ function autoJoin($cols = null, $tables = null, $filter = null)
/**
* Builds a select command involving joined tables from
* a list of column names and/or a list of table names.
*
* Returns an query array of the form used in $this->buildSQL,
* constructed on the basis of a sequential array $cols of
* column names and/or a sequential array $tables of table
* names. The 'FROM' clause in the resulting SQL contains
* all the table listed in the $tables parameter and all
* those containing the columns listed in the $cols array,
* as well as any linking tables required to establish
* many to many relationships between these tables. The
* 'WHERE' clause is constructed so as to create an inner
* join of these tables.
*
* The $cols parameter is a sequential array in which the
* values are column names. Column names may be qualified
* by a table name, using the SQL table.column syntax, but
* need not be qualified if they are unambiguous. The
* values in $cols can only be column names, and may not
* be functions or more complicated SQL expressions. If
* cols is null, the resulting SQL command will start with
* 'SELECT * FROM ...' .
*
* The $tables parameter is a sequential array in which the
* values are table names. If $tables is null, the FROM
* clause is constructed from the tables containing the
* columns in the $cols.
*
* The $params array is an associative array can have
* 'filter', and 'order' keys, which are both optional.
* A value $params['filter'] is an condition string to
* add (i.e., AND) to the automatically constructed set
* of join conditions. A value $params['order'] is an
* SQL 'ORDER BY' clause, with no 'ORDER BY' prefix.
*
* The function returns an associative array with keys
* ('select', 'from', 'where', ['order']), for which the
* associated values are strings containing the SELECT,
* FROM, WHERE and (optionally) ORDER BY clauses of the
* select statement. The entire SELECT command string
* can be obtained by passing the resulting array to
* the buildSQL method.
*
* @param array $cols sequential array of column names
* @param array $tables sequential array of table names
* @param array $filter SQL logical expression to be added
* (ANDed) to the where clause
* @return array sql query array for select statement
* @access public
*/
function autoJoin($cols = null, $tables = null, $filter = null)
{
// initialize array containing clauses of select statement
$query = array();
if (is_null($tables)) {
if (is_null($cols)) {
return $this->throwError(
DB_TABLE_DATABASE_ERR_NO_COL_NO_TBL);
}
$tables = array();
}
if (!$cols) {
// If no columns specified, SELECT * FROM ...
$query['select'] = '*';
} else {
// Qualify unqualified columns with table names
$all_tables = $tables;
foreach ($cols as $key => $col) {
$col_array = $this->validCol($col, $tables);
if (PEAR::isError($col_array)) {
return $col_array;
}
$table = $col_array[0];
$column = $col_array[1];
if (is_array($table)) {
return $this->throwError(
DB_TABLE_DATABASE_ERR_COL_NOT_UNIQUE, $col);
}
$cols[$key] = "$table.$column";
if (!in_array($table, $all_tables)) {
$all_tables[] = $table;
}
}
$tables = $all_tables;
// Construct select clause
$query['select'] = implode(', ', $cols);
}
// Construct array $joins of join conditions
$n_tables = count($tables);
if ($n_tables == 1) {
$query['from'] = $tables[0];
} else {
$join_tables = array($tables[0]); // list of joined tables
$link_tables = array(); // list of required linking tables
$joins = array(); // list of join conditions
// Initialize linked list of unjoined tables
$next = array();
for ( $i=1 ; $i < $n_tables-1 ; $i++) {
$next[$tables[$i]] = $tables[$i+1];
$prev[$tables[$i+1]] = $tables[$i];
}
$next[$tables[$n_tables-1]] = $tables[1];
$prev[$tables[1]] = $tables[$n_tables-1];
$n_remain = $n_tables - 1;
$head = $tables[1];
$table1 = $tables[1];
$joined = false;
$direct = true;
while ($n_remain > 0) {
if ($direct) {
// Search for references from table1 to joined tables
if (isset($this->_ref[$table1])) {
$list = $this->_ref[$table1];
foreach ($list as $table2 => $def) {
if (in_array($table2, $join_tables)) {
if ($joined) {
return $this->throwError(
DB_TABLE_DATABASE_ERR_AMBIG_JOIN,
$table1);
}
$fkey = $def['fkey'];
$rkey = $def['rkey'];
if (is_string($fkey)) {
$joins[] = "$table1.$fkey = $table2.$rkey";
} else {
for ($i=0; $i < count($fkey); $i++ ) {
$fcol = $fkey[$i];
$rcol = $rkey[$i];
$joins[] =
"$table1.$fcol = $table2.$rcol";
}
}
$joined = true;
}
}
}
// Search for references to table1 from joined tables
if (isset($this->_ref_to[$table1])) {
$list = $this->_ref_to[$table1];
foreach ($list as $table2) {
if (in_array($table2, $join_tables)) {
if ($joined) {
return $this->throwError(
DB_TABLE_DATABASE_ERR_AMBIG_JOIN,
$table1);
}
$def = $this->_ref[$table2][$table1];
$fkey = $def['fkey'];
$rkey = $def['rkey'];
if (is_string($fkey)) {
$joins[] = "$table2.$fkey = $table1.$rkey";
} else {
for ($i=0; $i < count($fkey); $i++ ) {
$fcol = $fkey[$i];
$rcol = $rkey[$i];
$joins[] =
"$table2.$fcol = $table1.$rcol";
}
}
$joined = true;
}
}
}
} else {
// Search for indirect linking table to table1
if (isset($this->_link[$table1])) {
foreach ($this->_link[$table1] as $table2 => $links) {
if (in_array($table2, $join_tables)) {
$n_link = count($links);
if ($n_link > 1) {
return $this->throwError(
DB_TABLE_DATABASE_ERR_AMBIG_JOIN,
$table1);
}
if ($joined and $n_link > 0) {
return $this->throwError(
DB_TABLE_DATABASE_ERR_AMBIG_JOIN,
$table1);
}
$link = $links[0];
$def1 = $this->_ref[$link][$table1];
$fkey1 = $def1['fkey'];
$rkey1 = $def1['rkey'];
if (is_string($fkey1)) {
$joins[] = "$link.$fkey1 = $table1.$rkey1";
} else {
for ($i=0; $i < count($fkey1); $i++ ) {
$fcol1 = $fkey1[$i];
$rcol1 = $rkey1[$i];
$joins[] =
"$link.$fcol1 = $table1.$rcol1";
}
}
$def2 = $this->_ref[$link][$table2];
$fkey2 = $def2['fkey'];
$rkey2 = $def2['rkey'];
if (is_string($fkey2)) {
$joins[] = "$link.$fkey2 = $table2.$rkey2";
} else {
for ($i=0; $i < count($fkey2); $i++ ) {
$fcol2 = $fkey2[$i];
$rcol2 = $rkey2[$i];
$joins[] =
"$link.$fcol2 = $table2.$rcol2";
}
}
$link_tables[] = $link;
$joined = true;
}
}
}
}
if ($joined) {
$join_tables[] = $table1;
$n_remain = $n_remain - 1;
if ($n_remain > 0) {
$head = $next[$table1];
$tail = $prev[$table1];
$prev[$head] = $tail;
$next[$tail] = $head;
$table1 = $head;
$joined = false;
$direct = true;
}
} else {
$table1 = $next[$table1];
if ($table1 == $head) {
if ($direct) {
$direct = false;
} else {
return $this->throwError(
DB_TABLE_DATABASE_ERR_FAIL_JOIN,$table1);
}
}
}
}
// Add any required linking tables to $tables array
if ($link_tables) {
foreach ($link_tables as $link) {
if (!in_array($link, $tables)) {
$tables[] = $link;
}
}
}
// Construct from clause from $tables array
$query['from'] = implode(', ', $tables);
// Construct where clause from $joins array
$query['where'] = implode("\n AND ", $joins);
}
// Add $filter condition, if present
if ($filter) {
if (isset($query['where'])) {
$query['where'] = '( ' . $query['where'] . " )\n" .
' AND ( ' . $filter . ')';
} else {
$query['where'] = $filter;
}
}
return $query;
}
// }}}
// {{{ function _buildFKeyFilter($data, $data_key = null, $filt_key = null, $match = 'simple')
/**
* Returns WHERE clause equating values of $data array to database column
* values
*
* Usage: The function is designed to return an SQL logical
* expression that equates the values of a set of foreign key columns in
* associative array $data, which is a row to be inserted or updated in
* one table, to the values of the corresponding columns of a referenced
* table. In this usage, $data_key is the foreign key (a column name or
* numerical array of column names), and $filt_key is the corresponding
* referenced key.
*
* Parameters: Parameter $data is an associative array containing data to
* be inserted into or used to update one row of a database table, in which
* array keys are column names. When present, $data_key contains either
* the name of a single array key of interest, or a numerical array of such
* keys. These are usually the names of the columns of a foreign key in
* that table. When, $data_key is null or absent, it is taken to be equal
* to an array containing all of the keys of $data. When present, $filt_key
* contains either a string or a numerical array of strings that are
* aliases for the keys in $data_key. These are usually the names of the
* corresponding columns in the referenced table. When $filt_key is null
* or absent, it is equated with $data_key internally. The function
* returns an SQL logical expression that equates the values in $data
* whose keys are specified by $data_key, to the values of database
* columns whose names are specified in $filt_key.
*
* General case: _buildFKeyFilter returns a SQL logical expression that
* equates the values of $data whose keys are given in $data_key with the
* values of database columns with names given in $filt_key. For example,
* if
* <code>
* $data = array( 'k1' => $v1, 'k2' => $v2, ... , 'k10' => $v10 );
* $data_key = array('k2', 'k5', 'k7');
* $filt_key = array('c2', 'c5', 'c7');
* </code>
* then buildFilter($data, $data_key, $filt_key) returns a string
* <code>
* "c2 = $v2 AND c5 = $v5 AND c7 = $v7"
* </code>
* in which the values $v2, $v5, $v7 are replaced by properly quoted
* SQL literal values. If, in the above example, $data_key = 'k5'
* and $filt_key = 'c5', then the function will return
* <code>
* "c5 = $v5"
* </code>
* where (again) $v5 is replaced by an SQL literal.
*
* Simple case: If parameters $data_key and $filt_key are null, the
* behavior is the same as that of the DB_Table_Base::buildFilter() method.
* For example, if
* <code>
* $data = array( 'c1' => $v1, 'c2' => $v2, 'c3' => $v3)
* </code>
* then _buildFKeyFilter($data) returns a string
* <code>
* "c1 => $val1 AND c2 => $val2 AND c3 = $v3"
* </code>
* in which the values $v1, $v2, $v3 are replaced by SQL literal values,
* quoted and escaped as appropriate for each data type and the backend.
*
* Quoting is done by the DB_Table_Database::quote() method, based on
* the php type of the values in $array. The treatment of null values
* in $data depends upon the value of the $match parameter.
*
* Null values: The treatment to null values in $data depends upon
* the value of the $match parameter . If $match == 'simple', an empty
* string is returned if any $value of $data with a key in $data_key
* is null. If $match == 'partial', the returned SQL expression
* equates only the relevant non-null values of $data to the values of
* corresponding database columns. If $match == 'full', the function
* returns an empty string if all of the relevant values of data are
* null, and returns a PEAR_Error if some of the selected values are
* null and others are not null.
*
* @param array $data associative array, keys are column names
* @param mixed $data_key string or numerical array of strings, in which
* values are a set of keys of interest in $data
* @param mixed $data_key string or numerical array of strings, in which
* values are names of a corresponding set of
* database column names.
* @return string SQL expression equating values in $data, for which keys
* also appear in $data_key, to values of corresponding
* database columns named in $filt_key.
* @access private
*/
function _buildFKeyFilter($data, $data_key = null, $filt_key = null,
$match = 'simple')
{
// Check $match type value
if (!in_array($match, array('simple', 'partial', 'full'))) {
return $this->throwError(
DB_TABLE_DATABASE_ERR_MATCH_TYPE);
}
// Simple case: Build filter from $data array alone
if (is_null($data_key) && is_null($filt_key)) {
return $this->buildFilter($data, $match);
}
// Defaults for $data_key and $filt_key:
if (is_null($data_key)) {
$data_key = array_keys($data);
}
if (is_null($filt_key)) {
$filt_key = $data_key;
}
// General case: $data_key and/or $filt_key not null
if (is_string($data_key)) {
if (!is_string($filt_key)) {
return $this->throwError(
DB_TABLE_DATABASE_ERR_FILT_KEY);
}
if (array_key_exists($data_key, $data)) {
$value = $data[$data_key];
if (!is_null($value)) {
$value = (string) $this->quote($data[$data_key]);
return "$filt_key = $value";
} else {
return '';
}
} else {
return '';
}
} elseif (is_array($data_key)) {
if (!is_array($filt_key)) {
return $this->throwError(
DB_TABLE_DATABASE_ERR_FILT_KEY);
}
$filter = array();
for ($i=0; $i < count($data_key); $i++) {
$data_col = $data_key[$i];
$filt_col = $filt_key[$i];
if (array_key_exists($data_col, $data)) {
$value = $data[$data_col];
if (!is_null($value)) {
if ($match == 'full' && isset($found_null)) {
return $this->throwError(
DB_TABLE_DATABASE_ERR_FULL_KEY);
}
$value = $this->quote($value);
$filter[] = "$filt_col = $value";
} else {
$found_null = true;
}
}
}
if ($match == 'simple' && isset($found_null)) {
return '';
}
if (count($filter) == 0) {
return '';
}
return implode(' AND ', $filter);
} else {
// Invalid data key
return $this->throwError(
DB_TABLE_DATABASE_ERR_DATA_KEY);
}
}
// }}}
// {{{ function quote($value)
/**
* Returns SQL literal string representation of a php value
*
* Calls MDB2::quote() or DB_Common::quoteSmart() to enquote and
* escape string values. If $value is:
* - a string, return the string enquoted and escaped
* - a number, return cast of number to string, without quotes
* - a boolean, return '1' for true and '0' for false
* - null, return the string 'NULL'
*
* @param mixed $value
* @return string Representation of value as an SQL literal
*
* @access public
*
* @see DB_Common::quoteSmart()
* @see MDB2::quote()
*/
function quote($value)
{
if (is_bool($value)) {
return $value ? '1' : '0';
}
if ($this->backend == 'mdb2') {
$value = $this->db->quote($value);
} else {
$value = $this->db->quoteSmart($value);
}
return (string) $value;
}
// }}}
// {{{ function __sleep()
/**
* Serializes all table references and sets $db = null, $backend = null
*
* @return array names of all properties
* @access public
*/
function __sleep()
{
$this->db = null;
$this->backend = null;
// needed in setDatabaseInstance, where null is passed by reference
$null_variable = null;
foreach ($this->_table as $name => $table_obj) {
$table_obj->db = null;
$table_obj->setDatabaseInstance($null_variable);
$this->_table[$name] = serialize($table_obj);
}
return array_keys(get_object_vars($this));
}
// }}}
// {{{ function __wakeup()
/**
* Unserializes DB_Table_Database object and all child DB_Table objects
*
* Immediately after unserialization, a DB_Table_Database object
* has null $db and $backend properties, as do all of its child
* DB_Table objects. The DB_Table_Database::setDB method should
* be called immediately after unserialization to re-establish
* the database connection, like so:
* <code>
* $db_object = unserialize($serialized_db);
* $db_object->setDB($conn);
* </code>
* where $conn is a DB/MDB2 object. This establishes a DB/MDB2
* connection for both the parent database and all child tables.
*
* This method unserializes all of the child DB_Table objects of
* a DB_Table_Database object. It must thus have access to the
* definitions of the associated DB_Table subclasses. These are
* listed in the $_table_subclass property. If a required subclass
* named $subclass is not defined, the __wakeup() method attempts
* to autoload a file "$subclass.php" in the directory specified
* by $this->table_subclass_path.
*
* @return void
* @access public
*/
function __wakeup()
{
foreach ($this->_table as $name => $table_string) {
// Check for subclass definition, and autoload if necessary.
$subclass = $this->_table_subclass[$name];
if (!is_null($subclass)) {
if (!class_exists($subclass)) {
$dir = $this->_table_subclass_path;
require_once $dir . '/' . $subclass . '.php';
}
}
// Unserialize table
$table_obj = unserialize($table_string);
// Reset references between database and table objects
$table_obj->setDatabaseInstance($this);
$this->_table[$name] = $table_obj;
}
}
// }}}
// {{{ function toXML()
/**
* Returns XML string representation of database declaration
*
* @param string $indent string of whitespace, prefix to each line
* @return string XML string representation
* @access public
*/
function toXML($indent = '') {
require_once 'DB/Table/XML.php';
$s = array();
$s[] = DB_Table_XML::openTag('database', $indent);
foreach ($this->_table as $name => $table_obj) {
$s[] = $table_obj->toXML($indent);
}
$s[] = DB_Table_XML::closeTag('database', $indent);
return implode("\n", $s);
}
// }}}
// {{{ function fromXML($xml_string, $conn)
/**
* Returns a DB_Table_Database object constructed from an XML string
*
* Uses the MDB2 XML schema for a database element, including a new
* syntax for foreign key indices.
*
* NOTE: This function requires PHP 5. It throws an error if used
* with PHP 4.
*
* @param string XML string representation
* @return object DB_Table_Database object on success (PEAR_Error on failure)
*
* @throws PEAR_Error if:
* - PHP version is not >= 5.0.0 (...DATABASE_ERR_PHP_VERSION )
* - Parsing by simpleXML fails (...DATABASE_ERR_XML_PARSE )
*
* @access public
*/
function fromXML($xml_string, $conn)
{
// Check PHP version. Throw error if not >= PHP 5.0.0
$version = phpversion();
if (version_compare($version, '5.0.0', "<")) {
return $this->throwError(
DB_TABLE_DATABASE_ERR_PHP_VERSION,
$version);
}
$xml = simplexml_load_string($xml_string);
if ($xml == false) {
return $this->throwError(
DB_TABLE_DATABASE_ERR_XML_PARSE);
}
// Instantiate database object
$database_name = (string) $xml->name;
$database_obj = new DB_Table_Database($conn, $database_name);
// Create array of foreign key references
$ref = array();
// Loop over tables
foreach ($xml->table as $table) {
$table_name = (string) $table->name;
// Instantiate table object
$table_obj = new DB_Table($conn, $table_name);
// Add columns to table object
$declaration = $table->declaration;
foreach ($declaration->field as $field) {
$col_name = (string) $field->name;
$type = (string) $field->type;
$def = array('type' => $type);
if (isset($field->length)) {
$def['size'] = (integer) $field->length;
}
if (isset($field->notnull)) {
if ($field->notnull) {
$def['require'] = true;
} else {
$def['require'] = false;
}
}
if (isset($field->default)) {
$def['default'] = $field->default;
}
if (isset($field->autoincrement)) {
if (is_null($table_obj->auto_inc_col)) {
$table_obj->auto_inc_col = $col_name;
} else {
return $this->throwError(
DB_TABLE_DATABASE_ERR_XML_MULT_AUTO_INC);
}
}
$table_obj->col[$col_name] = $def;
}
// Add indices
foreach ($declaration->index as $index) {
if (isset($index->name)) {
$name = (string) $index->name;
} else {
$name = null;
}
$def = array();
if (isset($index->primary)) {
$def['type'] = 'primary';
} elseif (isset($index->unique)) {
$def['type'] = 'unique';
} else {
$def['type'] = 'normal';
}
foreach ($index->field as $field) {
$def['cols'][] = (string) $field;
}
if ($name) {
$table_obj->idx[$name] = $def;
} else {
$table_obj->idx[] = $def;
}
}
// Add table object to database object
$database_obj->addTable($table_obj);
// Foreign key references
foreach ($declaration->foreign as $foreign) {
if (isset($foreign->name)) {
$name = (string) $foreign->name;
} else {
$name = null;
}
$fkey = array();
foreach ($foreign->field as $field) {
$fkey[] = (string) $field;
}
if (count($fkey) == 1) {
$fkey = $fkey[0];
}
$rtable = (string) $foreign->references->table;
if (isset($foreign->references->field)) {
$rkey = array();
foreach ($foreign->references->field as $field) {
$rkey[] = (string) $field;
}
if (count($rkey)==1) {
$rkey = $rkey[0];
}
} else {
$rkey = null;
}
if (isset($foreign->ondelete)) {
$on_delete = (string) $foreign->ondelete;
} else {
$on_delete = null;
}
if (isset($foreign->onupdate)) {
$on_update = (string) $foreign->onupdate;
} else {
$on_update = null;
}
// Add reference definition to $ref array
$def = array();
$def['fkey'] = $fkey;
$def['rkey'] = $rkey;
$def['on_delete'] = $on_delete;
$def['on_update'] = $on_update;
if (!isset($ref[$table_name])) {
$ref[$table_name] = array();
}
$ref[$table_name][$rtable] = $def;
}
// Release variable $table_obj to refer to another table
unset($table_obj);
}
// Add all references to database object
foreach ($ref as $ftable => $list) {
foreach ($list as $rtable => $def) {
$fkey = $def['fkey'];
$rkey = $def['rkey'];
$on_delete = $def['on_delete'];
$on_update = $def['on_update'];
$database_obj->addRef($ftable, $fkey, $rtable, $rkey,
$on_delete, $on_update);
}
}
return $database_obj;
}
// }}}
// }}}
}
// }}}
/* Local variables:
* tab-width: 4
* c-basic-offset: 4
* c-hanging-comment-ender-p: nil
* End:
*/
?>