PageRenderTime 41ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/src/application/libraries/Zend/Db/Table/Row/Abstract.php

https://bitbucket.org/masnug/grc276-blog-laravel
PHP | 1203 lines | 571 code | 144 blank | 488 comment | 101 complexity | 5053fbed59d0d602ad820f93e3b91aa9 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-2011 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 23775 2011-03-01 17:25:24Z ralph $
  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-2011 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. * Constructs where statement for retrieving row(s).
  650. *
  651. * @param bool $useDirty
  652. * @return array
  653. */
  654. protected function _getWhereQuery($useDirty = true)
  655. {
  656. $where = array();
  657. $db = $this->_getTable()->getAdapter();
  658. $primaryKey = $this->_getPrimaryKey($useDirty);
  659. $info = $this->_getTable()->info();
  660. $metadata = $info[Zend_Db_Table_Abstract::METADATA];
  661. // retrieve recently updated row using primary keys
  662. $where = array();
  663. foreach ($primaryKey as $column => $value) {
  664. $tableName = $db->quoteIdentifier($info[Zend_Db_Table_Abstract::NAME], true);
  665. $type = $metadata[$column]['DATA_TYPE'];
  666. $columnName = $db->quoteIdentifier($column, true);
  667. $where[] = $db->quoteInto("{$tableName}.{$columnName} = ?", $value, $type);
  668. }
  669. return $where;
  670. }
  671. /**
  672. * Refreshes properties from the database.
  673. *
  674. * @return void
  675. */
  676. protected function _refresh()
  677. {
  678. $where = $this->_getWhereQuery();
  679. $row = $this->_getTable()->fetchRow($where);
  680. if (null === $row) {
  681. require_once 'Zend/Db/Table/Row/Exception.php';
  682. throw new Zend_Db_Table_Row_Exception('Cannot refresh row as parent is missing');
  683. }
  684. $this->_data = $row->toArray();
  685. $this->_cleanData = $this->_data;
  686. $this->_modifiedFields = array();
  687. }
  688. /**
  689. * Allows pre-insert logic to be applied to row.
  690. * Subclasses may override this method.
  691. *
  692. * @return void
  693. */
  694. protected function _insert()
  695. {
  696. }
  697. /**
  698. * Allows post-insert logic to be applied to row.
  699. * Subclasses may override this method.
  700. *
  701. * @return void
  702. */
  703. protected function _postInsert()
  704. {
  705. }
  706. /**
  707. * Allows pre-update logic to be applied to row.
  708. * Subclasses may override this method.
  709. *
  710. * @return void
  711. */
  712. protected function _update()
  713. {
  714. }
  715. /**
  716. * Allows post-update logic to be applied to row.
  717. * Subclasses may override this method.
  718. *
  719. * @return void
  720. */
  721. protected function _postUpdate()
  722. {
  723. }
  724. /**
  725. * Allows pre-delete logic to be applied to row.
  726. * Subclasses may override this method.
  727. *
  728. * @return void
  729. */
  730. protected function _delete()
  731. {
  732. }
  733. /**
  734. * Allows post-delete logic to be applied to row.
  735. * Subclasses may override this method.
  736. *
  737. * @return void
  738. */
  739. protected function _postDelete()
  740. {
  741. }
  742. /**
  743. * Prepares a table reference for lookup.
  744. *
  745. * Ensures all reference keys are set and properly formatted.
  746. *
  747. * @param Zend_Db_Table_Abstract $dependentTable
  748. * @param Zend_Db_Table_Abstract $parentTable
  749. * @param string $ruleKey
  750. * @return array
  751. */
  752. protected function _prepareReference(Zend_Db_Table_Abstract $dependentTable, Zend_Db_Table_Abstract $parentTable, $ruleKey)
  753. {
  754. $parentTableName = (get_class($parentTable) === 'Zend_Db_Table') ? $parentTable->getDefinitionConfigName() : get_class($parentTable);
  755. $map = $dependentTable->getReference($parentTableName, $ruleKey);
  756. if (!isset($map[Zend_Db_Table_Abstract::REF_COLUMNS])) {
  757. $parentInfo = $parentTable->info();
  758. $map[Zend_Db_Table_Abstract::REF_COLUMNS] = array_values((array) $parentInfo['primary']);
  759. }
  760. $map[Zend_Db_Table_Abstract::COLUMNS] = (array) $map[Zend_Db_Table_Abstract::COLUMNS];
  761. $map[Zend_Db_Table_Abstract::REF_COLUMNS] = (array) $map[Zend_Db_Table_Abstract::REF_COLUMNS];
  762. return $map;
  763. }
  764. /**
  765. * Query a dependent table to retrieve rows matching the current row.
  766. *
  767. * @param string|Zend_Db_Table_Abstract $dependentTable
  768. * @param string OPTIONAL $ruleKey
  769. * @param Zend_Db_Table_Select OPTIONAL $select
  770. * @return Zend_Db_Table_Rowset_Abstract Query result from $dependentTable
  771. * @throws Zend_Db_Table_Row_Exception If $dependentTable is not a table or is not loadable.
  772. */
  773. public function findDependentRowset($dependentTable, $ruleKey = null, Zend_Db_Table_Select $select = null)
  774. {
  775. $db = $this->_getTable()->getAdapter();
  776. if (is_string($dependentTable)) {
  777. $dependentTable = $this->_getTableFromString($dependentTable);
  778. }
  779. if (!$dependentTable instanceof Zend_Db_Table_Abstract) {
  780. $type = gettype($dependentTable);
  781. if ($type == 'object') {
  782. $type = get_class($dependentTable);
  783. }
  784. require_once 'Zend/Db/Table/Row/Exception.php';
  785. throw new Zend_Db_Table_Row_Exception("Dependent table must be a Zend_Db_Table_Abstract, but it is $type");
  786. }
  787. // even if we are interacting between a table defined in a class and a
  788. // table via extension, ensure to persist the definition
  789. if (($tableDefinition = $this->_table->getDefinition()) !== null
  790. && ($dependentTable->getDefinition() == null)) {
  791. $dependentTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition));
  792. }
  793. if ($select === null) {
  794. $select = $dependentTable->select();
  795. } else {
  796. $select->setTable($dependentTable);
  797. }
  798. $map = $this->_prepareReference($dependentTable, $this->_getTable(), $ruleKey);
  799. for ($i = 0; $i < count($map[Zend_Db_Table_Abstract::COLUMNS]); ++$i) {
  800. $parentColumnName = $db->foldCase($map[Zend_Db_Table_Abstract::REF_COLUMNS][$i]);
  801. $value = $this->_data[$parentColumnName];
  802. // Use adapter from dependent table to ensure correct query construction
  803. $dependentDb = $dependentTable->getAdapter();
  804. $dependentColumnName = $dependentDb->foldCase($map[Zend_Db_Table_Abstract::COLUMNS][$i]);
  805. $dependentColumn = $dependentDb->quoteIdentifier($dependentColumnName, true);
  806. $dependentInfo = $dependentTable->info();
  807. $type = $dependentInfo[Zend_Db_Table_Abstract::METADATA][$dependentColumnName]['DATA_TYPE'];
  808. $select->where("$dependentColumn = ?", $value, $type);
  809. }
  810. return $dependentTable->fetchAll($select);
  811. }
  812. /**
  813. * Query a parent table to retrieve the single row matching the current row.
  814. *
  815. * @param string|Zend_Db_Table_Abstract $parentTable
  816. * @param string OPTIONAL $ruleKey
  817. * @param Zend_Db_Table_Select OPTIONAL $select
  818. * @return Zend_Db_Table_Row_Abstract Query result from $parentTable
  819. * @throws Zend_Db_Table_Row_Exception If $parentTable is not a table or is not loadable.
  820. */
  821. public function findParentRow($parentTable, $ruleKey = null, Zend_Db_Table_Select $select = null)
  822. {
  823. $db = $this->_getTable()->getAdapter();
  824. if (is_string($parentTable)) {
  825. $parentTable = $this->_getTableFromString($parentTable);
  826. }
  827. if (!$parentTable instanceof Zend_Db_Table_Abstract) {
  828. $type = gettype($parentTable);
  829. if ($type == 'object') {
  830. $type = get_class($parentTable);
  831. }
  832. require_once 'Zend/Db/Table/Row/Exception.php';
  833. throw new Zend_Db_Table_Row_Exception("Parent table must be a Zend_Db_Table_Abstract, but it is $type");
  834. }
  835. // even if we are interacting between a table defined in a class and a
  836. // table via extension, ensure to persist the definition
  837. if (($tableDefinition = $this->_table->getDefinition()) !== null
  838. && ($parentTable->getDefinition() == null)) {
  839. $parentTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition));
  840. }
  841. if ($select === null) {
  842. $select = $parentTable->select();
  843. } else {
  844. $select->setTable($parentTable);
  845. }
  846. $map = $this->_prepareReference($this->_getTable(), $parentTable, $ruleKey);
  847. // iterate the map, creating the proper wheres
  848. for ($i = 0; $i < count($map[Zend_Db_Table_Abstract::COLUMNS]); ++$i) {
  849. $dependentColumnName = $db->foldCase($map[Zend_Db_Table_Abstract::COLUMNS][$i]);
  850. $value = $this->_data[$dependentColumnName];
  851. // Use adapter from parent table to ensure correct query construction
  852. $parentDb = $parentTable->getAdapter();
  853. $parentColumnName = $parentDb->foldCase($map[Zend_Db_Table_Abstract::REF_COLUMNS][$i]);
  854. $parentColumn = $parentDb->quoteIdentifier($parentColumnName, true);
  855. $parentInfo = $parentTable->info();
  856. // determine where part
  857. $type = $parentInfo[Zend_Db_Table_Abstract::METADATA][$parentColumnName]['DATA_TYPE'];
  858. $nullable = $parentInfo[Zend_Db_Table_Abstract::METADATA][$parentColumnName]['NULLABLE'];
  859. if ($value === null && $nullable == true) {
  860. $select->where("$parentColumn IS NULL");
  861. } elseif ($value === null && $nullable == false) {
  862. return null;
  863. } else {
  864. $select->where("$parentColumn = ?", $value, $type);
  865. }
  866. }
  867. return $parentTable->fetchRow($select);
  868. }
  869. /**
  870. * @param string|Zend_Db_Table_Abstract $matchTable
  871. * @param string|Zend_Db_Table_Abstract $intersectionTable
  872. * @param string OPTIONAL $callerRefRule
  873. * @param string OPTIONAL $matchRefRule
  874. * @param Zend_Db_Table_Select OPTIONAL $select
  875. * @return Zend_Db_Table_Rowset_Abstract Query result from $matchTable
  876. * @throws Zend_Db_Table_Row_Exception If $matchTable or $intersectionTable is not a table class or is not loadable.
  877. */
  878. public function findManyToManyRowset($matchTable, $intersectionTable, $callerRefRule = null,
  879. $matchRefRule = null, Zend_Db_Table_Select $select = null)
  880. {
  881. $db = $this->_getTable()->getAdapter();
  882. if (is_string($intersectionTable)) {
  883. $intersectionTable = $this->_getTableFromString($intersectionTable);
  884. }
  885. if (!$intersectionTable instanceof Zend_Db_Table_Abstract) {
  886. $type = gettype($intersectionTable);
  887. if ($type == 'object') {
  888. $type = get_class($intersectionTable);
  889. }
  890. require_once 'Zend/Db/Table/Row/Exception.php';
  891. throw new Zend_Db_Table_Row_Exception("Intersection table must be a Zend_Db_Table_Abstract, but it is $type");
  892. }
  893. // even if we are interacting between a table defined in a class and a
  894. // table via extension, ensure to persist the definition
  895. if (($tableDefinition = $this->_table->getDefinition()) !== null
  896. && ($intersectionTable->getDefinition() == null)) {
  897. $intersectionTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition));
  898. }
  899. if (is_string($matchTable)) {
  900. $matchTable = $this->_getTableFromString($matchTable);
  901. }
  902. if (! $matchTable instanceof Zend_Db_Table_Abstract) {
  903. $type = gettype($matchTable);
  904. if ($type == 'object') {
  905. $type = get_class($matchTable);
  906. }
  907. require_once 'Zend/Db/Table/Row/Exception.php';
  908. throw new Zend_Db_Table_Row_Exception("Match table must be a Zend_Db_Table_Abstract, but it is $type");
  909. }
  910. // even if we are interacting between a table defined in a class and a
  911. // table via extension, ensure to persist the definition
  912. if (($tableDefinition = $this->_table->getDefinition()) !== null
  913. && ($matchTable->getDefinition() == null)) {
  914. $matchTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition));
  915. }
  916. if ($select === null) {
  917. $select = $matchTable->select();
  918. } else {
  919. $select->setTable($matchTable);
  920. }
  921. // Use adapter from intersection table to ensure correct query construction
  922. $interInfo = $intersectionTable->info();
  923. $interDb = $intersectionTable->getAdapter();
  924. $interName = $interInfo['name'];
  925. $interSchema = isset($interInfo['schema']) ? $interInfo['schema'] : null;
  926. $matchInfo = $matchTable->info();
  927. $matchName = $matchInfo['name'];
  928. $matchSchema = isset($matchInfo['schema']) ? $matchInfo['schema'] : null;
  929. $matchMap = $this->_prepareReference($intersectionTable, $matchTable, $matchRefRule);
  930. for ($i = 0; $i < count($matchMap[Zend_Db_Table_Abstract::COLUMNS]); ++$i) {
  931. $interCol = $interDb->quoteIdentifier('i' . '.' . $matchMap[Zend_Db_Table_Abstract::COLUMNS][$i], true);
  932. $matchCol = $interDb->quoteIdentifier('m' . '.' . $matchMap[Zend_Db_Table_Abstract::REF_COLUMNS][$i], true);
  933. $joinCond[] = "$interCol = $matchCol";
  934. }
  935. $joinCond = implode(' AND ', $joinCond);
  936. $select->from(array('i' => $interName), array(), $interSchema)
  937. ->joinInner(array('m' => $matchName), $joinCond, Zend_Db_Select::SQL_WILDCARD, $matchSchema)
  938. ->setIntegrityCheck(false);
  939. $callerMap = $this->_prepareReference($intersectionTable, $this->_getTable(), $callerRefRule);
  940. for ($i = 0; $i < count($callerMap[Zend_Db_Table_Abstract::COLUMNS]); ++$i) {
  941. $callerColumnName = $db->foldCase($callerMap[Zend_Db_Table_Abstract::REF_COLUMNS][$i]);
  942. $value = $this->_data[$callerColumnName];
  943. $interColumnName = $interDb->foldCase($callerMap[Zend_Db_Table_Abstract::COLUMNS][$i]);
  944. $interCol = $interDb->quoteIdentifier("i.$interColumnName", true);
  945. $interInfo = $intersectionTable->info();
  946. $type = $interInfo[Zend_Db_Table_Abstract::METADATA][$interColumnName]['DATA_TYPE'];
  947. $select->where($interDb->quoteInto("$interCol = ?", $value, $type));
  948. }
  949. $stmt = $select->query();
  950. $config = array(
  951. 'table' => $matchTable,
  952. 'data' => $stmt->fetchAll(Zend_Db::FETCH_ASSOC),
  953. 'rowClass' => $matchTable->getRowClass(),
  954. 'readOnly' => false,
  955. 'stored' => true
  956. );
  957. $rowsetClass = $matchTable->getRowsetClass();
  958. if (!class_exists($rowsetClass)) {
  959. try {
  960. require_once 'Zend/Loader.php';
  961. Zend_Loader::loadClass($rowsetClass);
  962. } catch (Zend_Exception $e) {
  963. require_once 'Zend/Db/Table/Row/Exception.php';
  964. throw new Zend_Db_Table_Row_Exception($e->getMessage(), $e->getCode(), $e);
  965. }
  966. }
  967. $rowset = new $rowsetClass($config);
  968. return $rowset;
  969. }
  970. /**
  971. * Turn magic function calls into non-magic function calls
  972. * to the above methods.
  973. *
  974. * @param string $method
  975. * @param array $args OPTIONAL Zend_Db_Table_Select query modifier
  976. * @return Zend_Db_Table_Row_Abstract|Zend_Db_Table_Rowset_Abstract
  977. * @throws Zend_Db_Table_Row_Exception If an invalid method is called.
  978. */
  979. public function __call($method, array $args)
  980. {
  981. $matches = array();
  982. if (count($args) && $args[0] instanceof Zend_Db_Table_Select) {
  983. $select = $args[0];
  984. } else {
  985. $select = null;
  986. }
  987. /**
  988. * Recognize methods for Has-Many cases:
  989. * findParent<Class>()
  990. * findParent<Class>By<Rule>()
  991. * Use the non-greedy pattern repeat modifier e.g. \w+?
  992. */
  993. if (preg_match('/^findParent(\w+?)(?:By(\w+))?$/', $method, $matches)) {
  994. $class = $matches[1];
  995. $ruleKey1 = isset($matches[2]) ? $matches[2] : null;
  996. return $this->findParentRow($class, $ruleKey1, $select);
  997. }
  998. /**
  999. * Recognize methods for Many-to-Many cases:
  1000. * find<Class1>Via<Class2>()
  1001. * find<Class1>Via<Class2>By<Rule>()
  1002. * find<Class1>Via<Class2>By<Rule1>And<Rule2>()
  1003. * Use the non-greedy pattern repeat modifier e.g. \w+?
  1004. */
  1005. if (preg_match('/^find(\w+?)Via(\w+?)(?:By(\w+?)(?:And(\w+))?)?$/', $method, $matches)) {
  1006. $class = $matches[1];
  1007. $viaClass = $matches[2];
  1008. $ruleKey1 = isset($matches[3]) ? $matches[3] : null;
  1009. $ruleKey2 = isset($matches[4]) ? $matches[4] : null;
  1010. return $this->findManyToManyRowset($class, $viaClass, $ruleKey1, $ruleKey2, $select);
  1011. }
  1012. /**
  1013. * Recognize methods for Belongs-To cases:
  1014. * find<Class>()
  1015. * find<Class>By<Rule>()
  1016. * Use the non-greedy pattern repeat modifier e.g. \w+?
  1017. */
  1018. if (preg_match('/^find(\w+?)(?:By(\w+))?$/', $method, $matches)) {
  1019. $class = $matches[1];
  1020. $ruleKey1 = isset($matches[2]) ? $matches[2] : null;
  1021. return $this->findDependentRowset($class, $ruleKey1, $select);
  1022. }
  1023. require_once 'Zend/Db/Table/Row/Exception.php';
  1024. throw new Zend_Db_Table_Row_Exception("Unrecognized method '$method()'");
  1025. }
  1026. /**
  1027. * _getTableFromString
  1028. *
  1029. * @param string $tableName
  1030. * @return Zend_Db_Table_Abstract
  1031. */
  1032. protected function _getTableFromString($tableName)
  1033. {
  1034. if ($this->_table instanceof Zend_Db_Table_Abstract) {
  1035. $tableDefinition = $this->_table->getDefinition();
  1036. if ($tableDefinition !== null && $tableDefinition->hasTableConfig($tableName)) {
  1037. return new Zend_Db_Table($tableName, $tableDefinition);
  1038. }
  1039. }
  1040. // assume the tableName is the class name
  1041. if (!class_exists($tableName)) {
  1042. try {
  1043. require_once 'Zend/Loader.php';
  1044. Zend_Loader::loadClass($tableName);
  1045. } catch (Zend_Exception $e) {
  1046. require_once 'Zend/Db/Table/Row/Exception.php';
  1047. throw new Zend_Db_Table_Row_Exception($e->getMessage(), $e->getCode(), $e);
  1048. }
  1049. }
  1050. $options = array();
  1051. if (($table = $this->_getTable())) {
  1052. $options['db'] = $table->getAdapter();
  1053. }
  1054. if (isset($tableDefinition) && $tableDefinition !== null) {
  1055. $options[Zend_Db_Table_Abstract::DEFINITION] = $tableDefinition;
  1056. }
  1057. return new $tableName($options);
  1058. }
  1059. }