Abstract.php 37 KB

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