PageRenderTime 53ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 1ms

/concrete/libraries/3rdparty/Zend/Db/Table/Row/Abstract.php

https://bitbucket.org/selfeky/xclusivescardwebsite
PHP | 1184 lines | 553 code | 138 blank | 493 comment | 94 complexity | 1906ad8e8b8b64d1c4128f6a4b572f29 MD5 | raw file
  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-2012 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 24831 2012-05-30 12:52:25Z rob $
  21. */
  22. /**
  23. * @see Zend_Db
  24. */
  25. require_once 'Zend/Db.php';
  26. /**
  27. * @category Zend
  28. * @package Zend_Db
  29. * @subpackage Table
  30. * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
  31. * @license http://framework.zend.com/license/new-bsd New BSD License
  32. */
  33. abstract class Zend_Db_Table_Row_Abstract implements ArrayAccess, IteratorAggregate
  34. {
  35. /**
  36. * The data for each column in the row (column_name => value).
  37. * The keys must match the physical names of columns in the
  38. * table for which this row is defined.
  39. *
  40. * @var array
  41. */
  42. protected $_data = array();
  43. /**
  44. * This is set to a copy of $_data when the data is fetched from
  45. * a database, specified as a new tuple in the constructor, or
  46. * when dirty data is posted to the database with save().
  47. *
  48. * @var array
  49. */
  50. protected $_cleanData = array();
  51. /**
  52. * Tracks columns where data has been updated. Allows more specific insert and
  53. * update operations.
  54. *
  55. * @var array
  56. */
  57. protected $_modifiedFields = array();
  58. /**
  59. * Zend_Db_Table_Abstract parent class or instance.
  60. *
  61. * @var Zend_Db_Table_Abstract
  62. */
  63. protected $_table = null;
  64. /**
  65. * Connected is true if we have a reference to a live
  66. * Zend_Db_Table_Abstract object.
  67. * This is false after the Rowset has been deserialized.
  68. *
  69. * @var boolean
  70. */
  71. protected $_connected = true;
  72. /**
  73. * A row is marked read only if it contains columns that are not physically represented within
  74. * the database schema (e.g. evaluated columns/Zend_Db_Expr columns). This can also be passed
  75. * as a run-time config options as a means of protecting row data.
  76. *
  77. * @var boolean
  78. */
  79. protected $_readOnly = false;
  80. /**
  81. * Name of the class of the Zend_Db_Table_Abstract object.
  82. *
  83. * @var string
  84. */
  85. protected $_tableClass = null;
  86. /**
  87. * Primary row key(s).
  88. *
  89. * @var array
  90. */
  91. protected $_primary;
  92. /**
  93. * Constructor.
  94. *
  95. * Supported params for $config are:-
  96. * - table = class name or object of type Zend_Db_Table_Abstract
  97. * - data = values of columns in this row.
  98. *
  99. * @param array $config OPTIONAL Array of user-specified config options.
  100. * @return void
  101. * @throws Zend_Db_Table_Row_Exception
  102. */
  103. public function __construct(array $config = array())
  104. {
  105. if (isset($config['table']) && $config['table'] instanceof Zend_Db_Table_Abstract) {
  106. $this->_table = $config['table'];
  107. $this->_tableClass = get_class($this->_table);
  108. } elseif ($this->_tableClass !== null) {
  109. $this->_table = $this->_getTableFromString($this->_tableClass);
  110. }
  111. if (isset($config['data'])) {
  112. if (!is_array($config['data'])) {
  113. require_once 'Zend/Db/Table/Row/Exception.php';
  114. throw new Zend_Db_Table_Row_Exception('Data must be an array');
  115. }
  116. $this->_data = $config['data'];
  117. }
  118. if (isset($config['stored']) && $config['stored'] === true) {
  119. $this->_cleanData = $this->_data;
  120. }
  121. if (isset($config['readOnly']) && $config['readOnly'] === true) {
  122. $this->setReadOnly(true);
  123. }
  124. // Retrieve primary keys from table schema
  125. if (($table = $this->_getTable())) {
  126. $info = $table->info();
  127. $this->_primary = (array) $info['primary'];
  128. }
  129. $this->init();
  130. }
  131. /**
  132. * Transform a column name from the user-specified form
  133. * to the physical form used in the database.
  134. * You can override this method in a custom Row class
  135. * to implement column name mappings, for example inflection.
  136. *
  137. * @param string $columnName Column name given.
  138. * @return string The column name after transformation applied (none by default).
  139. * @throws Zend_Db_Table_Row_Exception if the $columnName is not a string.
  140. */
  141. protected function _transformColumn($columnName)
  142. {
  143. if (!is_string($columnName)) {
  144. require_once 'Zend/Db/Table/Row/Exception.php';
  145. throw new Zend_Db_Table_Row_Exception('Specified column is not a string');
  146. }
  147. // Perform no transformation by default
  148. return $columnName;
  149. }
  150. /**
  151. * Retrieve row field value
  152. *
  153. * @param string $columnName The user-specified column name.
  154. * @return string The corresponding column value.
  155. * @throws Zend_Db_Table_Row_Exception if the $columnName is not a column in the row.
  156. */
  157. public function __get($columnName)
  158. {
  159. $columnName = $this->_transformColumn($columnName);
  160. if (!array_key_exists($columnName, $this->_data)) {
  161. require_once 'Zend/Db/Table/Row/Exception.php';
  162. throw new Zend_Db_Table_Row_Exception("Specified column \"$columnName\" is not in the row");
  163. }
  164. return $this->_data[$columnName];
  165. }
  166. /**
  167. * Set row field value
  168. *
  169. * @param string $columnName The column key.
  170. * @param mixed $value The value for the property.
  171. * @return void
  172. * @throws Zend_Db_Table_Row_Exception
  173. */
  174. public function __set($columnName, $value)
  175. {
  176. $columnName = $this->_transformColumn($columnName);
  177. if (!array_key_exists($columnName, $this->_data)) {
  178. require_once 'Zend/Db/Table/Row/Exception.php';
  179. throw new Zend_Db_Table_Row_Exception("Specified column \"$columnName\" is not in the row");
  180. }
  181. $this->_data[$columnName] = $value;
  182. $this->_modifiedFields[$columnName] = true;
  183. }
  184. /**
  185. * Unset row field value
  186. *
  187. * @param string $columnName The column key.
  188. * @return Zend_Db_Table_Row_Abstract
  189. * @throws Zend_Db_Table_Row_Exception
  190. */
  191. public function __unset($columnName)
  192. {
  193. $columnName = $this->_transformColumn($columnName);
  194. if (!array_key_exists($columnName, $this->_data)) {
  195. require_once 'Zend/Db/Table/Row/Exception.php';
  196. throw new Zend_Db_Table_Row_Exception("Specified column \"$columnName\" is not in the row");
  197. }
  198. if ($this->isConnected() && in_array($columnName, $this->_table->info('primary'))) {
  199. require_once 'Zend/Db/Table/Row/Exception.php';
  200. throw new Zend_Db_Table_Row_Exception("Specified column \"$columnName\" is a primary key and should not be unset");
  201. }
  202. unset($this->_data[$columnName]);
  203. return $this;
  204. }
  205. /**
  206. * Test existence of row field
  207. *
  208. * @param string $columnName The column key.
  209. * @return boolean
  210. */
  211. public function __isset($columnName)
  212. {
  213. $columnName = $this->_transformColumn($columnName);
  214. return array_key_exists($columnName, $this->_data);
  215. }
  216. /**
  217. * Store table, primary key and data in serialized object
  218. *
  219. * @return array
  220. */
  221. public function __sleep()
  222. {
  223. return array('_tableClass', '_primary', '_data', '_cleanData', '_readOnly' ,'_modifiedFields');
  224. }
  225. /**
  226. * Setup to do on wakeup.
  227. * A de-serialized Row should not be assumed to have access to a live
  228. * database connection, so set _connected = false.
  229. *
  230. * @return void
  231. */
  232. public function __wakeup()
  233. {
  234. $this->_connected = false;
  235. }
  236. /**
  237. * Proxy to __isset
  238. * Required by the ArrayAccess implementation
  239. *
  240. * @param string $offset
  241. * @return boolean
  242. */
  243. public function offsetExists($offset)
  244. {
  245. return $this->__isset($offset);
  246. }
  247. /**
  248. * Proxy to __get
  249. * Required by the ArrayAccess implementation
  250. *
  251. * @param string $offset
  252. * @return string
  253. */
  254. public function offsetGet($offset)
  255. {
  256. return $this->__get($offset);
  257. }
  258. /**
  259. * Proxy to __set
  260. * Required by the ArrayAccess implementation
  261. *
  262. * @param string $offset
  263. * @param mixed $value
  264. */
  265. public function offsetSet($offset, $value)
  266. {
  267. $this->__set($offset, $value);
  268. }
  269. /**
  270. * Proxy to __unset
  271. * Required by the ArrayAccess implementation
  272. *
  273. * @param string $offset
  274. */
  275. public function offsetUnset($offset)
  276. {
  277. return $this->__unset($offset);
  278. }
  279. /**
  280. * Initialize object
  281. *
  282. * Called from {@link __construct()} as final step of object instantiation.
  283. *
  284. * @return void
  285. */
  286. public function init()
  287. {
  288. }
  289. /**
  290. * Returns the table object, or null if this is disconnected row
  291. *
  292. * @return Zend_Db_Table_Abstract|null
  293. */
  294. public function getTable()
  295. {
  296. return $this->_table;
  297. }
  298. /**
  299. * Set the table object, to re-establish a live connection
  300. * to the database for a Row that has been de-serialized.
  301. *
  302. * @param Zend_Db_Table_Abstract $table
  303. * @return boolean
  304. * @throws Zend_Db_Table_Row_Exception
  305. */
  306. public function setTable(Zend_Db_Table_Abstract $table = null)
  307. {
  308. if ($table == null) {
  309. $this->_table = null;
  310. $this->_connected = false;
  311. return false;
  312. }
  313. $tableClass = get_class($table);
  314. if (! $table instanceof $this->_tableClass) {
  315. require_once 'Zend/Db/Table/Row/Exception.php';
  316. throw new Zend_Db_Table_Row_Exception("The specified Table is of class $tableClass, expecting class to be instance of $this->_tableClass");
  317. }
  318. $this->_table = $table;
  319. $this->_tableClass = $tableClass;
  320. $info = $this->_table->info();
  321. if ($info['cols'] != array_keys($this->_data)) {
  322. require_once 'Zend/Db/Table/Row/Exception.php';
  323. throw new Zend_Db_Table_Row_Exception('The specified Table does not have the same columns as the Row');
  324. }
  325. if (! array_intersect((array) $this->_primary, $info['primary']) == (array) $this->_primary) {
  326. require_once 'Zend/Db/Table/Row/Exception.php';
  327. throw new Zend_Db_Table_Row_Exception("The specified Table '$tableClass' does not have the same primary key as the Row");
  328. }
  329. $this->_connected = true;
  330. return true;
  331. }
  332. /**
  333. * Query the class name of the Table object for which this
  334. * Row was created.
  335. *
  336. * @return string
  337. */
  338. public function getTableClass()
  339. {
  340. return $this->_tableClass;
  341. }
  342. /**
  343. * Test the connected status of the row.
  344. *
  345. * @return boolean
  346. */
  347. public function isConnected()
  348. {
  349. return $this->_connected;
  350. }
  351. /**
  352. * Test the read-only status of the row.
  353. *
  354. * @return boolean
  355. */
  356. public function isReadOnly()
  357. {
  358. return $this->_readOnly;
  359. }
  360. /**
  361. * Set the read-only status of the row.
  362. *
  363. * @param boolean $flag
  364. * @return boolean
  365. */
  366. public function setReadOnly($flag)
  367. {
  368. $this->_readOnly = (bool) $flag;
  369. }
  370. /**
  371. * Returns an instance of the parent table's Zend_Db_Table_Select object.
  372. *
  373. * @return Zend_Db_Table_Select
  374. */
  375. public function select()
  376. {
  377. return $this->getTable()->select();
  378. }
  379. /**
  380. * Saves the properties to the database.
  381. *
  382. * This performs an intelligent insert/update, and reloads the
  383. * properties with fresh data from the table on success.
  384. *
  385. * @return mixed The primary key value(s), as an associative array if the
  386. * key is compound, or a scalar if the key is single-column.
  387. */
  388. public function save()
  389. {
  390. /**
  391. * If the _cleanData array is empty,
  392. * this is an INSERT of a new row.
  393. * Otherwise it is an UPDATE.
  394. */
  395. if (empty($this->_cleanData)) {
  396. return $this->_doInsert();
  397. } else {
  398. return $this->_doUpdate();
  399. }
  400. }
  401. /**
  402. * @return mixed The primary key value(s), as an associative array if the
  403. * key is compound, or a scalar if the key is single-column.
  404. */
  405. protected function _doInsert()
  406. {
  407. /**
  408. * A read-only row cannot be saved.
  409. */
  410. if ($this->_readOnly === true) {
  411. require_once 'Zend/Db/Table/Row/Exception.php';
  412. throw new Zend_Db_Table_Row_Exception('This row has been marked read-only');
  413. }
  414. /**
  415. * Run pre-INSERT logic
  416. */
  417. $this->_insert();
  418. /**
  419. * Execute the INSERT (this may throw an exception)
  420. */
  421. $data = array_intersect_key($this->_data, $this->_modifiedFields);
  422. $primaryKey = $this->_getTable()->insert($data);
  423. /**
  424. * Normalize the result to an array indexed by primary key column(s).
  425. * The table insert() method may return a scalar.
  426. */
  427. if (is_array($primaryKey)) {
  428. $newPrimaryKey = $primaryKey;
  429. } else {
  430. //ZF-6167 Use tempPrimaryKey temporary to avoid that zend encoding fails.
  431. $tempPrimaryKey = (array) $this->_primary;
  432. $newPrimaryKey = array(current($tempPrimaryKey) => $primaryKey);
  433. }
  434. /**
  435. * Save the new primary key value in _data. The primary key may have
  436. * been generated by a sequence or auto-increment mechanism, and this
  437. * merge should be done before the _postInsert() method is run, so the
  438. * new values are available for logging, etc.
  439. */
  440. $this->_data = array_merge($this->_data, $newPrimaryKey);
  441. /**
  442. * Run post-INSERT logic
  443. */
  444. $this->_postInsert();
  445. /**
  446. * Update the _cleanData to reflect that the data has been inserted.
  447. */
  448. $this->_refresh();
  449. return $primaryKey;
  450. }
  451. /**
  452. * @return mixed The primary key value(s), as an associative array if the
  453. * key is compound, or a scalar if the key is single-column.
  454. */
  455. protected function _doUpdate()
  456. {
  457. /**
  458. * A read-only row cannot be saved.
  459. */
  460. if ($this->_readOnly === true) {
  461. require_once 'Zend/Db/Table/Row/Exception.php';
  462. throw new Zend_Db_Table_Row_Exception('This row has been marked read-only');
  463. }
  464. /**
  465. * Get expressions for a WHERE clause
  466. * based on the primary key value(s).
  467. */
  468. $where = $this->_getWhereQuery(false);
  469. /**
  470. * Run pre-UPDATE logic
  471. */
  472. $this->_update();
  473. /**
  474. * Compare the data to the modified fields array to discover
  475. * which columns have been changed.
  476. */
  477. $diffData = array_intersect_key($this->_data, $this->_modifiedFields);
  478. /**
  479. * Were any of the changed columns part of the primary key?
  480. */
  481. $pkDiffData = array_intersect_key($diffData, array_flip((array)$this->_primary));
  482. /**
  483. * Execute cascading updates against dependent tables.
  484. * Do this only if primary key value(s) were changed.
  485. */
  486. if (count($pkDiffData) > 0) {
  487. $depTables = $this->_getTable()->getDependentTables();
  488. if (!empty($depTables)) {
  489. $pkNew = $this->_getPrimaryKey(true);
  490. $pkOld = $this->_getPrimaryKey(false);
  491. foreach ($depTables as $tableClass) {
  492. $t = $this->_getTableFromString($tableClass);
  493. $t->_cascadeUpdate($this->getTableClass(), $pkOld, $pkNew);
  494. }
  495. }
  496. }
  497. /**
  498. * Execute the UPDATE (this may throw an exception)
  499. * Do this only if data values were changed.
  500. * Use the $diffData variable, so the UPDATE statement
  501. * includes SET terms only for data values that changed.
  502. */
  503. if (count($diffData) > 0) {
  504. $this->_getTable()->update($diffData, $where);
  505. }
  506. /**
  507. * Run post-UPDATE logic. Do this before the _refresh()
  508. * so the _postUpdate() function can tell the difference
  509. * between changed data and clean (pre-changed) data.
  510. */
  511. $this->_postUpdate();
  512. /**
  513. * Refresh the data just in case triggers in the RDBMS changed
  514. * any columns. Also this resets the _cleanData.
  515. */
  516. $this->_refresh();
  517. /**
  518. * Return the primary key value(s) as an array
  519. * if the key is compound or a scalar if the key
  520. * is a scalar.
  521. */
  522. $primaryKey = $this->_getPrimaryKey(true);
  523. if (count($primaryKey) == 1) {
  524. return current($primaryKey);
  525. }
  526. return $primaryKey;
  527. }
  528. /**
  529. * Deletes existing rows.
  530. *
  531. * @return int The number of rows deleted.
  532. */
  533. public function delete()
  534. {
  535. /**
  536. * A read-only row cannot be deleted.
  537. */
  538. if ($this->_readOnly === true) {
  539. require_once 'Zend/Db/Table/Row/Exception.php';
  540. throw new Zend_Db_Table_Row_Exception('This row has been marked read-only');
  541. }
  542. $where = $this->_getWhereQuery();
  543. /**
  544. * Execute pre-DELETE logic
  545. */
  546. $this->_delete();
  547. /**
  548. * Execute cascading deletes against dependent tables
  549. */
  550. $depTables = $this->_getTable()->getDependentTables();
  551. if (!empty($depTables)) {
  552. $pk = $this->_getPrimaryKey();
  553. foreach ($depTables as $tableClass) {
  554. $t = $this->_getTableFromString($tableClass);
  555. $t->_cascadeDelete($this->getTableClass(), $pk);
  556. }
  557. }
  558. /**
  559. * Execute the DELETE (this may throw an exception)
  560. */
  561. $result = $this->_getTable()->delete($where);
  562. /**
  563. * Execute post-DELETE logic
  564. */
  565. $this->_postDelete();
  566. /**
  567. * Reset all fields to null to indicate that the row is not there
  568. */
  569. $this->_data = array_combine(
  570. array_keys($this->_data),
  571. array_fill(0, count($this->_data), null)
  572. );
  573. return $result;
  574. }
  575. public function getIterator()
  576. {
  577. return new ArrayIterator((array) $this->_data);
  578. }
  579. /**
  580. * Returns the column/value data as an array.
  581. *
  582. * @return array
  583. */
  584. public function toArray()
  585. {
  586. return (array)$this->_data;
  587. }
  588. /**
  589. * Sets all data in the row from an array.
  590. *
  591. * @param array $data
  592. * @return Zend_Db_Table_Row_Abstract Provides a fluent interface
  593. */
  594. public function setFromArray(array $data)
  595. {
  596. $data = array_intersect_key($data, $this->_data);
  597. foreach ($data as $columnName => $value) {
  598. $this->__set($columnName, $value);
  599. }
  600. return $this;
  601. }
  602. /**
  603. * Refreshes properties from the database.
  604. *
  605. * @return void
  606. */
  607. public function refresh()
  608. {
  609. return $this->_refresh();
  610. }
  611. /**
  612. * Retrieves an instance of the parent table.
  613. *
  614. * @return Zend_Db_Table_Abstract
  615. */
  616. protected function _getTable()
  617. {
  618. if (!$this->_connected) {
  619. require_once 'Zend/Db/Table/Row/Exception.php';
  620. throw new Zend_Db_Table_Row_Exception('Cannot save a Row unless it is connected');
  621. }
  622. return $this->_table;
  623. }
  624. /**
  625. * Retrieves an associative array of primary keys.
  626. *
  627. * @param bool $useDirty
  628. * @return array
  629. */
  630. protected function _getPrimaryKey($useDirty = true)
  631. {
  632. if (!is_array($this->_primary)) {
  633. require_once 'Zend/Db/Table/Row/Exception.php';
  634. throw new Zend_Db_Table_Row_Exception("The primary key must be set as an array");
  635. }
  636. $primary = array_flip($this->_primary);
  637. if ($useDirty) {
  638. $array = array_intersect_key($this->_data, $primary);
  639. } else {
  640. $array = array_intersect_key($this->_cleanData, $primary);
  641. }
  642. if (count($primary) != count($array)) {
  643. require_once 'Zend/Db/Table/Row/Exception.php';
  644. throw new Zend_Db_Table_Row_Exception("The specified Table '$this->_tableClass' does not have the same primary key as the Row");
  645. }
  646. return $array;
  647. }
  648. /**
  649. * Retrieves an associative array of primary keys.
  650. *
  651. * @param bool $useDirty
  652. * @return array
  653. */
  654. public function getPrimaryKey($useDirty = true)
  655. {
  656. return $this->_getPrimaryKey($useDirty);
  657. }
  658. /**
  659. * Constructs where statement for retrieving row(s).
  660. *
  661. * @param bool $useDirty
  662. * @return array
  663. */
  664. protected function _getWhereQuery($useDirty = true)
  665. {
  666. $where = array();
  667. $db = $this->_getTable()->getAdapter();
  668. $primaryKey = $this->_getPrimaryKey($useDirty);
  669. $info = $this->_getTable()->info();
  670. $metadata = $info[Zend_Db_Table_Abstract::METADATA];
  671. // retrieve recently updated row using primary keys
  672. $where = array();
  673. foreach ($primaryKey as $column => $value) {
  674. $tableName = $db->quoteIdentifier($info[Zend_Db_Table_Abstract::NAME], true);
  675. $type = $metadata[$column]['DATA_TYPE'];
  676. $columnName = $db->quoteIdentifier($column, true);
  677. $where[] = $db->quoteInto("{$tableName}.{$columnName} = ?", $value, $type);
  678. }
  679. return $where;
  680. }
  681. /**
  682. * Refreshes properties from the database.
  683. *
  684. * @return void
  685. */
  686. protected function _refresh()
  687. {
  688. $where = $this->_getWhereQuery();
  689. $row = $this->_getTable()->fetchRow($where);
  690. if (null === $row) {
  691. require_once 'Zend/Db/Table/Row/Exception.php';
  692. throw new Zend_Db_Table_Row_Exception('Cannot refresh row as parent is missing');
  693. }
  694. $this->_data = $row->toArray();
  695. $this->_cleanData = $this->_data;
  696. $this->_modifiedFields = array();
  697. }
  698. /**
  699. * Allows pre-insert logic to be applied to row.
  700. * Subclasses may override this method.
  701. *
  702. * @return void
  703. */
  704. protected function _insert()
  705. {
  706. }
  707. /**
  708. * Allows post-insert logic to be applied to row.
  709. * Subclasses may override this method.
  710. *
  711. * @return void
  712. */
  713. protected function _postInsert()
  714. {
  715. }
  716. /**
  717. * Allows pre-update logic to be applied to row.
  718. * Subclasses may override this method.
  719. *
  720. * @return void
  721. */
  722. protected function _update()
  723. {
  724. }
  725. /**
  726. * Allows post-update logic to be applied to row.
  727. * Subclasses may override this method.
  728. *
  729. * @return void
  730. */
  731. protected function _postUpdate()
  732. {
  733. }
  734. /**
  735. * Allows pre-delete logic to be applied to row.
  736. * Subclasses may override this method.
  737. *
  738. * @return void
  739. */
  740. protected function _delete()
  741. {
  742. }
  743. /**
  744. * Allows post-delete logic to be applied to row.
  745. * Subclasses may override this method.
  746. *
  747. * @return void
  748. */
  749. protected function _postDelete()
  750. {
  751. }
  752. /**
  753. * Prepares a table reference for lookup.
  754. *
  755. * Ensures all reference keys are set and properly formatted.
  756. *
  757. * @param Zend_Db_Table_Abstract $dependentTable
  758. * @param Zend_Db_Table_Abstract $parentTable
  759. * @param string $ruleKey
  760. * @return array
  761. */
  762. protected function _prepareReference(Zend_Db_Table_Abstract $dependentTable, Zend_Db_Table_Abstract $parentTable, $ruleKey)
  763. {
  764. $parentTableName = (get_class($parentTable) === 'Zend_Db_Table') ? $parentTable->getDefinitionConfigName() : get_class($parentTable);
  765. $map = $dependentTable->getReference($parentTableName, $ruleKey);
  766. if (!isset($map[Zend_Db_Table_Abstract::REF_COLUMNS])) {
  767. $parentInfo = $parentTable->info();
  768. $map[Zend_Db_Table_Abstract::REF_COLUMNS] = array_values((array) $parentInfo['primary']);
  769. }
  770. $map[Zend_Db_Table_Abstract::COLUMNS] = (array) $map[Zend_Db_Table_Abstract::COLUMNS];
  771. $map[Zend_Db_Table_Abstract::REF_COLUMNS] = (array) $map[Zend_Db_Table_Abstract::REF_COLUMNS];
  772. return $map;
  773. }
  774. /**
  775. * Query a dependent table to retrieve rows matching the current row.
  776. *
  777. * @param string|Zend_Db_Table_Abstract $dependentTable
  778. * @param string OPTIONAL $ruleKey
  779. * @param Zend_Db_Table_Select OPTIONAL $select
  780. * @return Zend_Db_Table_Rowset_Abstract Query result from $dependentTable
  781. * @throws Zend_Db_Table_Row_Exception If $dependentTable is not a table or is not loadable.
  782. */
  783. public function findDependentRowset($dependentTable, $ruleKey = null, Zend_Db_Table_Select $select = null)
  784. {
  785. $db = $this->_getTable()->getAdapter();
  786. if (is_string($dependentTable)) {
  787. $dependentTable = $this->_getTableFromString($dependentTable);
  788. }
  789. if (!$dependentTable instanceof Zend_Db_Table_Abstract) {
  790. $type = gettype($dependentTable);
  791. if ($type == 'object') {
  792. $type = get_class($dependentTable);
  793. }
  794. require_once 'Zend/Db/Table/Row/Exception.php';
  795. throw new Zend_Db_Table_Row_Exception("Dependent table must be a Zend_Db_Table_Abstract, but it is $type");
  796. }
  797. // even if we are interacting between a table defined in a class and a
  798. // table via extension, ensure to persist the definition
  799. if (($tableDefinition = $this->_table->getDefinition()) !== null
  800. && ($dependentTable->getDefinition() == null)) {
  801. $dependentTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition));
  802. }
  803. if ($select === null) {
  804. $select = $dependentTable->select();
  805. } else {
  806. $select->setTable($dependentTable);
  807. }
  808. $map = $this->_prepareReference($dependentTable, $this->_getTable(), $ruleKey);
  809. for ($i = 0; $i < count($map[Zend_Db_Table_Abstract::COLUMNS]); ++$i) {
  810. $parentColumnName = $db->foldCase($map[Zend_Db_Table_Abstract::REF_COLUMNS][$i]);
  811. $value = $this->_data[$parentColumnName];
  812. // Use adapter from dependent table to ensure correct query construction
  813. $dependentDb = $dependentTable->getAdapter();
  814. $dependentColumnName = $dependentDb->foldCase($map[Zend_Db_Table_Abstract::COLUMNS][$i]);
  815. $dependentColumn = $dependentDb->quoteIdentifier($dependentColumnName, true);
  816. $dependentInfo = $dependentTable->info();
  817. $type = $dependentInfo[Zend_Db_Table_Abstract::METADATA][$dependentColumnName]['DATA_TYPE'];
  818. $select->where("$dependentColumn = ?", $value, $type);
  819. }
  820. return $dependentTable->fetchAll($select);
  821. }
  822. /**
  823. * Query a parent table to retrieve the single row matching the current row.
  824. *
  825. * @param string|Zend_Db_Table_Abstract $parentTable
  826. * @param string OPTIONAL $ruleKey
  827. * @param Zend_Db_Table_Select OPTIONAL $select
  828. * @return Zend_Db_Table_Row_Abstract Query result from $parentTable
  829. * @throws Zend_Db_Table_Row_Exception If $parentTable is not a table or is not loadable.
  830. */
  831. public function findParentRow($parentTable, $ruleKey = null, Zend_Db_Table_Select $select = null)
  832. {
  833. $db = $this->_getTable()->getAdapter();
  834. if (is_string($parentTable)) {
  835. $parentTable = $this->_getTableFromString($parentTable);
  836. }
  837. if (!$parentTable instanceof Zend_Db_Table_Abstract) {
  838. $type = gettype($parentTable);
  839. if ($type == 'object') {
  840. $type = get_class($parentTable);
  841. }
  842. require_once 'Zend/Db/Table/Row/Exception.php';
  843. throw new Zend_Db_Table_Row_Exception("Parent table must be a Zend_Db_Table_Abstract, but it is $type");
  844. }
  845. // even if we are interacting between a table defined in a class and a
  846. // table via extension, ensure to persist the definition
  847. if (($tableDefinition = $this->_table->getDefinition()) !== null
  848. && ($parentTable->getDefinition() == null)) {
  849. $parentTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition));
  850. }
  851. if ($select === null) {
  852. $select = $parentTable->select();
  853. } else {
  854. $select->setTable($parentTable);
  855. }
  856. $map = $this->_prepareReference($this->_getTable(), $parentTable, $ruleKey);
  857. // iterate the map, creating the proper wheres
  858. for ($i = 0; $i < count($map[Zend_Db_Table_Abstract::COLUMNS]); ++$i) {
  859. $dependentColumnName = $db->foldCase($map[Zend_Db_Table_Abstract::COLUMNS][$i]);
  860. $value = $this->_data[$dependentColumnName];
  861. // Use adapter from parent table to ensure correct query construction
  862. $parentDb = $parentTable->getAdapter();
  863. $parentColumnName = $parentDb->foldCase($map[Zend_Db_Table_Abstract::REF_COLUMNS][$i]);
  864. $parentColumn = $parentDb->quoteIdentifier($parentColumnName, true);
  865. $parentInfo = $parentTable->info();
  866. // determine where part
  867. $type = $parentInfo[Zend_Db_Table_Abstract::METADATA][$parentColumnName]['DATA_TYPE'];
  868. $nullable = $parentInfo[Zend_Db_Table_Abstract::METADATA][$parentColumnName]['NULLABLE'];
  869. if ($value === null && $nullable == true) {
  870. $select->where("$parentColumn IS NULL");
  871. } elseif ($value === null && $nullable == false) {
  872. return null;
  873. } else {
  874. $select->where("$parentColumn = ?", $value, $type);
  875. }
  876. }
  877. return $parentTable->fetchRow($select);
  878. }
  879. /**
  880. * @param string|Zend_Db_Table_Abstract $matchTable
  881. * @param string|Zend_Db_Table_Abstract $intersectionTable
  882. * @param string OPTIONAL $callerRefRule
  883. * @param string OPTIONAL $matchRefRule
  884. * @param Zend_Db_Table_Select OPTIONAL $select
  885. * @return Zend_Db_Table_Rowset_Abstract Query result from $matchTable
  886. * @throws Zend_Db_Table_Row_Exception If $matchTable or $intersectionTable is not a table class or is not loadable.
  887. */
  888. public function findManyToManyRowset($matchTable, $intersectionTable, $callerRefRule = null,
  889. $matchRefRule = null, Zend_Db_Table_Select $select = null)
  890. {
  891. $db = $this->_getTable()->getAdapter();
  892. if (is_string($intersectionTable)) {
  893. $intersectionTable = $this->_getTableFromString($intersectionTable);
  894. }
  895. if (!$intersectionTable instanceof Zend_Db_Table_Abstract) {
  896. $type = gettype($intersectionTable);
  897. if ($type == 'object') {
  898. $type = get_class($intersectionTable);
  899. }
  900. require_once 'Zend/Db/Table/Row/Exception.php';
  901. throw new Zend_Db_Table_Row_Exception("Intersection table must be a Zend_Db_Table_Abstract, but it is $type");
  902. }
  903. // even if we are interacting between a table defined in a class and a
  904. // table via extension, ensure to persist the definition
  905. if (($tableDefinition = $this->_table->getDefinition()) !== null
  906. && ($intersectionTable->getDefinition() == null)) {
  907. $intersectionTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition));
  908. }
  909. if (is_string($matchTable)) {
  910. $matchTable = $this->_getTableFromString($matchTable);
  911. }
  912. if (! $matchTable instanceof Zend_Db_Table_Abstract) {
  913. $type = gettype($matchTable);
  914. if ($type == 'object') {
  915. $type = get_class($matchTable);
  916. }
  917. require_once 'Zend/Db/Table/Row/Exception.php';
  918. throw new Zend_Db_Table_Row_Exception("Match table must be a Zend_Db_Table_Abstract, but it is $type");
  919. }
  920. // even if we are interacting between a table defined in a class and a
  921. // table via extension, ensure to persist the definition
  922. if (($tableDefinition = $this->_table->getDefinition()) !== null
  923. && ($matchTable->getDefinition() == null)) {
  924. $matchTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition));
  925. }
  926. if ($select === null) {
  927. $select = $matchTable->select();
  928. } else {
  929. $select->setTable($matchTable);
  930. }
  931. // Use adapter from intersection table to ensure correct query construction
  932. $interInfo = $intersectionTable->info();
  933. $interDb = $intersectionTable->getAdapter();
  934. $interName = $interInfo['name'];
  935. $interSchema = isset($interInfo['schema']) ? $interInfo['schema'] : null;
  936. $matchInfo = $matchTable->info();
  937. $matchName = $matchInfo['name'];
  938. $matchSchema = isset($matchInfo['schema']) ? $matchInfo['schema'] : null;
  939. $matchMap = $this->_prepareReference($intersectionTable, $matchTable, $matchRefRule);
  940. for ($i = 0; $i < count($matchMap[Zend_Db_Table_Abstract::COLUMNS]); ++$i) {
  941. $interCol = $interDb->quoteIdentifier('i' . '.' . $matchMap[Zend_Db_Table_Abstract::COLUMNS][$i], true);
  942. $matchCol = $interDb->quoteIdentifier('m' . '.' . $matchMap[Zend_Db_Table_Abstract::REF_COLUMNS][$i], true);
  943. $joinCond[] = "$interCol = $matchCol";
  944. }
  945. $joinCond = implode(' AND ', $joinCond);
  946. $select->from(array('i' => $interName), array(), $interSchema)
  947. ->joinInner(array('m' => $matchName), $joinCond, Zend_Db_Select::SQL_WILDCARD, $matchSchema)
  948. ->setIntegrityCheck(false);
  949. $callerMap = $this->_prepareReference($intersectionTable, $this->_getTable(), $callerRefRule);
  950. for ($i = 0; $i < count($callerMap[Zend_Db_Table_Abstract::COLUMNS]); ++$i) {
  951. $callerColumnName = $db->foldCase($callerMap[Zend_Db_Table_Abstract::REF_COLUMNS][$i]);
  952. $value = $this->_data[$callerColumnName];
  953. $interColumnName = $interDb->foldCase($callerMap[Zend_Db_Table_Abstract::COLUMNS][$i]);
  954. $interCol = $interDb->quoteIdentifier("i.$interColumnName", true);
  955. $interInfo = $intersectionTable->info();
  956. $type = $interInfo[Zend_Db_Table_Abstract::METADATA][$interColumnName]['DATA_TYPE'];
  957. $select->where($interDb->quoteInto("$interCol = ?", $value, $type));
  958. }
  959. $stmt = $select->query();
  960. $config = array(
  961. 'table' => $matchTable,
  962. 'data' => $stmt->fetchAll(Zend_Db::FETCH_ASSOC),
  963. 'rowClass' => $matchTable->getRowClass(),
  964. 'readOnly' => false,
  965. 'stored' => true
  966. );
  967. $rowsetClass = $matchTable->getRowsetClass();
  968. if (!class_exists($rowsetClass)) {
  969. try {
  970. require_once 'Zend/Loader.php';
  971. Zend_Loader::loadClass($rowsetClass);
  972. } catch (Zend_Exception $e) {
  973. require_once 'Zend/Db/Table/Row/Exception.php';
  974. throw new Zend_Db_Table_Row_Exception($e->getMessage(), $e->getCode(), $e);
  975. }
  976. }
  977. $rowset = new $rowsetClass($config);
  978. return $rowset;
  979. }
  980. /**
  981. * Turn magic function calls into non-magic function calls
  982. * to the above methods.
  983. *
  984. * @param string $method
  985. * @param array $args OPTIONAL Zend_Db_Table_Select query modifier
  986. * @return Zend_Db_Table_Row_Abstract|Zend_Db_Table_Rowset_Abstract
  987. * @throws Zend_Db_Table_Row_Exception If an invalid method is called.
  988. */
  989. public function __call($method, array $args)
  990. {
  991. $matches = array();
  992. if (count($args) && $args[0] instanceof Zend_Db_Table_Select) {
  993. $select = $args[0];
  994. } else {
  995. $select = null;
  996. }
  997. /**
  998. * Recognize methods for Has-Many cases:
  999. * findParent<Class>()
  1000. * findParent<Class>By<Rule>()
  1001. * Use the non-greedy pattern repeat modifier e.g. \w+?
  1002. */
  1003. if (preg_match('/^findParent(\w+?)(?:By(\w+))?$/', $method, $matches)) {
  1004. $class = $matches[1];
  1005. $ruleKey1 = isset($matches[2]) ? $matches[2] : null;
  1006. return $this->findParentRow($class, $ruleKey1, $select);
  1007. }
  1008. /**
  1009. * Recognize methods for Many-to-Many cases:
  1010. * find<Class1>Via<Class2>()
  1011. * find<Class1>Via<Class2>By<Rule>()
  1012. * find<Class1>Via<Class2>By<Rule1>And<Rule2>()
  1013. * Use the non-greedy pattern repeat modifier e.g. \w+?
  1014. */
  1015. if (preg_match('/^find(\w+?)Via(\w+?)(?:By(\w+?)(?:And(\w+))?)?$/', $method, $matches)) {
  1016. $class = $matches[1];
  1017. $viaClass = $matches[2];
  1018. $ruleKey1 = isset($matches[3]) ? $matches[3] : null;
  1019. $ruleKey2 = isset($matches[4]) ? $matches[4] : null;
  1020. return $this->findManyToManyRowset($class, $viaClass, $ruleKey1, $ruleKey2, $select);
  1021. }
  1022. /**
  1023. * Recognize methods for Belongs-To cases:
  1024. * find<Class>()
  1025. * find<Class>By<Rule>()
  1026. * Use the non-greedy pattern repeat modifier e.g. \w+?
  1027. */
  1028. if (preg_match('/^find(\w+?)(?:By(\w+))?$/', $method, $matches)) {
  1029. $class = $matches[1];
  1030. $ruleKey1 = isset($matches[2]) ? $matches[2] : null;
  1031. return $this->findDependentRowset($class, $ruleKey1, $select);
  1032. }
  1033. require_once 'Zend/Db/Table/Row/Exception.php';
  1034. throw new Zend_Db_Table_Row_Exception("Unrecognized method '$method()'");
  1035. }
  1036. /**
  1037. * _getTableFromString
  1038. *
  1039. * @param string $tableName
  1040. * @return Zend_Db_Table_Abstract
  1041. */
  1042. protected function _getTableFromString($tableName)
  1043. {
  1044. return Zend_Db_Table_Abstract::getTableFromString($tableName, $this->_table);
  1045. }
  1046. }