<?php /** * Zend Framework * * LICENSE * * This source file is subject to the new BSD license that is bundled * with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://framework.zend.com/license/new-bsd * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@zend.com so we can send you a copy immediately. * * @category Zend * @package Zend_Db * @subpackage Table * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License * @version $Id$ */ /** * @see Zend_Db_Adapter_Abstract */ require_once 'Zend/Db/Adapter/Abstract.php'; /** * @see Zend_Db_Adapter_Abstract */ require_once 'Zend/Db/Select.php'; /** * @see Zend_Db */ require_once 'Zend/Db.php'; /** * Class for SQL table interface. * * @category Zend * @package Zend_Db * @subpackage Table * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ abstract class Zend_Db_Table_Abstract { const ADAPTER = 'db'; const DEFINITION = 'definition'; const DEFINITION_CONFIG_NAME = 'definitionConfigName'; const SCHEMA = 'schema'; const NAME = 'name'; const PRIMARY = 'primary'; const COLS = 'cols'; const METADATA = 'metadata'; const METADATA_CACHE = 'metadataCache'; const METADATA_CACHE_IN_CLASS = 'metadataCacheInClass'; const ROW_CLASS = 'rowClass'; const ROWSET_CLASS = 'rowsetClass'; const REFERENCE_MAP = 'referenceMap'; const DEPENDENT_TABLES = 'dependentTables'; const SEQUENCE = 'sequence'; const COLUMNS = 'columns'; const REF_TABLE_CLASS = 'refTableClass'; const REF_COLUMNS = 'refColumns'; const ON_DELETE = 'onDelete'; const ON_UPDATE = 'onUpdate'; const CASCADE = 'cascade'; const CASCADE_RECURSE = 'cascadeRecurse'; const RESTRICT = 'restrict'; const SET_NULL = 'setNull'; const DEFAULT_NONE = 'defaultNone'; const DEFAULT_CLASS = 'defaultClass'; const DEFAULT_DB = 'defaultDb'; const SELECT_WITH_FROM_PART = true; const SELECT_WITHOUT_FROM_PART = false; /** * Default Zend_Db_Adapter_Abstract object. * * @var Zend_Db_Adapter_Abstract */ protected static $_defaultDb; /** * Optional Zend_Db_Table_Definition object * * @var unknown_type */ protected $_definition = null; /** * Optional definition config name used in concrete implementation * * @var string */ protected $_definitionConfigName = null; /** * Default cache for information provided by the adapter's describeTable() method. * * @var Zend_Cache_Core */ protected static $_defaultMetadataCache = null; /** * Zend_Db_Adapter_Abstract object. * * @var Zend_Db_Adapter_Abstract */ protected $_db; /** * The schema name (default null means current schema) * * @var array */ protected $_schema = null; /** * The table name. * * @var string */ protected $_name = null; /** * The table column names derived from Zend_Db_Adapter_Abstract::describeTable(). * * @var array */ protected $_cols; /** * The primary key column or columns. * A compound key should be declared as an array. * You may declare a single-column primary key * as a string. * * @var mixed */ protected $_primary = null; /** * If your primary key is a compound key, and one of the columns uses * an auto-increment or sequence-generated value, set _identity * to the ordinal index in the $_primary array for that column. * Note this index is the position of the column in the primary key, * not the position of the column in the table. The primary key * array is 1-based. * * @var integer */ protected $_identity = 1; /** * Define the logic for new values in the primary key. * May be a string, boolean true, or boolean false. * * @var mixed */ protected $_sequence = true; /** * Information provided by the adapter's describeTable() method. * * @var array */ protected $_metadata = array(); /** * Cache for information provided by the adapter's describeTable() method. * * @var Zend_Cache_Core */ protected $_metadataCache = null; /** * Flag: whether or not to cache metadata in the class * @var bool */ protected $_metadataCacheInClass = true; /** * Classname for row * * @var string */ protected $_rowClass = 'Zend_Db_Table_Row'; /** * Classname for rowset * * @var string */ protected $_rowsetClass = 'Zend_Db_Table_Rowset'; /** * Associative array map of declarative referential integrity rules. * This array has one entry per foreign key in the current table. * Each key is a mnemonic name for one reference rule. * * Each value is also an associative array, with the following keys: * - columns = array of names of column(s) in the child table. * - refTableClass = class name of the parent table. * - refColumns = array of names of column(s) in the parent table, * in the same order as those in the 'columns' entry. * - onDelete = "cascade" means that a delete in the parent table also * causes a delete of referencing rows in the child table. * - onUpdate = "cascade" means that an update of primary key values in * the parent table also causes an update of referencing * rows in the child table. * * @var array */ protected $_referenceMap = array(); /** * Simple array of class names of tables that are "children" of the current * table, in other words tables that contain a foreign key to this one. * Array elements are not table names; they are class names of classes that * extend Zend_Db_Table_Abstract. * * @var array */ protected $_dependentTables = array(); protected $_defaultSource = self::DEFAULT_NONE; protected $_defaultValues = array(); /** * Constructor. * * Supported params for $config are: * - db = user-supplied instance of database connector, * or key name of registry instance. * - name = table name. * - primary = string or array of primary key(s). * - rowClass = row class name. * - rowsetClass = rowset class name. * - referenceMap = array structure to declare relationship * to parent tables. * - dependentTables = array of child tables. * - metadataCache = cache for information from adapter describeTable(). * * @param mixed $config Array of user-specified config options, or just the Db Adapter. * @return void */ public function __construct($config = array()) { /** * Allow a scalar argument to be the Adapter object or Registry key. */ if (!is_array($config)) { $config = array(self::ADAPTER => $config); } if ($config) { $this->setOptions($config); } $this->_setup(); $this->init(); } /** * setOptions() * * @param array $options * @return Zend_Db_Table_Abstract */ public function setOptions(Array $options) { foreach ($options as $key => $value) { switch ($key) { case self::ADAPTER: $this->_setAdapter($value); break; case self::DEFINITION: $this->setDefinition($value); break; case self::DEFINITION_CONFIG_NAME: $this->setDefinitionConfigName($value); break; case self::SCHEMA: $this->_schema = (string) $value; break; case self::NAME: $this->_name = (string) $value; break; case self::PRIMARY: $this->_primary = (array) $value; break; case self::ROW_CLASS: $this->setRowClass($value); break; case self::ROWSET_CLASS: $this->setRowsetClass($value); break; case self::REFERENCE_MAP: $this->setReferences($value); break; case self::DEPENDENT_TABLES: $this->setDependentTables($value); break; case self::METADATA_CACHE: $this->_setMetadataCache($value); break; case self::METADATA_CACHE_IN_CLASS: $this->setMetadataCacheInClass($value); break; case self::SEQUENCE: $this->_setSequence($value); break; default: // ignore unrecognized configuration directive break; } } return $this; } /** * setDefinition() * * @param Zend_Db_Table_Definition $definition * @return Zend_Db_Table_Abstract */ public function setDefinition(Zend_Db_Table_Definition $definition) { $this->_definition = $definition; return $this; } /** * getDefinition() * * @return Zend_Db_Table_Definition|null */ public function getDefinition() { return $this->_definition; } /** * setDefinitionConfigName() * * @param string $definition * @return Zend_Db_Table_Abstract */ public function setDefinitionConfigName($definitionConfigName) { $this->_definitionConfigName = $definitionConfigName; return $this; } /** * getDefinitionConfigName() * * @return string */ public function getDefinitionConfigName() { return $this->_definitionConfigName; } /** * @param string $classname * @return Zend_Db_Table_Abstract Provides a fluent interface */ public function setRowClass($classname) { $this->_rowClass = (string) $classname; return $this; } /** * @return string */ public function getRowClass() { return $this->_rowClass; } /** * @param string $classname * @return Zend_Db_Table_Abstract Provides a fluent interface */ public function setRowsetClass($classname) { $this->_rowsetClass = (string) $classname; return $this; } /** * @return string */ public function getRowsetClass() { return $this->_rowsetClass; } /** * Add a reference to the reference map * * @param string $ruleKey * @param string|array $columns * @param string $refTableClass * @param string|array $refColumns * @param string $onDelete * @param string $onUpdate * @return Zend_Db_Table_Abstract */ public function addReference($ruleKey, $columns, $refTableClass, $refColumns, $onDelete = null, $onUpdate = null) { $reference = array(self::COLUMNS => (array) $columns, self::REF_TABLE_CLASS => $refTableClass, self::REF_COLUMNS => (array) $refColumns); if (!empty($onDelete)) { $reference[self::ON_DELETE] = $onDelete; } if (!empty($onUpdate)) { $reference[self::ON_UPDATE] = $onUpdate; } $this->_referenceMap[$ruleKey] = $reference; return $this; } /** * @param array $referenceMap * @return Zend_Db_Table_Abstract Provides a fluent interface */ public function setReferences(array $referenceMap) { $this->_referenceMap = $referenceMap; return $this; } /** * @param string $tableClassname * @param string $ruleKey OPTIONAL * @return array * @throws Zend_Db_Table_Exception */ public function getReference($tableClassname, $ruleKey = null) { $thisClass = get_class($this); if ($thisClass === 'Zend_Db_Table') { $thisClass = $this->_definitionConfigName; } $refMap = $this->_getReferenceMapNormalized(); if ($ruleKey !== null) { if (!isset($refMap[$ruleKey])) { require_once "Zend/Db/Table/Exception.php"; throw new Zend_Db_Table_Exception("No reference rule \"$ruleKey\" from table $thisClass to table $tableClassname"); } if ($refMap[$ruleKey][self::REF_TABLE_CLASS] != $tableClassname) { require_once "Zend/Db/Table/Exception.php"; throw new Zend_Db_Table_Exception("Reference rule \"$ruleKey\" does not reference table $tableClassname"); } return $refMap[$ruleKey]; } foreach ($refMap as $reference) { if ($reference[self::REF_TABLE_CLASS] == $tableClassname) { return $reference; } } require_once "Zend/Db/Table/Exception.php"; throw new Zend_Db_Table_Exception("No reference from table $thisClass to table $tableClassname"); } /** * @param array $dependentTables * @return Zend_Db_Table_Abstract Provides a fluent interface */ public function setDependentTables(array $dependentTables) { $this->_dependentTables = $dependentTables; return $this; } /** * @return array */ public function getDependentTables() { return $this->_dependentTables; } /** * set the defaultSource property - this tells the table class where to find default values * * @param string $defaultSource * @return Zend_Db_Table_Abstract */ public function setDefaultSource($defaultSource = self::DEFAULT_NONE) { if (!in_array($defaultSource, array(self::DEFAULT_CLASS, self::DEFAULT_DB, self::DEFAULT_NONE))) { $defaultSource = self::DEFAULT_NONE; } $this->_defaultSource = $defaultSource; return $this; } /** * returns the default source flag that determines where defaultSources come from * * @return unknown */ public function getDefaultSource() { return $this->_defaultSource; } /** * set the default values for the table class * * @param array $defaultValues * @return Zend_Db_Table_Abstract */ public function setDefaultValues(Array $defaultValues) { foreach ($defaultValues as $defaultName => $defaultValue) { if (array_key_exists($defaultName, $this->_metadata)) { $this->_defaultValues[$defaultName] = $defaultValue; } } return $this; } public function getDefaultValues() { return $this->_defaultValues; } /** * Sets the default Zend_Db_Adapter_Abstract for all Zend_Db_Table objects. * * @param mixed $db Either an Adapter object, or a string naming a Registry key * @return void */ public static function setDefaultAdapter($db = null) { self::$_defaultDb = self::_setupAdapter($db); } /** * Gets the default Zend_Db_Adapter_Abstract for all Zend_Db_Table objects. * * @return Zend_Db_Adapter_Abstract or null */ public static function getDefaultAdapter() { return self::$_defaultDb; } /** * @param mixed $db Either an Adapter object, or a string naming a Registry key * @return Zend_Db_Table_Abstract Provides a fluent interface */ protected function _setAdapter($db) { $this->_db = self::_setupAdapter($db); return $this; } /** * Gets the Zend_Db_Adapter_Abstract for this particular Zend_Db_Table object. * * @return Zend_Db_Adapter_Abstract */ public function getAdapter() { return $this->_db; } /** * @param mixed $db Either an Adapter object, or a string naming a Registry key * @return Zend_Db_Adapter_Abstract * @throws Zend_Db_Table_Exception */ protected static function _setupAdapter($db) { if ($db === null) { return null; } if (is_string($db)) { require_once 'Zend/Registry.php'; $db = Zend_Registry::get($db); } if (!$db instanceof Zend_Db_Adapter_Abstract) { require_once 'Zend/Db/Table/Exception.php'; throw new Zend_Db_Table_Exception('Argument must be of type Zend_Db_Adapter_Abstract, or a Registry key where a Zend_Db_Adapter_Abstract object is stored'); } return $db; } /** * Sets the default metadata cache for information returned by Zend_Db_Adapter_Abstract::describeTable(). * * If $defaultMetadataCache is null, then no metadata cache is used by default. * * @param mixed $metadataCache Either a Cache object, or a string naming a Registry key * @return void */ public static function setDefaultMetadataCache($metadataCache = null) { self::$_defaultMetadataCache = self::_setupMetadataCache($metadataCache); } /** * Gets the default metadata cache for information returned by Zend_Db_Adapter_Abstract::describeTable(). * * @return Zend_Cache_Core or null */ public static function getDefaultMetadataCache() { return self::$_defaultMetadataCache; } /** * Sets the metadata cache for information returned by Zend_Db_Adapter_Abstract::describeTable(). * * If $metadataCache is null, then no metadata cache is used. Since there is no opportunity to reload metadata * after instantiation, this method need not be public, particularly because that it would have no effect * results in unnecessary API complexity. To configure the metadata cache, use the metadataCache configuration * option for the class constructor upon instantiation. * * @param mixed $metadataCache Either a Cache object, or a string naming a Registry key * @return Zend_Db_Table_Abstract Provides a fluent interface */ protected function _setMetadataCache($metadataCache) { $this->_metadataCache = self::_setupMetadataCache($metadataCache); return $this; } /** * Gets the metadata cache for information returned by Zend_Db_Adapter_Abstract::describeTable(). * * @return Zend_Cache_Core or null */ public function getMetadataCache() { return $this->_metadataCache; } /** * Indicate whether metadata should be cached in the class for the duration * of the instance * * @param bool $flag * @return Zend_Db_Table_Abstract */ public function setMetadataCacheInClass($flag) { $this->_metadataCacheInClass = (bool) $flag; return $this; } /** * Retrieve flag indicating if metadata should be cached for duration of * instance * * @return bool */ public function metadataCacheInClass() { return $this->_metadataCacheInClass; } /** * @param mixed $metadataCache Either a Cache object, or a string naming a Registry key * @return Zend_Cache_Core * @throws Zend_Db_Table_Exception */ protected static function _setupMetadataCache($metadataCache) { if ($metadataCache === null) { return null; } if (is_string($metadataCache)) { require_once 'Zend/Registry.php'; $metadataCache = Zend_Registry::get($metadataCache); } if (!$metadataCache instanceof Zend_Cache_Core) { require_once 'Zend/Db/Table/Exception.php'; throw new Zend_Db_Table_Exception('Argument must be of type Zend_Cache_Core, or a Registry key where a Zend_Cache_Core object is stored'); } return $metadataCache; } /** * Sets the sequence member, which defines the behavior for generating * primary key values in new rows. * - If this is a string, then the string names the sequence object. * - If this is boolean true, then the key uses an auto-incrementing * or identity mechanism. * - If this is boolean false, then the key is user-defined. * Use this for natural keys, for example. * * @param mixed $sequence * @return Zend_Db_Table_Adapter_Abstract Provides a fluent interface */ protected function _setSequence($sequence) { $this->_sequence = $sequence; return $this; } /** * Turnkey for initialization of a table object. * Calls other protected methods for individual tasks, to make it easier * for a subclass to override part of the setup logic. * * @return void */ protected function _setup() { $this->_setupDatabaseAdapter(); $this->_setupTableName(); } /** * Initialize database adapter. * * @return void * @throws Zend_Db_Table_Exception */ protected function _setupDatabaseAdapter() { if (! $this->_db) { $this->_db = self::getDefaultAdapter(); if (!$this->_db instanceof Zend_Db_Adapter_Abstract) { require_once 'Zend/Db/Table/Exception.php'; throw new Zend_Db_Table_Exception('No adapter found for ' . get_class($this)); } } } /** * Initialize table and schema names. * * If the table name is not set in the class definition, * use the class name itself as the table name. * * A schema name provided with the table name (e.g., "schema.table") overrides * any existing value for $this->_schema. * * @return void */ protected function _setupTableName() { if (! $this->_name) { $this->_name = get_class($this); } else if (strpos($this->_name, '.')) { list($this->_schema, $this->_name) = explode('.', $this->_name); } } /** * Initializes metadata. * * If metadata cannot be loaded from cache, adapter's describeTable() method is called to discover metadata * information. Returns true if and only if the metadata are loaded from cache. * * @return boolean * @throws Zend_Db_Table_Exception */ protected function _setupMetadata() { if ($this->metadataCacheInClass() && (count($this->_metadata) > 0)) { return true; } // Assume that metadata will be loaded from cache $isMetadataFromCache = true; // If $this has no metadata cache but the class has a default metadata cache if (null === $this->_metadataCache && null !== self::$_defaultMetadataCache) { // Make $this use the default metadata cache of the class $this->_setMetadataCache(self::$_defaultMetadataCache); } // If $this has a metadata cache if (null !== $this->_metadataCache) { // Define the cache identifier where the metadata are saved //get db configuration $dbConfig = $this->_db->getConfig(); $port = isset($dbConfig['options']['port']) ? ':'.$dbConfig['options']['port'] : (isset($dbConfig['port']) ? ':'.$dbConfig['port'] : null); $host = isset($dbConfig['options']['host']) ? ':'.$dbConfig['options']['host'] : (isset($dbConfig['host']) ? ':'.$dbConfig['host'] : null); // Define the cache identifier where the metadata are saved $cacheId = md5( // port:host/dbname:schema.table (based on availabilty) $port . $host . '/'. $dbConfig['dbname'] . ':' . $this->_schema. '.' . $this->_name ); } // If $this has no metadata cache or metadata cache misses if (null === $this->_metadataCache || !($metadata = $this->_metadataCache->load($cacheId))) { // Metadata are not loaded from cache $isMetadataFromCache = false; // Fetch metadata from the adapter's describeTable() method $metadata = $this->_db->describeTable($this->_name, $this->_schema); // If $this has a metadata cache, then cache the metadata if (null !== $this->_metadataCache && !$this->_metadataCache->save($metadata, $cacheId)) { trigger_error('Failed saving metadata to metadataCache', E_USER_NOTICE); } } // Assign the metadata to $this $this->_metadata = $metadata; // Return whether the metadata were loaded from cache return $isMetadataFromCache; } /** * Retrieve table columns * * @return array */ protected function _getCols() { if (null === $this->_cols) { $this->_setupMetadata(); $this->_cols = array_keys($this->_metadata); } return $this->_cols; } /** * Initialize primary key from metadata. * If $_primary is not defined, discover primary keys * from the information returned by describeTable(). * * @return void * @throws Zend_Db_Table_Exception */ protected function _setupPrimaryKey() { if (!$this->_primary) { $this->_setupMetadata(); $this->_primary = array(); foreach ($this->_metadata as $col) { if ($col['PRIMARY']) { $this->_primary[ $col['PRIMARY_POSITION'] ] = $col['COLUMN_NAME']; if ($col['IDENTITY']) { $this->_identity = $col['PRIMARY_POSITION']; } } } // if no primary key was specified and none was found in the metadata // then throw an exception. if (empty($this->_primary)) { require_once 'Zend/Db/Table/Exception.php'; throw new Zend_Db_Table_Exception("A table must have a primary key, but none was found for table '{$this->_name}'"); } } else if (!is_array($this->_primary)) { $this->_primary = array(1 => $this->_primary); } else if (isset($this->_primary[0])) { array_unshift($this->_primary, null); unset($this->_primary[0]); } $cols = $this->_getCols(); if (! array_intersect((array) $this->_primary, $cols) == (array) $this->_primary) { require_once 'Zend/Db/Table/Exception.php'; throw new Zend_Db_Table_Exception("Primary key column(s) (" . implode(',', (array) $this->_primary) . ") are not columns in this table (" . implode(',', $cols) . ")"); } $primary = (array) $this->_primary; $pkIdentity = $primary[(int) $this->_identity]; /** * Special case for PostgreSQL: a SERIAL key implicitly uses a sequence * object whose name is "<table>_<column>_seq". */ if ($this->_sequence === true && $this->_db instanceof Zend_Db_Adapter_Pdo_Pgsql) { $this->_sequence = $this->_db->quoteIdentifier("{$this->_name}_{$pkIdentity}_seq"); if ($this->_schema) { $this->_sequence = $this->_db->quoteIdentifier($this->_schema) . '.' . $this->_sequence; } } } /** * Returns a normalized version of the reference map * * @return array */ protected function _getReferenceMapNormalized() { $referenceMapNormalized = array(); foreach ($this->_referenceMap as $rule => $map) { $referenceMapNormalized[$rule] = array(); foreach ($map as $key => $value) { switch ($key) { // normalize COLUMNS and REF_COLUMNS to arrays case self::COLUMNS: case self::REF_COLUMNS: if (!is_array($value)) { $referenceMapNormalized[$rule][$key] = array($value); } else { $referenceMapNormalized[$rule][$key] = $value; } break; // other values are copied as-is default: $referenceMapNormalized[$rule][$key] = $value; break; } } } return $referenceMapNormalized; } /** * Initialize object * * Called from {@link __construct()} as final step of object instantiation. * * @return void */ public function init() { } /** * Returns table information. * * You can elect to return only a part of this information by supplying its key name, * otherwise all information is returned as an array. * * @param string $key The specific info part to return OPTIONAL * @return mixed * @throws Zend_Db_Table_Exception */ public function info($key = null) { $this->_setupPrimaryKey(); $info = array( self::SCHEMA => $this->_schema, self::NAME => $this->_name, self::COLS => $this->_getCols(), self::PRIMARY => (array) $this->_primary, self::METADATA => $this->_metadata, self::ROW_CLASS => $this->getRowClass(), self::ROWSET_CLASS => $this->getRowsetClass(), self::REFERENCE_MAP => $this->_referenceMap, self::DEPENDENT_TABLES => $this->_dependentTables, self::SEQUENCE => $this->_sequence ); if ($key === null) { return $info; } if (!array_key_exists($key, $info)) { require_once 'Zend/Db/Table/Exception.php'; throw new Zend_Db_Table_Exception('There is no table information for the key "' . $key . '"'); } return $info[$key]; } /** * Returns an instance of a Zend_Db_Table_Select object. * * @param bool $withFromPart Whether or not to include the from part of the select based on the table * @return Zend_Db_Table_Select */ public function select($withFromPart = self::SELECT_WITHOUT_FROM_PART) { require_once 'Zend/Db/Table/Select.php'; $select = new Zend_Db_Table_Select($this); if ($withFromPart == self::SELECT_WITH_FROM_PART) { $select->from($this->info(self::NAME), Zend_Db_Table_Select::SQL_WILDCARD, $this->info(self::SCHEMA)); } return $select; } /** * Inserts a new row. * * @param array $data Column-value pairs. * @return mixed The primary key of the row inserted. */ public function insert(array $data) { $this->_setupPrimaryKey(); /** * Zend_Db_Table assumes that if you have a compound primary key * and one of the columns in the key uses a sequence, * it's the _first_ column in the compound key. */ $primary = (array) $this->_primary; $pkIdentity = $primary[(int)$this->_identity]; /** * If this table uses a database sequence object and the data does not * specify a value, then get the next ID from the sequence and add it * to the row. We assume that only the first column in a compound * primary key takes a value from a sequence. */ if (is_string($this->_sequence) && !isset($data[$pkIdentity])) { $data[$pkIdentity] = $this->_db->nextSequenceId($this->_sequence); $pkSuppliedBySequence = true; } /** * If the primary key can be generated automatically, and no value was * specified in the user-supplied data, then omit it from the tuple. * * Note: this checks for sensible values in the supplied primary key * position of the data. The following values are considered empty: * null, false, true, '', array() */ if (!isset($pkSuppliedBySequence) && array_key_exists($pkIdentity, $data)) { if ($data[$pkIdentity] === null // null || $data[$pkIdentity] === '' // empty string || is_bool($data[$pkIdentity]) // boolean || (is_array($data[$pkIdentity]) && empty($data[$pkIdentity]))) { // empty array unset($data[$pkIdentity]); } } /** * INSERT the new row. */ $tableSpec = ($this->_schema ? $this->_schema . '.' : '') . $this->_name; $this->_db->insert($tableSpec, $data); /** * Fetch the most recent ID generated by an auto-increment * or IDENTITY column, unless the user has specified a value, * overriding the auto-increment mechanism. */ if ($this->_sequence === true && !isset($data[$pkIdentity])) { $data[$pkIdentity] = $this->_db->lastInsertId(); } /** * Return the primary key value if the PK is a single column, * else return an associative array of the PK column/value pairs. */ $pkData = array_intersect_key($data, array_flip($primary)); if (count($primary) == 1) { reset($pkData); return current($pkData); } return $pkData; } /** * Check if the provided column is an identity of the table * * @param string $column * @throws Zend_Db_Table_Exception * @return boolean */ public function isIdentity($column) { $this->_setupPrimaryKey(); if (!isset($this->_metadata[$column])) { /** * @see Zend_Db_Table_Exception */ require_once 'Zend/Db/Table/Exception.php'; throw new Zend_Db_Table_Exception('Column "' . $column . '" not found in table.'); } return (bool) $this->_metadata[$column]['IDENTITY']; } /** * Updates existing rows. * * @param array $data Column-value pairs. * @param array|string $where An SQL WHERE clause, or an array of SQL WHERE clauses. * @return int The number of rows updated. */ public function update(array $data, $where) { $tableSpec = ($this->_schema ? $this->_schema . '.' : '') . $this->_name; return $this->_db->update($tableSpec, $data, $where); } /** * Called by a row object for the parent table's class during save() method. * * @param string $parentTableClassname * @param array $oldPrimaryKey * @param array $newPrimaryKey * @return int */ public function _cascadeUpdate($parentTableClassname, array $oldPrimaryKey, array $newPrimaryKey) { $this->_setupMetadata(); $rowsAffected = 0; foreach ($this->_getReferenceMapNormalized() as $map) { if ($map[self::REF_TABLE_CLASS] == $parentTableClassname && isset($map[self::ON_UPDATE])) { switch ($map[self::ON_UPDATE]) { case self::CASCADE: $newRefs = array(); $where = array(); for ($i = 0; $i < count($map[self::COLUMNS]); ++$i) { $col = $this->_db->foldCase($map[self::COLUMNS][$i]); $refCol = $this->_db->foldCase($map[self::REF_COLUMNS][$i]); if (array_key_exists($refCol, $newPrimaryKey)) { $newRefs[$col] = $newPrimaryKey[$refCol]; } $type = $this->_metadata[$col]['DATA_TYPE']; $where[] = $this->_db->quoteInto( $this->_db->quoteIdentifier($col, true) . ' = ?', $oldPrimaryKey[$refCol], $type); } $rowsAffected += $this->update($newRefs, $where); break; default: // no action break; } } } return $rowsAffected; } /** * Deletes existing rows. * * @param array|string $where SQL WHERE clause(s). * @return int The number of rows deleted. */ public function delete($where) { $depTables = $this->getDependentTables(); if (!empty($depTables)) { $resultSet = $this->fetchAll($where); if (count($resultSet) > 0 ) { foreach ($resultSet as $row) { /** * Execute cascading deletes against dependent tables */ foreach ($depTables as $tableClass) { $t = self::getTableFromString($tableClass, $this); $t->_cascadeDelete($tableClass, $row->getPrimaryKey()); } } } } $tableSpec = ($this->_schema ? $this->_schema . '.' : '') . $this->_name; return $this->_db->delete($tableSpec, $where); } /** * Called by parent table's class during delete() method. * * @param string $parentTableClassname * @param array $primaryKey * @return int Number of affected rows */ public function _cascadeDelete($parentTableClassname, array $primaryKey) { // setup metadata $this->_setupMetadata(); // get this class name $thisClass = get_class($this); if ($thisClass === 'Zend_Db_Table') { $thisClass = $this->_definitionConfigName; } $rowsAffected = 0; foreach ($this->_getReferenceMapNormalized() as $map) { if ($map[self::REF_TABLE_CLASS] == $parentTableClassname && isset($map[self::ON_DELETE])) { $where = array(); // CASCADE or CASCADE_RECURSE if (in_array($map[self::ON_DELETE], array(self::CASCADE, self::CASCADE_RECURSE))) { for ($i = 0; $i < count($map[self::COLUMNS]); ++$i) { $col = $this->_db->foldCase($map[self::COLUMNS][$i]); $refCol = $this->_db->foldCase($map[self::REF_COLUMNS][$i]); $type = $this->_metadata[$col]['DATA_TYPE']; $where[] = $this->_db->quoteInto( $this->_db->quoteIdentifier($col, true) . ' = ?', $primaryKey[$refCol], $type); } } // CASCADE_RECURSE if ($map[self::ON_DELETE] == self::CASCADE_RECURSE) { /** * Execute cascading deletes against dependent tables */ $depTables = $this->getDependentTables(); if (!empty($depTables)) { foreach ($depTables as $tableClass) { $t = self::getTableFromString($tableClass, $this); foreach ($this->fetchAll($where) as $depRow) { $rowsAffected += $t->_cascadeDelete($thisClass, $depRow->getPrimaryKey()); } } } } // CASCADE or CASCADE_RECURSE if (in_array($map[self::ON_DELETE], array(self::CASCADE, self::CASCADE_RECURSE))) { $rowsAffected += $this->delete($where); } } } return $rowsAffected; } /** * Fetches rows by primary key. The argument specifies one or more primary * key value(s). To find multiple rows by primary key, the argument must * be an array. * * This method accepts a variable number of arguments. If the table has a * multi-column primary key, the number of arguments must be the same as * the number of columns in the primary key. To find multiple rows in a * table with a multi-column primary key, each argument must be an array * with the same number of elements. * * The find() method always returns a Rowset object, even if only one row * was found. * * @param mixed $key The value(s) of the primary keys. * @return Zend_Db_Table_Rowset_Abstract Row(s) matching the criteria. * @throws Zend_Db_Table_Exception */ public function find() { $this->_setupPrimaryKey(); $args = func_get_args(); $keyNames = array_values((array) $this->_primary); if (count($args) < count($keyNames)) { require_once 'Zend/Db/Table/Exception.php'; throw new Zend_Db_Table_Exception("Too few columns for the primary key"); } if (count($args) > count($keyNames)) { require_once 'Zend/Db/Table/Exception.php'; throw new Zend_Db_Table_Exception("Too many columns for the primary key"); } $whereList = array(); $numberTerms = 0; foreach ($args as $keyPosition => $keyValues) { $keyValuesCount = count($keyValues); // Coerce the values to an array. // Don't simply typecast to array, because the values // might be Zend_Db_Expr objects. if (!is_array($keyValues)) { $keyValues = array($keyValues); } if ($numberTerms == 0) { $numberTerms = $keyValuesCount; } else if ($keyValuesCount != $numberTerms) { require_once 'Zend/Db/Table/Exception.php'; throw new Zend_Db_Table_Exception("Missing value(s) for the primary key"); } $keyValues = array_values($keyValues); for ($i = 0; $i < $keyValuesCount; ++$i) { if (!isset($whereList[$i])) { $whereList[$i] = array(); } $whereList[$i][$keyPosition] = $keyValues[$i]; } } $whereClause = null; if (count($whereList)) { $whereOrTerms = array(); $tableName = $this->_db->quoteTableAs($this->_name, null, true); foreach ($whereList as $keyValueSets) { $whereAndTerms = array(); foreach ($keyValueSets as $keyPosition => $keyValue) { $type = $this->_metadata[$keyNames[$keyPosition]]['DATA_TYPE']; $columnName = $this->_db->quoteIdentifier($keyNames[$keyPosition], true); $whereAndTerms[] = $this->_db->quoteInto( $tableName . '.' . $columnName . ' = ?', $keyValue, $type); } $whereOrTerms[] = '(' . implode(' AND ', $whereAndTerms) . ')'; } $whereClause = '(' . implode(' OR ', $whereOrTerms) . ')'; } // issue ZF-5775 (empty where clause should return empty rowset) if ($whereClause == null) { $rowsetClass = $this->getRowsetClass(); if (!class_exists($rowsetClass)) { require_once 'Zend/Loader.php'; Zend_Loader::loadClass($rowsetClass); } return new $rowsetClass(array('table' => $this, 'rowClass' => $this->getRowClass(), 'stored' => true)); } return $this->fetchAll($whereClause); } /** * Fetches all rows. * * Honors the Zend_Db_Adapter fetch mode. * * @param string|array|Zend_Db_Table_Select $where OPTIONAL An SQL WHERE clause or Zend_Db_Table_Select object. * @param string|array $order OPTIONAL An SQL ORDER clause. * @param int $count OPTIONAL An SQL LIMIT count. * @param int $offset OPTIONAL An SQL LIMIT offset. * @return Zend_Db_Table_Rowset_Abstract The row results per the Zend_Db_Adapter fetch mode. */ public function fetchAll($where = null, $order = null, $count = null, $offset = null) { if (!($where instanceof Zend_Db_Table_Select)) { $select = $this->select(); if ($where !== null) { $this->_where($select, $where); } if ($order !== null) { $this->_order($select, $order); } if ($count !== null || $offset !== null) { $select->limit($count, $offset); } } else { $select = $where; } $rows = $this->_fetch($select); $data = array( 'table' => $this, 'data' => $rows, 'readOnly' => $select->isReadOnly(), 'rowClass' => $this->getRowClass(), 'stored' => true ); $rowsetClass = $this->getRowsetClass(); if (!class_exists($rowsetClass)) { require_once 'Zend/Loader.php'; Zend_Loader::loadClass($rowsetClass); } return new $rowsetClass($data); } /** * Fetches one row in an object of type Zend_Db_Table_Row_Abstract, * or returns null if no row matches the specified criteria. * * @param string|array|Zend_Db_Table_Select $where OPTIONAL An SQL WHERE clause or Zend_Db_Table_Select object. * @param string|array $order OPTIONAL An SQL ORDER clause. * @param int $offset OPTIONAL An SQL OFFSET value. * @return Zend_Db_Table_Row_Abstract|null The row results per the * Zend_Db_Adapter fetch mode, or null if no row found. */ public function fetchRow($where = null, $order = null, $offset = null) { if (!($where instanceof Zend_Db_Table_Select)) { $select = $this->select(); if ($where !== null) { $this->_where($select, $where); } if ($order !== null) { $this->_order($select, $order); } $select->limit(1, ((is_numeric($offset)) ? (int) $offset : null)); } else { $select = $where->limit(1, $where->getPart(Zend_Db_Select::LIMIT_OFFSET)); } $rows = $this->_fetch($select); if (count($rows) == 0) { return null; } $data = array( 'table' => $this, 'data' => $rows[0], 'readOnly' => $select->isReadOnly(), 'stored' => true ); $rowClass = $this->getRowClass(); if (!class_exists($rowClass)) { require_once 'Zend/Loader.php'; Zend_Loader::loadClass($rowClass); } return new $rowClass($data); } /** * Fetches a new blank row (not from the database). * * @return Zend_Db_Table_Row_Abstract * @deprecated since 0.9.3 - use createRow() instead. */ public function fetchNew() { return $this->createRow(); } /** * Fetches a new blank row (not from the database). * * @param array $data OPTIONAL data to populate in the new row. * @param string $defaultSource OPTIONAL flag to force default values into new row * @return Zend_Db_Table_Row_Abstract */ public function createRow(array $data = array(), $defaultSource = null) { $cols = $this->_getCols(); $defaults = array_combine($cols, array_fill(0, count($cols), null)); // nothing provided at call-time, take the class value if ($defaultSource == null) { $defaultSource = $this->_defaultSource; } if (!in_array($defaultSource, array(self::DEFAULT_CLASS, self::DEFAULT_DB, self::DEFAULT_NONE))) { $defaultSource = self::DEFAULT_NONE; } if ($defaultSource == self::DEFAULT_DB) { foreach ($this->_metadata as $metadataName => $metadata) { if (($metadata['DEFAULT'] != null) && ($metadata['NULLABLE'] !== true || ($metadata['NULLABLE'] === true && isset($this->_defaultValues[$metadataName]) && $this->_defaultValues[$metadataName] === true)) && (!(isset($this->_defaultValues[$metadataName]) && $this->_defaultValues[$metadataName] === false))) { $defaults[$metadataName] = $metadata['DEFAULT']; } } } elseif ($defaultSource == self::DEFAULT_CLASS && $this->_defaultValues) { foreach ($this->_defaultValues as $defaultName => $defaultValue) { if (array_key_exists($defaultName, $defaults)) { $defaults[$defaultName] = $defaultValue; } } } $config = array( 'table' => $this, 'data' => $defaults, 'readOnly' => false, 'stored' => false ); $rowClass = $this->getRowClass(); if (!class_exists($rowClass)) { require_once 'Zend/Loader.php'; Zend_Loader::loadClass($rowClass); } $row = new $rowClass($config); $row->setFromArray($data); return $row; } /** * Generate WHERE clause from user-supplied string or array * * @param string|array $where OPTIONAL An SQL WHERE clause. * @return Zend_Db_Table_Select */ protected function _where(Zend_Db_Table_Select $select, $where) { $where = (array) $where; foreach ($where as $key => $val) { // is $key an int? if (is_int($key)) { // $val is the full condition $select->where($val); } else { // $key is the condition with placeholder, // and $val is quoted into the condition $select->where($key, $val); } } return $select; } /** * Generate ORDER clause from user-supplied string or array * * @param string|array $order OPTIONAL An SQL ORDER clause. * @return Zend_Db_Table_Select */ protected function _order(Zend_Db_Table_Select $select, $order) { if (!is_array($order)) { $order = array($order); } foreach ($order as $val) { $select->order($val); } return $select; } /** * Support method for fetching rows. * * @param Zend_Db_Table_Select $select query options. * @return array An array containing the row results in FETCH_ASSOC mode. */ protected function _fetch(Zend_Db_Table_Select $select) { $stmt = $this->_db->query($select); $data = $stmt->fetchAll(Zend_Db::FETCH_ASSOC); return $data; } public static function getTableFromString($tableName, Zend_Db_Table_Abstract $referenceTable = null) { if ($referenceTable instanceof Zend_Db_Table_Abstract) { $tableDefinition = $referenceTable->getDefinition(); if ($tableDefinition !== null && $tableDefinition->hasTableConfig($tableName)) { return new Zend_Db_Table($tableName, $tableDefinition); } } // assume the tableName is the class name if (!class_exists($tableName)) { try { require_once 'Zend/Loader.php'; Zend_Loader::loadClass($tableName); } catch (Zend_Exception $e) { require_once 'Zend/Db/Table/Row/Exception.php'; throw new Zend_Db_Table_Row_Exception($e->getMessage(), $e->getCode(), $e); } } $options = array(); if ($referenceTable instanceof Zend_Db_Table_Abstract) { $options['db'] = $referenceTable->getAdapter(); } if (isset($tableDefinition) && $tableDefinition !== null) { $options[Zend_Db_Table_Abstract::DEFINITION] = $tableDefinition; } return new $tableName($options); } }