PageRenderTime 54ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

/library/Zend/Db/Table/Abstract.php

https://bitbucket.org/baruffaldi/webapp-urltube
PHP | 1279 lines | 637 code | 134 blank | 508 comment | 110 complexity | 93f0da027aeb430b0c3645e54262cb44 MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.0, MIT
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_Db
  17. * @subpackage Table
  18. * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
  19. * @license http://framework.zend.com/license/new-bsd New BSD License
  20. * @version $Id: Abstract.php 6320 2007-09-12 00:27:22Z bkarwin $
  21. */
  22. /**
  23. * @see Zend_Db_Adapter_Abstract
  24. */
  25. require_once 'Zend/Db/Adapter/Abstract.php';
  26. /**
  27. * @see Zend_Db_Adapter_Abstract
  28. */
  29. require_once 'Zend/Db/Select.php';
  30. /**
  31. * @see Zend_Db
  32. */
  33. require_once 'Zend/Db.php';
  34. /**
  35. * Class for SQL table interface.
  36. *
  37. * @category Zend
  38. * @package Zend_Db
  39. * @subpackage Table
  40. * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
  41. * @license http://framework.zend.com/license/new-bsd New BSD License
  42. */
  43. abstract class Zend_Db_Table_Abstract
  44. {
  45. const ADAPTER = 'db';
  46. const SCHEMA = 'schema';
  47. const NAME = 'name';
  48. const PRIMARY = 'primary';
  49. const COLS = 'cols';
  50. const METADATA = 'metadata';
  51. const METADATA_CACHE = 'metadataCache';
  52. const ROW_CLASS = 'rowClass';
  53. const ROWSET_CLASS = 'rowsetClass';
  54. const REFERENCE_MAP = 'referenceMap';
  55. const DEPENDENT_TABLES = 'dependentTables';
  56. const SEQUENCE = 'sequence';
  57. const COLUMNS = 'columns';
  58. const REF_TABLE_CLASS = 'refTableClass';
  59. const REF_COLUMNS = 'refColumns';
  60. const ON_DELETE = 'onDelete';
  61. const ON_UPDATE = 'onUpdate';
  62. const CASCADE = 'cascade';
  63. const RESTRICT = 'restrict';
  64. const SET_NULL = 'setNull';
  65. const DEFAULT_NONE = 'defaultNone';
  66. const DEFAULT_CLASS = 'defaultClass';
  67. const DEFAULT_DB = 'defaultDb';
  68. /**
  69. * Default Zend_Db_Adapter_Abstract object.
  70. *
  71. * @var Zend_Db_Adapter_Abstract
  72. */
  73. protected static $_defaultDb;
  74. /**
  75. * Default cache for information provided by the adapter's describeTable() method.
  76. *
  77. * @var Zend_Cache_Core
  78. */
  79. protected static $_defaultMetadataCache = null;
  80. /**
  81. * Zend_Db_Adapter_Abstract object.
  82. *
  83. * @var Zend_Db_Adapter_Abstract
  84. */
  85. protected $_db;
  86. /**
  87. * The schema name (default null means current schema)
  88. *
  89. * @var array
  90. */
  91. protected $_schema = null;
  92. /**
  93. * The table name.
  94. *
  95. * @var array
  96. */
  97. protected $_name = null;
  98. /**
  99. * The table column names derived from Zend_Db_Adapter_Abstract::describeTable().
  100. *
  101. * @var array
  102. */
  103. protected $_cols;
  104. /**
  105. * The primary key column or columns.
  106. * A compound key should be declared as an array.
  107. * You may declare a single-column primary key
  108. * as a string.
  109. *
  110. * @var mixed
  111. */
  112. protected $_primary = null;
  113. /**
  114. * If your primary key is a compound key, and one of the columns uses
  115. * an auto-increment or sequence-generated value, set _identity
  116. * to the ordinal index in the $_primary array for that column.
  117. * Note this index is the position of the column in the primary key,
  118. * not the position of the column in the table. The primary key
  119. * array is 1-based.
  120. *
  121. * @var integer
  122. */
  123. protected $_identity = 1;
  124. /**
  125. * Define the logic for new values in the primary key.
  126. * May be a string, boolean true, or boolean false.
  127. *
  128. * @var mixed
  129. */
  130. protected $_sequence = true;
  131. /**
  132. * Information provided by the adapter's describeTable() method.
  133. *
  134. * @var array
  135. */
  136. protected $_metadata = array();
  137. /**
  138. * Cache for information provided by the adapter's describeTable() method.
  139. *
  140. * @var Zend_Cache_Core
  141. */
  142. protected $_metadataCache = null;
  143. /**
  144. * Classname for row
  145. *
  146. * @var string
  147. */
  148. protected $_rowClass = 'Zend_Db_Table_Row';
  149. /**
  150. * Classname for rowset
  151. *
  152. * @var string
  153. */
  154. protected $_rowsetClass = 'Zend_Db_Table_Rowset';
  155. /**
  156. * Associative array map of declarative referential integrity rules.
  157. * This array has one entry per foreign key in the current table.
  158. * Each key is a mnemonic name for one reference rule.
  159. *
  160. * Each value is also an associative array, with the following keys:
  161. * - columns = array of names of column(s) in the child table.
  162. * - refTableClass = class name of the parent table.
  163. * - refColumns = array of names of column(s) in the parent table,
  164. * in the same order as those in the 'columns' entry.
  165. * - onDelete = "cascade" means that a delete in the parent table also
  166. * causes a delete of referencing rows in the child table.
  167. * - onUpdate = "cascade" means that an update of primary key values in
  168. * the parent table also causes an update of referencing
  169. * rows in the child table.
  170. *
  171. * @var array
  172. */
  173. protected $_referenceMap = array();
  174. /**
  175. * Simple array of class names of tables that are "children" of the current
  176. * table, in other words tables that contain a foreign key to this one.
  177. * Array elements are not table names; they are class names of classes that
  178. * extend Zend_Db_Table_Abstract.
  179. *
  180. * @var array
  181. */
  182. protected $_dependentTables = array();
  183. protected $_defaultSource = self::DEFAULT_NONE;
  184. protected $_defaultValues = array();
  185. /**
  186. * Constructor.
  187. *
  188. * Supported params for $config are:
  189. * - db = user-supplied instance of database connector,
  190. * or key name of registry instance.
  191. * - name = table name.
  192. * - primary = string or array of primary key(s).
  193. * - rowClass = row class name.
  194. * - rowsetClass = rowset class name.
  195. * - referenceMap = array structure to declare relationship
  196. * to parent tables.
  197. * - dependentTables = array of child tables.
  198. * - metadataCache = cache for information from adapter describeTable().
  199. *
  200. * @param mixed $config Array of user-specified config options, or just the Db Adapter.
  201. * @return void
  202. */
  203. public function __construct($config = array())
  204. {
  205. /**
  206. * Allow a scalar argument to be the Adapter object or Registry key.
  207. */
  208. if (!is_array($config)) {
  209. $config = array(self::ADAPTER => $config);
  210. }
  211. foreach ($config as $key => $value) {
  212. switch ($key) {
  213. case self::ADAPTER:
  214. $this->_setAdapter($value);
  215. break;
  216. case self::SCHEMA:
  217. $this->_schema = (string) $value;
  218. break;
  219. case self::NAME:
  220. $this->_name = (string) $value;
  221. break;
  222. case self::PRIMARY:
  223. $this->_primary = (array) $value;
  224. break;
  225. case self::ROW_CLASS:
  226. $this->setRowClass($value);
  227. break;
  228. case self::ROWSET_CLASS:
  229. $this->setRowsetClass($value);
  230. break;
  231. case self::REFERENCE_MAP:
  232. $this->setReferences($value);
  233. break;
  234. case self::DEPENDENT_TABLES:
  235. $this->setDependentTables($value);
  236. break;
  237. case self::METADATA_CACHE:
  238. $this->_setMetadataCache($value);
  239. break;
  240. case self::SEQUENCE:
  241. $this->_setSequence($value);
  242. break;
  243. default:
  244. // ignore unrecognized configuration directive
  245. break;
  246. }
  247. }
  248. $this->_setup();
  249. $this->init();
  250. }
  251. /**
  252. * @param string $classname
  253. * @return Zend_Db_Table_Abstract Provides a fluent interface
  254. */
  255. public function setRowClass($classname)
  256. {
  257. $this->_rowClass = (string) $classname;
  258. return $this;
  259. }
  260. /**
  261. * @return string
  262. */
  263. public function getRowClass()
  264. {
  265. return $this->_rowClass;
  266. }
  267. /**
  268. * @param string $classname
  269. * @return Zend_Db_Table_Abstract Provides a fluent interface
  270. */
  271. public function setRowsetClass($classname)
  272. {
  273. $this->_rowsetClass = (string) $classname;
  274. return $this;
  275. }
  276. /**
  277. * @return string
  278. */
  279. public function getRowsetClass()
  280. {
  281. return $this->_rowsetClass;
  282. }
  283. /**
  284. * @param array $referenceMap
  285. * @return Zend_Db_Table_Abstract Provides a fluent interface
  286. */
  287. public function setReferences(array $referenceMap)
  288. {
  289. $this->_referenceMap = $referenceMap;
  290. return $this;
  291. }
  292. /**
  293. * @param string $tableClassname
  294. * @param string $ruleKey OPTIONAL
  295. * @return array
  296. * @throws Zend_Db_Table_Exception
  297. */
  298. public function getReference($tableClassname, $ruleKey = null)
  299. {
  300. $thisClass = get_class($this);
  301. $refMap = $this->_getReferenceMapNormalized();
  302. if ($ruleKey !== null) {
  303. if (!isset($refMap[$ruleKey])) {
  304. require_once "Zend/Db/Table/Exception.php";
  305. throw new Zend_Db_Table_Exception("No reference rule \"$ruleKey\" from table $thisClass to table $tableClassname");
  306. }
  307. if ($refMap[$ruleKey][self::REF_TABLE_CLASS] != $tableClassname) {
  308. require_once "Zend/Db/Table/Exception.php";
  309. throw new Zend_Db_Table_Exception("Reference rule \"$ruleKey\" does not reference table $tableClassname");
  310. }
  311. return $refMap[$ruleKey];
  312. }
  313. foreach ($refMap as $reference) {
  314. if ($reference[self::REF_TABLE_CLASS] == $tableClassname) {
  315. return $reference;
  316. }
  317. }
  318. require_once "Zend/Db/Table/Exception.php";
  319. throw new Zend_Db_Table_Exception("No reference from table $thisClass to table $tableClassname");
  320. }
  321. /**
  322. * @param array $dependentTables
  323. * @return Zend_Db_Table_Abstract Provides a fluent interface
  324. */
  325. public function setDependentTables(array $dependentTables)
  326. {
  327. $this->_dependentTables = $dependentTables;
  328. return $this;
  329. }
  330. /**
  331. * @return array
  332. */
  333. public function getDependentTables()
  334. {
  335. return $this->_dependentTables;
  336. }
  337. /**
  338. * set the defaultSource property - this tells the table class where to find default values
  339. *
  340. * @param string $defaultSource
  341. * @return Zend_Db_Table_Abstract
  342. */
  343. public function setDefaultSource($defaultSource = self::DEFAULT_NONE)
  344. {
  345. if (!in_array($defaultSource, array(self::DEFAULT_CLASS, self::DEFAULT_DB, self::DEFAULT_NONE))) {
  346. $defaultSource = self::DEFAULT_NONE;
  347. }
  348. $this->_defaultSource = $defaultSource;
  349. return $this;
  350. }
  351. /**
  352. * returns the default source flag that determines where defaultSources come from
  353. *
  354. * @return unknown
  355. */
  356. public function getDefaultSource()
  357. {
  358. return $this->_defaultSource;
  359. }
  360. /**
  361. * set the default values for the table class
  362. *
  363. * @param array $defaultValues
  364. * @return Zend_Db_Table_Abstract
  365. */
  366. public function setDefaultValues(Array $defaultValues)
  367. {
  368. foreach ($defaultValues as $defaultName => $defaultValue) {
  369. if (array_key_exists($defaultName, $this->_metadata)) {
  370. $this->_defaultValues[$defaultName] = $defaultValue;
  371. }
  372. }
  373. return $this;
  374. }
  375. public function getDefaultValues()
  376. {
  377. return $this->_defaultValues;
  378. }
  379. /**
  380. * Sets the default Zend_Db_Adapter_Abstract for all Zend_Db_Table objects.
  381. *
  382. * @param mixed $db Either an Adapter object, or a string naming a Registry key
  383. * @return void
  384. */
  385. public static final function setDefaultAdapter($db = null)
  386. {
  387. self::$_defaultDb = self::_setupAdapter($db);
  388. }
  389. /**
  390. * Gets the default Zend_Db_Adapter_Abstract for all Zend_Db_Table objects.
  391. *
  392. * @return Zend_Db_Adapter_Abstract or null
  393. */
  394. public static final function getDefaultAdapter()
  395. {
  396. return self::$_defaultDb;
  397. }
  398. /**
  399. * @param mixed $db Either an Adapter object, or a string naming a Registry key
  400. * @return Zend_Db_Table_Abstract Provides a fluent interface
  401. */
  402. protected final function _setAdapter($db)
  403. {
  404. $this->_db = self::_setupAdapter($db);
  405. return $this;
  406. }
  407. /**
  408. * Gets the Zend_Db_Adapter_Abstract for this particular Zend_Db_Table object.
  409. *
  410. * @return Zend_Db_Adapter_Abstract
  411. */
  412. public final function getAdapter()
  413. {
  414. return $this->_db;
  415. }
  416. /**
  417. * @param mixed $db Either an Adapter object, or a string naming a Registry key
  418. * @return Zend_Db_Adapter_Abstract
  419. * @throws Zend_Db_Table_Exception
  420. */
  421. protected static final function _setupAdapter($db)
  422. {
  423. if ($db === null) {
  424. return null;
  425. }
  426. if (is_string($db)) {
  427. require_once 'Zend/Registry.php';
  428. $db = Zend_Registry::get($db);
  429. }
  430. if (!$db instanceof Zend_Db_Adapter_Abstract) {
  431. require_once 'Zend/Db/Table/Exception.php';
  432. 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');
  433. }
  434. return $db;
  435. }
  436. /**
  437. * Sets the default metadata cache for information returned by Zend_Db_Adapter_Abstract::describeTable().
  438. *
  439. * If $defaultMetadataCache is null, then no metadata cache is used by default.
  440. *
  441. * @param mixed $metadataCache Either a Cache object, or a string naming a Registry key
  442. * @return void
  443. */
  444. public static function setDefaultMetadataCache($metadataCache = null)
  445. {
  446. self::$_defaultMetadataCache = self::_setupMetadataCache($metadataCache);
  447. }
  448. /**
  449. * Gets the default metadata cache for information returned by Zend_Db_Adapter_Abstract::describeTable().
  450. *
  451. * @return Zend_Cache_Core or null
  452. */
  453. public static function getDefaultMetadataCache()
  454. {
  455. return self::$_defaultMetadataCache;
  456. }
  457. /**
  458. * Sets the metadata cache for information returned by Zend_Db_Adapter_Abstract::describeTable().
  459. *
  460. * If $metadataCache is null, then no metadata cache is used. Since there is no opportunity to reload metadata
  461. * after instantiation, this method need not be public, particularly because that it would have no effect
  462. * results in unnecessary API complexity. To configure the metadata cache, use the metadataCache configuration
  463. * option for the class constructor upon instantiation.
  464. *
  465. * @param mixed $metadataCache Either a Cache object, or a string naming a Registry key
  466. * @return Zend_Db_Table_Abstract Provides a fluent interface
  467. */
  468. protected function _setMetadataCache($metadataCache)
  469. {
  470. $this->_metadataCache = self::_setupMetadataCache($metadataCache);
  471. return $this;
  472. }
  473. /**
  474. * Gets the metadata cache for information returned by Zend_Db_Adapter_Abstract::describeTable().
  475. *
  476. * @return Zend_Cache_Core or null
  477. */
  478. public function getMetadataCache()
  479. {
  480. return $this->_metadataCache;
  481. }
  482. /**
  483. * @param mixed $metadataCache Either a Cache object, or a string naming a Registry key
  484. * @return Zend_Cache_Core
  485. * @throws Zend_Db_Table_Exception
  486. */
  487. protected static final function _setupMetadataCache($metadataCache)
  488. {
  489. if ($metadataCache === null) {
  490. return null;
  491. }
  492. if (is_string($metadataCache)) {
  493. require_once 'Zend/Registry.php';
  494. $metadataCache = Zend_Registry::get($metadataCache);
  495. }
  496. if (!$metadataCache instanceof Zend_Cache_Core) {
  497. require_once 'Zend/Db/Table/Exception.php';
  498. 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');
  499. }
  500. return $metadataCache;
  501. }
  502. /**
  503. * Sets the sequence member, which defines the behavior for generating
  504. * primary key values in new rows.
  505. * - If this is a string, then the string names the sequence object.
  506. * - If this is boolean true, then the key uses an auto-incrementing
  507. * or identity mechanism.
  508. * - If this is boolean false, then the key is user-defined.
  509. * Use this for natural keys, for example.
  510. *
  511. * @param mixed $sequence
  512. * @return Zend_Db_Table_Adapter_Abstract Provides a fluent interface
  513. */
  514. protected function _setSequence($sequence)
  515. {
  516. $this->_sequence = $sequence;
  517. return $this;
  518. }
  519. /**
  520. * Turnkey for initialization of a table object.
  521. * Calls other protected methods for individual tasks, to make it easier
  522. * for a subclass to override part of the setup logic.
  523. *
  524. * @return void
  525. */
  526. protected function _setup()
  527. {
  528. $this->_setupDatabaseAdapter();
  529. $this->_setupTableName();
  530. $this->_setupMetadata();
  531. $this->_setupPrimaryKey();
  532. }
  533. /**
  534. * Initialize database adapter.
  535. *
  536. * @return void
  537. */
  538. protected function _setupDatabaseAdapter()
  539. {
  540. if (! $this->_db) {
  541. $this->_db = self::getDefaultAdapter();
  542. if (!$this->_db instanceof Zend_Db_Adapter_Abstract) {
  543. require_once 'Zend/Db/Table/Exception.php';
  544. throw new Zend_Db_Table_Exception('No adapter found for ' . get_class($this));
  545. }
  546. }
  547. }
  548. /**
  549. * Initialize table and schema names.
  550. *
  551. * If the table name is not set in the class definition,
  552. * use the class name itself as the table name.
  553. *
  554. * A schema name provided with the table name (e.g., "schema.table") overrides
  555. * any existing value for $this->_schema.
  556. *
  557. * @return void
  558. */
  559. protected function _setupTableName()
  560. {
  561. if (! $this->_name) {
  562. $this->_name = get_class($this);
  563. } else if (strpos($this->_name, '.')) {
  564. list($this->_schema, $this->_name) = explode('.', $this->_name);
  565. }
  566. }
  567. /**
  568. * Initializes metadata.
  569. *
  570. * If metadata cannot be loaded from cache, adapter's describeTable() method is called to discover metadata
  571. * information. Returns true if and only if the metadata are loaded from cache.
  572. *
  573. * @return boolean
  574. * @throws Zend_Db_Table_Exception
  575. */
  576. protected function _setupMetadata()
  577. {
  578. // Assume that metadata will be loaded from cache
  579. $isMetadataFromCache = true;
  580. // If $this has no metadata cache but the class has a default metadata cache
  581. if (null === $this->_metadataCache && null !== self::$_defaultMetadataCache) {
  582. // Make $this use the default metadata cache of the class
  583. $this->_setMetadataCache(self::$_defaultMetadataCache);
  584. }
  585. // If $this has a metadata cache
  586. if (null !== $this->_metadataCache) {
  587. // Define the cache identifier where the metadata are saved
  588. $cacheId = md5("$this->_schema.$this->_name");
  589. }
  590. // If $this has no metadata cache or metadata cache misses
  591. if (null === $this->_metadataCache || !($metadata = $this->_metadataCache->load($cacheId))) {
  592. // Metadata are not loaded from cache
  593. $isMetadataFromCache = false;
  594. // Fetch metadata from the adapter's describeTable() method
  595. $metadata = $this->_db->describeTable($this->_name, $this->_schema);
  596. // If $this has a metadata cache, then cache the metadata
  597. if (null !== $this->_metadataCache && !$this->_metadataCache->save($metadata, $cacheId)) {
  598. /**
  599. * @see Zend_Db_Table_Exception
  600. */
  601. require_once 'Zend/Db/Table/Exception.php';
  602. throw new Zend_Db_Table_Exception('Failed saving metadata to metadataCache');
  603. }
  604. }
  605. // Assign the metadata to $this
  606. $this->_metadata = $metadata;
  607. // Update the columns
  608. $this->_cols = array_keys($this->_metadata);
  609. // Return whether the metadata were loaded from cache
  610. return $isMetadataFromCache;
  611. }
  612. /**
  613. * Initialize primary key from metadata.
  614. * If $_primary is not defined, discover primary keys
  615. * from the information returned by describeTable().
  616. *
  617. * @return void
  618. * @throws Zend_Db_Table_Exception
  619. */
  620. protected function _setupPrimaryKey()
  621. {
  622. if (!$this->_primary) {
  623. $this->_primary = array();
  624. foreach ($this->_metadata as $col) {
  625. if ($col['PRIMARY']) {
  626. $this->_primary[ $col['PRIMARY_POSITION'] ] = $col['COLUMN_NAME'];
  627. if ($col['IDENTITY']) {
  628. $this->_identity = $col['PRIMARY_POSITION'];
  629. }
  630. }
  631. }
  632. // if no primary key was specified and none was found in the metadata
  633. // then throw an exception.
  634. if (empty($this->_primary)) {
  635. require_once 'Zend/Db/Table/Exception.php';
  636. throw new Zend_Db_Table_Exception('A table must have a primary key, but none was found');
  637. }
  638. } else if (!is_array($this->_primary)) {
  639. $this->_primary = array(1 => $this->_primary);
  640. } else if (isset($this->_primary[0])) {
  641. array_unshift($this->_primary, null);
  642. unset($this->_primary[0]);
  643. }
  644. if (! array_intersect((array) $this->_primary, $this->_cols) == (array) $this->_primary) {
  645. require_once 'Zend/Db/Table/Exception.php';
  646. throw new Zend_Db_Table_Exception("Primary key column(s) ("
  647. . implode(',', (array) $this->_primary)
  648. . ") are not columns in this table ("
  649. . implode(',', $this->_cols)
  650. . ")");
  651. }
  652. $primary = (array) $this->_primary;
  653. $pkIdentity = $primary[(int) $this->_identity];
  654. /**
  655. * Special case for PostgreSQL: a SERIAL key implicitly uses a sequence
  656. * object whose name is "<table>_<column>_seq".
  657. */
  658. if ($this->_sequence === true && $this->_db instanceof Zend_Db_Adapter_Pdo_Pgsql) {
  659. $this->_sequence = "{$this->_name}_{$pkIdentity}_seq";
  660. if ($this->_schema) {
  661. $this->_sequence = $this->_schema . '.' . $this->_sequence;
  662. }
  663. }
  664. }
  665. /**
  666. * Returns a normalized version of the reference map
  667. *
  668. * @return array
  669. */
  670. protected function _getReferenceMapNormalized()
  671. {
  672. $referenceMapNormalized = array();
  673. foreach ($this->_referenceMap as $rule => $map) {
  674. $referenceMapNormalized[$rule] = array();
  675. foreach ($map as $key => $value) {
  676. switch ($key) {
  677. // normalize COLUMNS and REF_COLUMNS to arrays
  678. case self::COLUMNS:
  679. case self::REF_COLUMNS:
  680. if (!is_array($value)) {
  681. $referenceMapNormalized[$rule][$key] = array($value);
  682. } else {
  683. $referenceMapNormalized[$rule][$key] = $value;
  684. }
  685. break;
  686. // other values are copied as-is
  687. default:
  688. $referenceMapNormalized[$rule][$key] = $value;
  689. break;
  690. }
  691. }
  692. }
  693. return $referenceMapNormalized;
  694. }
  695. /**
  696. * Initialize object
  697. *
  698. * Called from {@link __construct()} as final step of object instantiation.
  699. *
  700. * @return void
  701. */
  702. public function init()
  703. {
  704. }
  705. /**
  706. * Returns table information.
  707. *
  708. * You can elect to return only a part of this information by supplying its key name,
  709. * otherwise all information is returned as an array.
  710. *
  711. * @param $key The specific info part to return OPTIONAL
  712. * @return mixed
  713. */
  714. public function info($key = null)
  715. {
  716. $info = array(
  717. self::SCHEMA => $this->_schema,
  718. self::NAME => $this->_name,
  719. self::COLS => (array) $this->_cols,
  720. self::PRIMARY => (array) $this->_primary,
  721. self::METADATA => $this->_metadata,
  722. self::ROW_CLASS => $this->_rowClass,
  723. self::ROWSET_CLASS => $this->_rowsetClass,
  724. self::REFERENCE_MAP => $this->_referenceMap,
  725. self::DEPENDENT_TABLES => $this->_dependentTables,
  726. self::SEQUENCE => $this->_sequence
  727. );
  728. if ($key === null) {
  729. return $info;
  730. }
  731. if (!array_key_exists($key, $info)) {
  732. require_once 'Zend/Db/Table/Exception.php';
  733. throw new Zend_Db_Table_Exception('There is no table information for the key "' . $key . '"');
  734. }
  735. return $info[$key];
  736. }
  737. /**
  738. * Returns an instance of a Zend_Db_Table_Select object.
  739. *
  740. * @return Zend_Db_Table_Select
  741. */
  742. public function select()
  743. {
  744. require_once 'Zend/Db/Table/Select.php';
  745. return new Zend_Db_Table_Select($this);
  746. }
  747. /**
  748. * Inserts a new row.
  749. *
  750. * @param array $data Column-value pairs.
  751. * @return mixed The primary key of the row inserted.
  752. */
  753. public function insert(array $data)
  754. {
  755. /**
  756. * Zend_Db_Table assumes that if you have a compound primary key
  757. * and one of the columns in the key uses a sequence,
  758. * it's the _first_ column in the compound key.
  759. */
  760. $primary = (array) $this->_primary;
  761. $pkIdentity = $primary[(int)$this->_identity];
  762. /**
  763. * If this table uses a database sequence object and the data does not
  764. * specify a value, then get the next ID from the sequence and add it
  765. * to the row. We assume that only the first column in a compound
  766. * primary key takes a value from a sequence.
  767. */
  768. if (is_string($this->_sequence) && !isset($data[$pkIdentity])) {
  769. $data[$pkIdentity] = $this->_db->nextSequenceId($this->_sequence);
  770. }
  771. /**
  772. * If the primary key can be generated automatically, and no value was
  773. * specified in the user-supplied data, then omit it from the tuple.
  774. */
  775. if (array_key_exists($pkIdentity, $data) && $data[$pkIdentity] === null) {
  776. unset($data[$pkIdentity]);
  777. }
  778. /**
  779. * INSERT the new row.
  780. */
  781. $tableSpec = ($this->_schema ? $this->_schema . '.' : '') . $this->_name;
  782. $this->_db->insert($tableSpec, $data);
  783. /**
  784. * Fetch the most recent ID generated by an auto-increment
  785. * or IDENTITY column, unless the user has specified a value,
  786. * overriding the auto-increment mechanism.
  787. */
  788. if ($this->_sequence === true && !isset($data[$pkIdentity])) {
  789. $data[$pkIdentity] = $this->_db->lastInsertId();
  790. }
  791. /**
  792. * Return the primary key value if the PK is a single column,
  793. * else return an associative array of the PK column/value pairs.
  794. */
  795. $pkData = array_intersect_key($data, array_flip($primary));
  796. if (count($primary) == 1) {
  797. reset($pkData);
  798. return current($pkData);
  799. }
  800. return $pkData;
  801. }
  802. /**
  803. * Updates existing rows.
  804. *
  805. * @param array $data Column-value pairs.
  806. * @param array|string $where An SQL WHERE clause, or an array of SQL WHERE clauses.
  807. * @return int The number of rows updated.
  808. */
  809. public function update(array $data, $where)
  810. {
  811. $tableSpec = ($this->_schema ? $this->_schema . '.' : '') . $this->_name;
  812. return $this->_db->update($tableSpec, $data, $where);
  813. }
  814. /**
  815. * Called by a row object for the parent table's class during save() method.
  816. *
  817. * @param string $parentTableClassname
  818. * @param array $oldPrimaryKey
  819. * @param array $newPrimaryKey
  820. * @return int
  821. */
  822. public function _cascadeUpdate($parentTableClassname, array $oldPrimaryKey, array $newPrimaryKey)
  823. {
  824. $rowsAffected = 0;
  825. foreach ($this->_getReferenceMapNormalized() as $map) {
  826. if ($map[self::REF_TABLE_CLASS] == $parentTableClassname && isset($map[self::ON_UPDATE])) {
  827. switch ($map[self::ON_UPDATE]) {
  828. case self::CASCADE:
  829. $newRefs = array();
  830. $where = array();
  831. for ($i = 0; $i < count($map[self::COLUMNS]); ++$i) {
  832. $col = $this->_db->foldCase($map[self::COLUMNS][$i]);
  833. $refCol = $this->_db->foldCase($map[self::REF_COLUMNS][$i]);
  834. if (array_key_exists($refCol, $newPrimaryKey)) {
  835. $newRefs[$col] = $newPrimaryKey[$refCol];
  836. }
  837. $type = $this->_metadata[$col]['DATA_TYPE'];
  838. $where[] = $this->_db->quoteInto(
  839. $this->_db->quoteIdentifier($col, true) . ' = ?',
  840. $oldPrimaryKey[$refCol], $type);
  841. }
  842. $rowsAffected += $this->update($newRefs, $where);
  843. break;
  844. default:
  845. // no action
  846. break;
  847. }
  848. }
  849. }
  850. return $rowsAffected;
  851. }
  852. /**
  853. * Deletes existing rows.
  854. *
  855. * @param array|string $where SQL WHERE clause(s).
  856. * @return int The number of rows deleted.
  857. */
  858. public function delete($where)
  859. {
  860. $tableSpec = ($this->_schema ? $this->_schema . '.' : '') . $this->_name;
  861. return $this->_db->delete($tableSpec, $where);
  862. }
  863. /**
  864. * Called by parent table's class during delete() method.
  865. *
  866. * @param string $parentTableClassname
  867. * @param array $primaryKey
  868. * @return int Number of affected rows
  869. */
  870. public function _cascadeDelete($parentTableClassname, array $primaryKey)
  871. {
  872. $rowsAffected = 0;
  873. foreach ($this->_getReferenceMapNormalized() as $map) {
  874. if ($map[self::REF_TABLE_CLASS] == $parentTableClassname && isset($map[self::ON_DELETE])) {
  875. switch ($map[self::ON_DELETE]) {
  876. case self::CASCADE:
  877. $where = array();
  878. for ($i = 0; $i < count($map[self::COLUMNS]); ++$i) {
  879. $col = $this->_db->foldCase($map[self::COLUMNS][$i]);
  880. $refCol = $this->_db->foldCase($map[self::REF_COLUMNS][$i]);
  881. $type = $this->_metadata[$col]['DATA_TYPE'];
  882. $where[] = $this->_db->quoteInto(
  883. $this->_db->quoteIdentifier($col, true) . ' = ?',
  884. $primaryKey[$refCol], $type);
  885. }
  886. $rowsAffected += $this->delete($where);
  887. break;
  888. default:
  889. // no action
  890. break;
  891. }
  892. }
  893. }
  894. return $rowsAffected;
  895. }
  896. /**
  897. * Fetches rows by primary key. The argument specifies one or more primary
  898. * key value(s). To find multiple rows by primary key, the argument must
  899. * be an array.
  900. *
  901. * This method accepts a variable number of arguments. If the table has a
  902. * multi-column primary key, the number of arguments must be the same as
  903. * the number of columns in the primary key. To find multiple rows in a
  904. * table with a multi-column primary key, each argument must be an array
  905. * with the same number of elements.
  906. *
  907. * The find() method always returns a Rowset object, even if only one row
  908. * was found.
  909. *
  910. * @param mixed $key The value(s) of the primary keys.
  911. * @return Zend_Db_Table_Rowset_Abstract Row(s) matching the criteria.
  912. * @throws Zend_Db_Table_Exception
  913. */
  914. public function find()
  915. {
  916. $args = func_get_args();
  917. $keyNames = array_values((array) $this->_primary);
  918. if (count($args) < count($keyNames)) {
  919. require_once 'Zend/Db/Table/Exception.php';
  920. throw new Zend_Db_Table_Exception("Too few columns for the primary key");
  921. }
  922. if (count($args) > count($keyNames)) {
  923. require_once 'Zend/Db/Table/Exception.php';
  924. throw new Zend_Db_Table_Exception("Too many columns for the primary key");
  925. }
  926. $whereList = array();
  927. $numberTerms = 0;
  928. foreach ($args as $keyPosition => $keyValues) {
  929. // Coerce the values to an array.
  930. // Don't simply typecast to array, because the values
  931. // might be Zend_Db_Expr objects.
  932. if (!is_array($keyValues)) {
  933. $keyValues = array($keyValues);
  934. }
  935. if ($numberTerms == 0) {
  936. $numberTerms = count($keyValues);
  937. } else if (count($keyValues) != $numberTerms) {
  938. require_once 'Zend/Db/Table/Exception.php';
  939. throw new Zend_Db_Table_Exception("Missing value(s) for the primary key");
  940. }
  941. for ($i = 0; $i < count($keyValues); ++$i) {
  942. if (!isset($whereList[$i])) {
  943. $whereList[$i] = array();
  944. }
  945. $whereList[$i][$keyPosition] = $keyValues[$i];
  946. }
  947. }
  948. $whereClause = null;
  949. if (count($whereList)) {
  950. $whereOrTerms = array();
  951. foreach ($whereList as $keyValueSets) {
  952. $whereAndTerms = array();
  953. foreach ($keyValueSets as $keyPosition => $keyValue) {
  954. $type = $this->_metadata[$keyNames[$keyPosition]]['DATA_TYPE'];
  955. $tableName = $this->_db->quoteTableAs($this->_name, null, true);
  956. $columnName = $this->_db->quoteIdentifier($keyNames[$keyPosition], true);
  957. $whereAndTerms[] = $this->_db->quoteInto(
  958. $tableName . '.' . $columnName . ' = ?',
  959. $keyValue, $type);
  960. }
  961. $whereOrTerms[] = '(' . implode(' AND ', $whereAndTerms) . ')';
  962. }
  963. $whereClause = '(' . implode(' OR ', $whereOrTerms) . ')';
  964. }
  965. return $this->fetchAll($whereClause);
  966. }
  967. /**
  968. * Fetches all rows.
  969. *
  970. * Honors the Zend_Db_Adapter fetch mode.
  971. *
  972. * @param string|array|Zend_Db_Table_Select $where OPTIONAL An SQL WHERE clause or Zend_Db_Table_Select object.
  973. * @param string|array $order OPTIONAL An SQL ORDER clause.
  974. * @param int $count OPTIONAL An SQL LIMIT count.
  975. * @param int $offset OPTIONAL An SQL LIMIT offset.
  976. * @return Zend_Db_Table_Rowset_Abstract The row results per the Zend_Db_Adapter fetch mode.
  977. */
  978. public function fetchAll($where = null, $order = null, $count = null, $offset = null)
  979. {
  980. if (!($where instanceof Zend_Db_Table_Select)) {
  981. $select = $this->select();
  982. if ($where !== null) {
  983. $this->_where($select, $where);
  984. }
  985. if ($order !== null) {
  986. $this->_order($select, $order);
  987. }
  988. if ($count !== null || $offset !== null) {
  989. $select->limit($count, $offset);
  990. }
  991. } else {
  992. $select = $where;
  993. }
  994. $rows = $this->_fetch($select);
  995. $data = array(
  996. 'table' => $this,
  997. 'data' => $rows,
  998. 'readOnly' => $select->isReadOnly(),
  999. 'rowClass' => $this->_rowClass,
  1000. 'stored' => true
  1001. );
  1002. @Zend_Loader::loadClass($this->_rowsetClass);
  1003. return new $this->_rowsetClass($data);
  1004. }
  1005. /**
  1006. * Fetches one row in an object of type Zend_Db_Table_Row_Abstract,
  1007. * or returns Boolean false if no row matches the specified criteria.
  1008. *
  1009. * @param string|array|Zend_Db_Table_Select $where OPTIONAL An SQL WHERE clause or Zend_Db_Table_Select object.
  1010. * @param string|array $order OPTIONAL An SQL ORDER clause.
  1011. * @return Zend_Db_Table_Row_Abstract The row results per the
  1012. * Zend_Db_Adapter fetch mode, or null if no row found.
  1013. */
  1014. public function fetchRow($where = null, $order = null)
  1015. {
  1016. if (!($where instanceof Zend_Db_Table_Select)) {
  1017. $select = $this->select();
  1018. if ($where !== null) {
  1019. $this->_where($select, $where);
  1020. }
  1021. if ($order !== null) {
  1022. $this->_order($select, $order);
  1023. }
  1024. $select->limit(1);
  1025. } else {
  1026. $select = $where->limit(1);
  1027. }
  1028. $rows = $this->_fetch($select);
  1029. if (count($rows) == 0) {
  1030. return null;
  1031. }
  1032. $data = array(
  1033. 'table' => $this,
  1034. 'data' => $rows[0],
  1035. 'readOnly' => $select->isReadOnly(),
  1036. 'stored' => true
  1037. );
  1038. @Zend_Loader::loadClass($this->_rowClass);
  1039. return new $this->_rowClass($data);
  1040. }
  1041. /**
  1042. * Fetches a new blank row (not from the database).
  1043. *
  1044. * @return Zend_Db_Table_Row_Abstract
  1045. * @deprecated since 0.9.3 - use createRow() instead.
  1046. */
  1047. public function fetchNew()
  1048. {
  1049. return $this->createRow();
  1050. }
  1051. /**
  1052. * Fetches a new blank row (not from the database).
  1053. *
  1054. * @param array $data OPTIONAL data to populate in the new row.
  1055. * @param string $defaultSource OPTIONAL flag to force default values into new row
  1056. * @return Zend_Db_Table_Row_Abstract
  1057. */
  1058. public function createRow(array $data = array(), $defaultSource = null)
  1059. {
  1060. $defaults = array_combine($this->_cols, array_fill(0, count($this->_cols), null));
  1061. // nothing provided at call-time, take the class value
  1062. if ($defaultSource == null) {
  1063. $defaultSource = $this->_defaultSource;
  1064. }
  1065. if (!in_array($defaultSource, array(self::DEFAULT_CLASS, self::DEFAULT_DB, self::DEFAULT_NONE))) {
  1066. $defaultSource = self::DEFAULT_NONE;
  1067. }
  1068. if ($defaultSource == self::DEFAULT_DB) {
  1069. foreach ($this->_metadata as $metadataName => $metadata) {
  1070. if (($metadata['DEFAULT'] != null) &&
  1071. ($metadata['NULLABLE'] !== true || ($metadata['NULLABLE'] === true && isset($this->_defaultValues[$metadataName]) && $this->_defaultValues[$metadataName] === true)) &&
  1072. (!(isset($this->_defaultValues[$metadataName]) && $this->_defaultValues[$metadataName] === false))) {
  1073. $defaults[$metadataName] = $metadata['DEFAULT'];
  1074. }
  1075. }
  1076. } elseif ($defaultSource == self::DEFAULT_CLASS && $this->_defaultValues) {
  1077. foreach ($this->_defaultValues as $defaultName => $defaultValue) {
  1078. if (array_key_exists($defaultName, $defaults)) {
  1079. $defaults[$defaultName] = $defaultValue;
  1080. }
  1081. }
  1082. }
  1083. $data = array_intersect_key($data, $defaults);
  1084. $config = array(
  1085. 'table' => $this,
  1086. 'data' => $defaults,
  1087. 'readOnly' => false,
  1088. 'stored' => false
  1089. );
  1090. @Zend_Loader::loadClass($this->_rowClass);
  1091. $row = new $this->_rowClass($config);
  1092. $row->setFromArray($data);
  1093. return $row;
  1094. }
  1095. /**
  1096. * Generate WHERE clause from user-supplied string or array
  1097. *
  1098. * @param string|array $where OPTIONAL An SQL WHERE clause.
  1099. * @return Zend_Db_Table_Select
  1100. */
  1101. protected function _where(Zend_Db_Table_Select $select, $where)
  1102. {
  1103. $where = (array) $where;
  1104. foreach ($where as $key => $val) {
  1105. // is $key an int?
  1106. if (is_int($key)) {
  1107. // $val is the full condition
  1108. $select->where($val);
  1109. } else {
  1110. // $key is the condition with placeholder,
  1111. // and $val is quoted into the condition
  1112. $select->where($key, $val);
  1113. }
  1114. }
  1115. return $select;
  1116. }
  1117. /**
  1118. * Generate ORDER clause from user-supplied string or array
  1119. *
  1120. * @param string|array $order OPTIONAL An SQL ORDER clause.
  1121. * @return Zend_Db_Table_Select
  1122. */
  1123. protected function _order(Zend_Db_Table_Select $select, $order)
  1124. {
  1125. if (!is_array($order)) {
  1126. $order = array($order);
  1127. }
  1128. foreach ($order as $val) {
  1129. $select->order($val);
  1130. }
  1131. return $select;
  1132. }
  1133. /**
  1134. * Support method for fetching rows.
  1135. *
  1136. * @param Zend_Db_Table_Select $select query options.
  1137. * @return array An array containing the row results in FETCH_ASSOC mode.
  1138. */
  1139. protected function _fetch(Zend_Db_Table_Select $select)
  1140. {
  1141. $stmt = $this->_db->query($select);
  1142. $data = $stmt->fetchAll(Zend_Db::FETCH_ASSOC);
  1143. return $data;
  1144. }
  1145. }