Abstract.php 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197
  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-2009 Zend Technologies USA Inc. (http://www.zend.com)
  19. * @license http://framework.zend.com/license/new-bsd New BSD License
  20. * @version $Id$
  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-2009 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
  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. * Does nothing
  271. * Required by the ArrayAccess implementation
  272. *
  273. * @param string $offset
  274. */
  275. public function offsetUnset($offset)
  276. {
  277. }
  278. /**
  279. * Initialize object
  280. *
  281. * Called from {@link __construct()} as final step of object instantiation.
  282. *
  283. * @return void
  284. */
  285. public function init()
  286. {
  287. }
  288. /**
  289. * Returns the table object, or null if this is disconnected row
  290. *
  291. * @return Zend_Db_Table_Abstract|null
  292. */
  293. public function getTable()
  294. {
  295. return $this->_table;
  296. }
  297. /**
  298. * Set the table object, to re-establish a live connection
  299. * to the database for a Row that has been de-serialized.
  300. *
  301. * @param Zend_Db_Table_Abstract $table
  302. * @return boolean
  303. * @throws Zend_Db_Table_Row_Exception
  304. */
  305. public function setTable(Zend_Db_Table_Abstract $table = null)
  306. {
  307. if ($table == null) {
  308. $this->_table = null;
  309. $this->_connected = false;
  310. return false;
  311. }
  312. $tableClass = get_class($table);
  313. if (! $table instanceof $this->_tableClass) {
  314. require_once 'Zend/Db/Table/Row/Exception.php';
  315. throw new Zend_Db_Table_Row_Exception("The specified Table is of class $tableClass, expecting class to be instance of $this->_tableClass");
  316. }
  317. $this->_table = $table;
  318. $this->_tableClass = $tableClass;
  319. $info = $this->_table->info();
  320. if ($info['cols'] != array_keys($this->_data)) {
  321. require_once 'Zend/Db/Table/Row/Exception.php';
  322. throw new Zend_Db_Table_Row_Exception('The specified Table does not have the same columns as the Row');
  323. }
  324. if (! array_intersect((array) $this->_primary, $info['primary']) == (array) $this->_primary) {
  325. require_once 'Zend/Db/Table/Row/Exception.php';
  326. throw new Zend_Db_Table_Row_Exception("The specified Table '$tableClass' does not have the same primary key as the Row");
  327. }
  328. $this->_connected = true;
  329. return true;
  330. }
  331. /**
  332. * Query the class name of the Table object for which this
  333. * Row was created.
  334. *
  335. * @return string
  336. */
  337. public function getTableClass()
  338. {
  339. return $this->_tableClass;
  340. }
  341. /**
  342. * Test the connected status of the row.
  343. *
  344. * @return boolean
  345. */
  346. public function isConnected()
  347. {
  348. return $this->_connected;
  349. }
  350. /**
  351. * Test the read-only status of the row.
  352. *
  353. * @return boolean
  354. */
  355. public function isReadOnly()
  356. {
  357. return $this->_readOnly;
  358. }
  359. /**
  360. * Set the read-only status of the row.
  361. *
  362. * @param boolean $flag
  363. * @return boolean
  364. */
  365. public function setReadOnly($flag)
  366. {
  367. $this->_readOnly = (bool) $flag;
  368. }
  369. /**
  370. * Returns an instance of the parent table's Zend_Db_Table_Select object.
  371. *
  372. * @return Zend_Db_Table_Select
  373. */
  374. public function select()
  375. {
  376. return $this->getTable()->select();
  377. }
  378. /**
  379. * Saves the properties to the database.
  380. *
  381. * This performs an intelligent insert/update, and reloads the
  382. * properties with fresh data from the table on success.
  383. *
  384. * @return mixed The primary key value(s), as an associative array if the
  385. * key is compound, or a scalar if the key is single-column.
  386. */
  387. public function save()
  388. {
  389. /**
  390. * If the _cleanData array is empty,
  391. * this is an INSERT of a new row.
  392. * Otherwise it is an UPDATE.
  393. */
  394. if (empty($this->_cleanData)) {
  395. return $this->_doInsert();
  396. } else {
  397. return $this->_doUpdate();
  398. }
  399. }
  400. /**
  401. * @return mixed The primary key value(s), as an associative array if the
  402. * key is compound, or a scalar if the key is single-column.
  403. */
  404. protected function _doInsert()
  405. {
  406. /**
  407. * A read-only row cannot be saved.
  408. */
  409. if ($this->_readOnly === true) {
  410. require_once 'Zend/Db/Table/Row/Exception.php';
  411. throw new Zend_Db_Table_Row_Exception('This row has been marked read-only');
  412. }
  413. /**
  414. * Run pre-INSERT logic
  415. */
  416. $this->_insert();
  417. /**
  418. * Execute the INSERT (this may throw an exception)
  419. */
  420. $data = array_intersect_key($this->_data, $this->_modifiedFields);
  421. $primaryKey = $this->_getTable()->insert($data);
  422. /**
  423. * Normalize the result to an array indexed by primary key column(s).
  424. * The table insert() method may return a scalar.
  425. */
  426. if (is_array($primaryKey)) {
  427. $newPrimaryKey = $primaryKey;
  428. } else {
  429. //ZF-6167 Use tempPrimaryKey temporary to avoid that zend encoding fails.
  430. $tempPrimaryKey = (array) $this->_primary;
  431. $newPrimaryKey = array(current($tempPrimaryKey) => $primaryKey);
  432. }
  433. /**
  434. * Save the new primary key value in _data. The primary key may have
  435. * been generated by a sequence or auto-increment mechanism, and this
  436. * merge should be done before the _postInsert() method is run, so the
  437. * new values are available for logging, etc.
  438. */
  439. $this->_data = array_merge($this->_data, $newPrimaryKey);
  440. /**
  441. * Run post-INSERT logic
  442. */
  443. $this->_postInsert();
  444. /**
  445. * Update the _cleanData to reflect that the data has been inserted.
  446. */
  447. $this->_refresh();
  448. return $primaryKey;
  449. }
  450. /**
  451. * @return mixed The primary key value(s), as an associative array if the
  452. * key is compound, or a scalar if the key is single-column.
  453. */
  454. protected function _doUpdate()
  455. {
  456. /**
  457. * A read-only row cannot be saved.
  458. */
  459. if ($this->_readOnly === true) {
  460. require_once 'Zend/Db/Table/Row/Exception.php';
  461. throw new Zend_Db_Table_Row_Exception('This row has been marked read-only');
  462. }
  463. /**
  464. * Get expressions for a WHERE clause
  465. * based on the primary key value(s).
  466. */
  467. $where = $this->_getWhereQuery(false);
  468. /**
  469. * Run pre-UPDATE logic
  470. */
  471. $this->_update();
  472. /**
  473. * Compare the data to the modified fields array to discover
  474. * which columns have been changed.
  475. */
  476. $diffData = array_intersect_key($this->_data, $this->_modifiedFields);
  477. /**
  478. * Were any of the changed columns part of the primary key?
  479. */
  480. $pkDiffData = array_intersect_key($diffData, array_flip((array)$this->_primary));
  481. /**
  482. * Execute cascading updates against dependent tables.
  483. * Do this only if primary key value(s) were changed.
  484. */
  485. if (count($pkDiffData) > 0) {
  486. $depTables = $this->_getTable()->getDependentTables();
  487. if (!empty($depTables)) {
  488. $pkNew = $this->_getPrimaryKey(true);
  489. $pkOld = $this->_getPrimaryKey(false);
  490. foreach ($depTables as $tableClass) {
  491. $t = $this->_getTableFromString($tableClass);
  492. $t->_cascadeUpdate($this->getTableClass(), $pkOld, $pkNew);
  493. }
  494. }
  495. }
  496. /**
  497. * Execute the UPDATE (this may throw an exception)
  498. * Do this only if data values were changed.
  499. * Use the $diffData variable, so the UPDATE statement
  500. * includes SET terms only for data values that changed.
  501. */
  502. if (count($diffData) > 0) {
  503. $this->_getTable()->update($diffData, $where);
  504. }
  505. /**
  506. * Run post-UPDATE logic. Do this before the _refresh()
  507. * so the _postUpdate() function can tell the difference
  508. * between changed data and clean (pre-changed) data.
  509. */
  510. $this->_postUpdate();
  511. /**
  512. * Refresh the data just in case triggers in the RDBMS changed
  513. * any columns. Also this resets the _cleanData.
  514. */
  515. $this->_refresh();
  516. /**
  517. * Return the primary key value(s) as an array
  518. * if the key is compound or a scalar if the key
  519. * is a scalar.
  520. */
  521. $primaryKey = $this->_getPrimaryKey(true);
  522. if (count($primaryKey) == 1) {
  523. return current($primaryKey);
  524. }
  525. return $primaryKey;
  526. }
  527. /**
  528. * Deletes existing rows.
  529. *
  530. * @return int The number of rows deleted.
  531. */
  532. public function delete()
  533. {
  534. /**
  535. * A read-only row cannot be deleted.
  536. */
  537. if ($this->_readOnly === true) {
  538. require_once 'Zend/Db/Table/Row/Exception.php';
  539. throw new Zend_Db_Table_Row_Exception('This row has been marked read-only');
  540. }
  541. $where = $this->_getWhereQuery();
  542. /**
  543. * Execute pre-DELETE logic
  544. */
  545. $this->_delete();
  546. /**
  547. * Execute cascading deletes against dependent tables
  548. */
  549. $depTables = $this->_getTable()->getDependentTables();
  550. if (!empty($depTables)) {
  551. $pk = $this->_getPrimaryKey();
  552. foreach ($depTables as $tableClass) {
  553. $t = $this->_getTableFromString($tableClass);
  554. $t->_cascadeDelete($this->getTableClass(), $pk);
  555. }
  556. }
  557. /**
  558. * Execute the DELETE (this may throw an exception)
  559. */
  560. $result = $this->_getTable()->delete($where);
  561. /**
  562. * Execute post-DELETE logic
  563. */
  564. $this->_postDelete();
  565. /**
  566. * Reset all fields to null to indicate that the row is not there
  567. */
  568. $this->_data = array_combine(
  569. array_keys($this->_data),
  570. array_fill(0, count($this->_data), null)
  571. );
  572. return $result;
  573. }
  574. /**
  575. * Returns the column/value data as an array.
  576. *
  577. * @return array
  578. */
  579. public function toArray()
  580. {
  581. return (array)$this->_data;
  582. }
  583. /**
  584. * Sets all data in the row from an array.
  585. *
  586. * @param array $data
  587. * @return Zend_Db_Table_Row_Abstract Provides a fluent interface
  588. */
  589. public function setFromArray(array $data)
  590. {
  591. $data = array_intersect_key($data, $this->_data);
  592. foreach ($data as $columnName => $value) {
  593. $this->__set($columnName, $value);
  594. }
  595. return $this;
  596. }
  597. /**
  598. * Refreshes properties from the database.
  599. *
  600. * @return void
  601. */
  602. public function refresh()
  603. {
  604. return $this->_refresh();
  605. }
  606. /**
  607. * Retrieves an instance of the parent table.
  608. *
  609. * @return Zend_Db_Table_Abstract
  610. */
  611. protected function _getTable()
  612. {
  613. if (!$this->_connected) {
  614. require_once 'Zend/Db/Table/Row/Exception.php';
  615. throw new Zend_Db_Table_Row_Exception('Cannot save a Row unless it is connected');
  616. }
  617. return $this->_table;
  618. }
  619. /**
  620. * Retrieves an associative array of primary keys.
  621. *
  622. * @param bool $useDirty
  623. * @return array
  624. */
  625. protected function _getPrimaryKey($useDirty = true)
  626. {
  627. if (!is_array($this->_primary)) {
  628. require_once 'Zend/Db/Table/Row/Exception.php';
  629. throw new Zend_Db_Table_Row_Exception("The primary key must be set as an array");
  630. }
  631. $primary = array_flip($this->_primary);
  632. if ($useDirty) {
  633. $array = array_intersect_key($this->_data, $primary);
  634. } else {
  635. $array = array_intersect_key($this->_cleanData, $primary);
  636. }
  637. if (count($primary) != count($array)) {
  638. require_once 'Zend/Db/Table/Row/Exception.php';
  639. throw new Zend_Db_Table_Row_Exception("The specified Table '$this->_tableClass' does not have the same primary key as the Row");
  640. }
  641. return $array;
  642. }
  643. /**
  644. * Constructs where statement for retrieving row(s).
  645. *
  646. * @param bool $useDirty
  647. * @return array
  648. */
  649. protected function _getWhereQuery($useDirty = true)
  650. {
  651. $where = array();
  652. $db = $this->_getTable()->getAdapter();
  653. $primaryKey = $this->_getPrimaryKey($useDirty);
  654. $info = $this->_getTable()->info();
  655. $metadata = $info[Zend_Db_Table_Abstract::METADATA];
  656. // retrieve recently updated row using primary keys
  657. $where = array();
  658. foreach ($primaryKey as $column => $value) {
  659. $tableName = $db->quoteIdentifier($info[Zend_Db_Table_Abstract::NAME], true);
  660. $type = $metadata[$column]['DATA_TYPE'];
  661. $columnName = $db->quoteIdentifier($column, true);
  662. $where[] = $db->quoteInto("{$tableName}.{$columnName} = ?", $value, $type);
  663. }
  664. return $where;
  665. }
  666. /**
  667. * Refreshes properties from the database.
  668. *
  669. * @return void
  670. */
  671. protected function _refresh()
  672. {
  673. $where = $this->_getWhereQuery();
  674. $row = $this->_getTable()->fetchRow($where);
  675. if (null === $row) {
  676. require_once 'Zend/Db/Table/Row/Exception.php';
  677. throw new Zend_Db_Table_Row_Exception('Cannot refresh row as parent is missing');
  678. }
  679. $this->_data = $row->toArray();
  680. $this->_cleanData = $this->_data;
  681. $this->_modifiedFields = array();
  682. }
  683. /**
  684. * Allows pre-insert logic to be applied to row.
  685. * Subclasses may override this method.
  686. *
  687. * @return void
  688. */
  689. protected function _insert()
  690. {
  691. }
  692. /**
  693. * Allows post-insert logic to be applied to row.
  694. * Subclasses may override this method.
  695. *
  696. * @return void
  697. */
  698. protected function _postInsert()
  699. {
  700. }
  701. /**
  702. * Allows pre-update logic to be applied to row.
  703. * Subclasses may override this method.
  704. *
  705. * @return void
  706. */
  707. protected function _update()
  708. {
  709. }
  710. /**
  711. * Allows post-update logic to be applied to row.
  712. * Subclasses may override this method.
  713. *
  714. * @return void
  715. */
  716. protected function _postUpdate()
  717. {
  718. }
  719. /**
  720. * Allows pre-delete logic to be applied to row.
  721. * Subclasses may override this method.
  722. *
  723. * @return void
  724. */
  725. protected function _delete()
  726. {
  727. }
  728. /**
  729. * Allows post-delete logic to be applied to row.
  730. * Subclasses may override this method.
  731. *
  732. * @return void
  733. */
  734. protected function _postDelete()
  735. {
  736. }
  737. /**
  738. * Prepares a table reference for lookup.
  739. *
  740. * Ensures all reference keys are set and properly formatted.
  741. *
  742. * @param Zend_Db_Table_Abstract $dependentTable
  743. * @param Zend_Db_Table_Abstract $parentTable
  744. * @param string $ruleKey
  745. * @return array
  746. */
  747. protected function _prepareReference(Zend_Db_Table_Abstract $dependentTable, Zend_Db_Table_Abstract $parentTable, $ruleKey)
  748. {
  749. $parentTableName = (get_class($parentTable) === 'Zend_Db_Table') ? $parentTable->getDefinitionConfigName() : get_class($parentTable);
  750. $map = $dependentTable->getReference($parentTableName, $ruleKey);
  751. if (!isset($map[Zend_Db_Table_Abstract::REF_COLUMNS])) {
  752. $parentInfo = $parentTable->info();
  753. $map[Zend_Db_Table_Abstract::REF_COLUMNS] = array_values((array) $parentInfo['primary']);
  754. }
  755. $map[Zend_Db_Table_Abstract::COLUMNS] = (array) $map[Zend_Db_Table_Abstract::COLUMNS];
  756. $map[Zend_Db_Table_Abstract::REF_COLUMNS] = (array) $map[Zend_Db_Table_Abstract::REF_COLUMNS];
  757. return $map;
  758. }
  759. /**
  760. * Query a dependent table to retrieve rows matching the current row.
  761. *
  762. * @param string|Zend_Db_Table_Abstract $dependentTable
  763. * @param string OPTIONAL $ruleKey
  764. * @param Zend_Db_Table_Select OPTIONAL $select
  765. * @return Zend_Db_Table_Rowset_Abstract Query result from $dependentTable
  766. * @throws Zend_Db_Table_Row_Exception If $dependentTable is not a table or is not loadable.
  767. */
  768. public function findDependentRowset($dependentTable, $ruleKey = null, Zend_Db_Table_Select $select = null)
  769. {
  770. $db = $this->_getTable()->getAdapter();
  771. if (is_string($dependentTable)) {
  772. $dependentTable = $this->_getTableFromString($dependentTable);
  773. }
  774. if (!$dependentTable instanceof Zend_Db_Table_Abstract) {
  775. $type = gettype($dependentTable);
  776. if ($type == 'object') {
  777. $type = get_class($dependentTable);
  778. }
  779. require_once 'Zend/Db/Table/Row/Exception.php';
  780. throw new Zend_Db_Table_Row_Exception("Dependent table must be a Zend_Db_Table_Abstract, but it is $type");
  781. }
  782. // even if we are interacting between a table defined in a class and a
  783. // table via extension, ensure to persist the definition
  784. if (($tableDefinition = $this->_table->getDefinition()) !== null
  785. && ($dependentTable->getDefinition() == null)) {
  786. $dependentTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition));
  787. }
  788. if ($select === null) {
  789. $select = $dependentTable->select();
  790. } else {
  791. $select->setTable($dependentTable);
  792. }
  793. $map = $this->_prepareReference($dependentTable, $this->_getTable(), $ruleKey);
  794. for ($i = 0; $i < count($map[Zend_Db_Table_Abstract::COLUMNS]); ++$i) {
  795. $parentColumnName = $db->foldCase($map[Zend_Db_Table_Abstract::REF_COLUMNS][$i]);
  796. $value = $this->_data[$parentColumnName];
  797. // Use adapter from dependent table to ensure correct query construction
  798. $dependentDb = $dependentTable->getAdapter();
  799. $dependentColumnName = $dependentDb->foldCase($map[Zend_Db_Table_Abstract::COLUMNS][$i]);
  800. $dependentColumn = $dependentDb->quoteIdentifier($dependentColumnName, true);
  801. $dependentInfo = $dependentTable->info();
  802. $type = $dependentInfo[Zend_Db_Table_Abstract::METADATA][$dependentColumnName]['DATA_TYPE'];
  803. $select->where("$dependentColumn = ?", $value, $type);
  804. }
  805. return $dependentTable->fetchAll($select);
  806. }
  807. /**
  808. * Query a parent table to retrieve the single row matching the current row.
  809. *
  810. * @param string|Zend_Db_Table_Abstract $parentTable
  811. * @param string OPTIONAL $ruleKey
  812. * @param Zend_Db_Table_Select OPTIONAL $select
  813. * @return Zend_Db_Table_Row_Abstract Query result from $parentTable
  814. * @throws Zend_Db_Table_Row_Exception If $parentTable is not a table or is not loadable.
  815. */
  816. public function findParentRow($parentTable, $ruleKey = null, Zend_Db_Table_Select $select = null)
  817. {
  818. $db = $this->_getTable()->getAdapter();
  819. if (is_string($parentTable)) {
  820. $parentTable = $this->_getTableFromString($parentTable);
  821. }
  822. if (!$parentTable instanceof Zend_Db_Table_Abstract) {
  823. $type = gettype($parentTable);
  824. if ($type == 'object') {
  825. $type = get_class($parentTable);
  826. }
  827. require_once 'Zend/Db/Table/Row/Exception.php';
  828. throw new Zend_Db_Table_Row_Exception("Parent table must be a Zend_Db_Table_Abstract, but it is $type");
  829. }
  830. // even if we are interacting between a table defined in a class and a
  831. // table via extension, ensure to persist the definition
  832. if (($tableDefinition = $this->_table->getDefinition()) !== null
  833. && ($parentTable->getDefinition() == null)) {
  834. $parentTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition));
  835. }
  836. if ($select === null) {
  837. $select = $parentTable->select();
  838. } else {
  839. $select->setTable($parentTable);
  840. }
  841. $map = $this->_prepareReference($this->_getTable(), $parentTable, $ruleKey);
  842. // iterate the map, creating the proper wheres
  843. for ($i = 0; $i < count($map[Zend_Db_Table_Abstract::COLUMNS]); ++$i) {
  844. $dependentColumnName = $db->foldCase($map[Zend_Db_Table_Abstract::COLUMNS][$i]);
  845. $value = $this->_data[$dependentColumnName];
  846. // Use adapter from parent table to ensure correct query construction
  847. $parentDb = $parentTable->getAdapter();
  848. $parentColumnName = $parentDb->foldCase($map[Zend_Db_Table_Abstract::REF_COLUMNS][$i]);
  849. $parentColumn = $parentDb->quoteIdentifier($parentColumnName, true);
  850. $parentInfo = $parentTable->info();
  851. // determine where part
  852. $type = $parentInfo[Zend_Db_Table_Abstract::METADATA][$parentColumnName]['DATA_TYPE'];
  853. $nullable = $parentInfo[Zend_Db_Table_Abstract::METADATA][$parentColumnName]['NULLABLE'];
  854. if ($value === null && $nullable == true) {
  855. $select->where("$parentColumn IS NULL");
  856. } elseif ($value === null && $nullable == false) {
  857. return null;
  858. } else {
  859. $select->where("$parentColumn = ?", $value, $type);
  860. }
  861. }
  862. return $parentTable->fetchRow($select);
  863. }
  864. /**
  865. * @param string|Zend_Db_Table_Abstract $matchTable
  866. * @param string|Zend_Db_Table_Abstract $intersectionTable
  867. * @param string OPTIONAL $callerRefRule
  868. * @param string OPTIONAL $matchRefRule
  869. * @param Zend_Db_Table_Select OPTIONAL $select
  870. * @return Zend_Db_Table_Rowset_Abstract Query result from $matchTable
  871. * @throws Zend_Db_Table_Row_Exception If $matchTable or $intersectionTable is not a table class or is not loadable.
  872. */
  873. public function findManyToManyRowset($matchTable, $intersectionTable, $callerRefRule = null,
  874. $matchRefRule = null, Zend_Db_Table_Select $select = null)
  875. {
  876. $db = $this->_getTable()->getAdapter();
  877. if (is_string($intersectionTable)) {
  878. $intersectionTable = $this->_getTableFromString($intersectionTable);
  879. }
  880. if (!$intersectionTable instanceof Zend_Db_Table_Abstract) {
  881. $type = gettype($intersectionTable);
  882. if ($type == 'object') {
  883. $type = get_class($intersectionTable);
  884. }
  885. require_once 'Zend/Db/Table/Row/Exception.php';
  886. throw new Zend_Db_Table_Row_Exception("Intersection table must be a Zend_Db_Table_Abstract, but it is $type");
  887. }
  888. // even if we are interacting between a table defined in a class and a
  889. // table via extension, ensure to persist the definition
  890. if (($tableDefinition = $this->_table->getDefinition()) !== null
  891. && ($intersectionTable->getDefinition() == null)) {
  892. $intersectionTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition));
  893. }
  894. if (is_string($matchTable)) {
  895. $matchTable = $this->_getTableFromString($matchTable);
  896. }
  897. if (! $matchTable instanceof Zend_Db_Table_Abstract) {
  898. $type = gettype($matchTable);
  899. if ($type == 'object') {
  900. $type = get_class($matchTable);
  901. }
  902. require_once 'Zend/Db/Table/Row/Exception.php';
  903. throw new Zend_Db_Table_Row_Exception("Match table must be a Zend_Db_Table_Abstract, but it is $type");
  904. }
  905. // even if we are interacting between a table defined in a class and a
  906. // table via extension, ensure to persist the definition
  907. if (($tableDefinition = $this->_table->getDefinition()) !== null
  908. && ($matchTable->getDefinition() == null)) {
  909. $matchTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition));
  910. }
  911. if ($select === null) {
  912. $select = $matchTable->select();
  913. } else {
  914. $select->setTable($matchTable);
  915. }
  916. // Use adapter from intersection table to ensure correct query construction
  917. $interInfo = $intersectionTable->info();
  918. $interDb = $intersectionTable->getAdapter();
  919. $interName = $interInfo['name'];
  920. $interSchema = isset($interInfo['schema']) ? $interInfo['schema'] : null;
  921. $matchInfo = $matchTable->info();
  922. $matchName = $matchInfo['name'];
  923. $matchSchema = isset($matchInfo['schema']) ? $matchInfo['schema'] : null;
  924. $matchMap = $this->_prepareReference($intersectionTable, $matchTable, $matchRefRule);
  925. for ($i = 0; $i < count($matchMap[Zend_Db_Table_Abstract::COLUMNS]); ++$i) {
  926. $interCol = $interDb->quoteIdentifier('i' . '.' . $matchMap[Zend_Db_Table_Abstract::COLUMNS][$i], true);
  927. $matchCol = $interDb->quoteIdentifier('m' . '.' . $matchMap[Zend_Db_Table_Abstract::REF_COLUMNS][$i], true);
  928. $joinCond[] = "$interCol = $matchCol";
  929. }
  930. $joinCond = implode(' AND ', $joinCond);
  931. $select->from(array('i' => $interName), Zend_Db_Select::SQL_WILDCARD, $interSchema)
  932. ->joinInner(array('m' => $matchName), $joinCond, Zend_Db_Select::SQL_WILDCARD, $matchSchema)
  933. ->setIntegrityCheck(false);
  934. $callerMap = $this->_prepareReference($intersectionTable, $this->_getTable(), $callerRefRule);
  935. for ($i = 0; $i < count($callerMap[Zend_Db_Table_Abstract::COLUMNS]); ++$i) {
  936. $callerColumnName = $db->foldCase($callerMap[Zend_Db_Table_Abstract::REF_COLUMNS][$i]);
  937. $value = $this->_data[$callerColumnName];
  938. $interColumnName = $interDb->foldCase($callerMap[Zend_Db_Table_Abstract::COLUMNS][$i]);
  939. $interCol = $interDb->quoteIdentifier("i.$interColumnName", true);
  940. $interInfo = $intersectionTable->info();
  941. $type = $interInfo[Zend_Db_Table_Abstract::METADATA][$interColumnName]['DATA_TYPE'];
  942. $select->where($interDb->quoteInto("$interCol = ?", $value, $type));
  943. }
  944. $stmt = $select->query();
  945. $config = array(
  946. 'table' => $matchTable,
  947. 'data' => $stmt->fetchAll(Zend_Db::FETCH_ASSOC),
  948. 'rowClass' => $matchTable->getRowClass(),
  949. 'readOnly' => false,
  950. 'stored' => true
  951. );
  952. $rowsetClass = $matchTable->getRowsetClass();
  953. if (!class_exists($rowsetClass)) {
  954. try {
  955. require_once 'Zend/Loader.php';
  956. Zend_Loader::loadClass($rowsetClass);
  957. } catch (Zend_Exception $e) {
  958. require_once 'Zend/Db/Table/Row/Exception.php';
  959. throw new Zend_Db_Table_Row_Exception($e->getMessage());
  960. }
  961. }
  962. $rowset = new $rowsetClass($config);
  963. return $rowset;
  964. }
  965. /**
  966. * Turn magic function calls into non-magic function calls
  967. * to the above methods.
  968. *
  969. * @param string $method
  970. * @param array $args OPTIONAL Zend_Db_Table_Select query modifier
  971. * @return Zend_Db_Table_Row_Abstract|Zend_Db_Table_Rowset_Abstract
  972. * @throws Zend_Db_Table_Row_Exception If an invalid method is called.
  973. */
  974. public function __call($method, array $args)
  975. {
  976. $matches = array();
  977. if (count($args) && $args[0] instanceof Zend_Db_Table_Select) {
  978. $select = $args[0];
  979. } else {
  980. $select = null;
  981. }
  982. /**
  983. * Recognize methods for Has-Many cases:
  984. * findParent<Class>()
  985. * findParent<Class>By<Rule>()
  986. * Use the non-greedy pattern repeat modifier e.g. \w+?
  987. */
  988. if (preg_match('/^findParent(\w+?)(?:By(\w+))?$/', $method, $matches)) {
  989. $class = $matches[1];
  990. $ruleKey1 = isset($matches[2]) ? $matches[2] : null;
  991. return $this->findParentRow($class, $ruleKey1, $select);
  992. }
  993. /**
  994. * Recognize methods for Many-to-Many cases:
  995. * find<Class1>Via<Class2>()
  996. * find<Class1>Via<Class2>By<Rule>()
  997. * find<Class1>Via<Class2>By<Rule1>And<Rule2>()
  998. * Use the non-greedy pattern repeat modifier e.g. \w+?
  999. */
  1000. if (preg_match('/^find(\w+?)Via(\w+?)(?:By(\w+?)(?:And(\w+))?)?$/', $method, $matches)) {
  1001. $class = $matches[1];
  1002. $viaClass = $matches[2];
  1003. $ruleKey1 = isset($matches[3]) ? $matches[3] : null;
  1004. $ruleKey2 = isset($matches[4]) ? $matches[4] : null;
  1005. return $this->findManyToManyRowset($class, $viaClass, $ruleKey1, $ruleKey2, $select);
  1006. }
  1007. /**
  1008. * Recognize methods for Belongs-To cases:
  1009. * find<Class>()
  1010. * find<Class>By<Rule>()
  1011. * Use the non-greedy pattern repeat modifier e.g. \w+?
  1012. */
  1013. if (preg_match('/^find(\w+?)(?:By(\w+))?$/', $method, $matches)) {
  1014. $class = $matches[1];
  1015. $ruleKey1 = isset($matches[2]) ? $matches[2] : null;
  1016. return $this->findDependentRowset($class, $ruleKey1, $select);
  1017. }
  1018. require_once 'Zend/Db/Table/Row/Exception.php';
  1019. throw new Zend_Db_Table_Row_Exception("Unrecognized method '$method()'");
  1020. }
  1021. /**
  1022. * _getTableFromString
  1023. *
  1024. * @param string $tableName
  1025. * @return Zend_Db_Table_Abstract
  1026. */
  1027. protected function _getTableFromString($tableName)
  1028. {
  1029. if ($this->_table instanceof Zend_Db_Table_Abstract) {
  1030. $tableDefinition = $this->_table->getDefinition();
  1031. if ($tableDefinition !== null && $tableDefinition->hasTableConfig($tableName)) {
  1032. return new Zend_Db_Table($tableName, $tableDefinition);
  1033. }
  1034. }
  1035. // assume the tableName is the class name
  1036. if (!class_exists($tableName)) {
  1037. try {
  1038. require_once 'Zend/Loader.php';
  1039. Zend_Loader::loadClass($tableName);
  1040. } catch (Zend_Exception $e) {
  1041. require_once 'Zend/Db/Table/Row/Exception.php';
  1042. throw new Zend_Db_Table_Row_Exception($e->getMessage());
  1043. }
  1044. }
  1045. $options = array();
  1046. if (($table = $this->_getTable())) {
  1047. $options['db'] = $table->getAdapter();
  1048. }
  1049. if (isset($tableDefinition) && $tableDefinition !== null) {
  1050. $options[Zend_Db_Table_Abstract::DEFINITION] = $tableDefinition;
  1051. }
  1052. return new $tableName($options);
  1053. }
  1054. }