Abstract.php 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208
  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-2010 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-2010 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. $this->_doUpdateCascade(); // broken out for overriding
  488. }
  489. /**
  490. * Execute the UPDATE (this may throw an exception)
  491. * Do this only if data values were changed.
  492. * Use the $diffData variable, so the UPDATE statement
  493. * includes SET terms only for data values that changed.
  494. */
  495. if (count($diffData) > 0) {
  496. $this->_getTable()->update($diffData, $where);
  497. }
  498. /**
  499. * Run post-UPDATE logic. Do this before the _refresh()
  500. * so the _postUpdate() function can tell the difference
  501. * between changed data and clean (pre-changed) data.
  502. */
  503. $this->_postUpdate();
  504. /**
  505. * Refresh the data just in case triggers in the RDBMS changed
  506. * any columns. Also this resets the _cleanData.
  507. */
  508. $this->_refresh();
  509. /**
  510. * Return the primary key value(s) as an array
  511. * if the key is compound or a scalar if the key
  512. * is a scalar.
  513. */
  514. $primaryKey = $this->_getPrimaryKey(true);
  515. if (count($primaryKey) == 1) {
  516. return current($primaryKey);
  517. }
  518. return $primaryKey;
  519. }
  520. protected function _doUpdateCascade()
  521. {
  522. $depTables = $this->_getTable()->getDependentTables();
  523. if (!empty($depTables)) {
  524. $pkNew = $this->_getPrimaryKey(true);
  525. $pkOld = $this->_getPrimaryKey(false);
  526. foreach ($depTables as $tableClass) {
  527. $t = $this->_getTableFromString($tableClass);
  528. $t->_cascadeUpdate($this->getTableClass(), $pkOld, $pkNew);
  529. }
  530. }
  531. }
  532. /**
  533. * Deletes existing rows.
  534. *
  535. * @return int The number of rows deleted.
  536. */
  537. public function delete()
  538. {
  539. /**
  540. * A read-only row cannot be deleted.
  541. */
  542. if ($this->_readOnly === true) {
  543. require_once 'Zend/Db/Table/Row/Exception.php';
  544. throw new Zend_Db_Table_Row_Exception('This row has been marked read-only');
  545. }
  546. $where = $this->_getWhereQuery();
  547. /**
  548. * Execute pre-DELETE logic
  549. */
  550. $this->_delete();
  551. /**
  552. * Execute cascading deletes against dependent tables
  553. */
  554. $depTables = $this->_getTable()->getDependentTables();
  555. if (!empty($depTables)) {
  556. $pk = $this->_getPrimaryKey();
  557. foreach ($depTables as $tableClass) {
  558. $t = $this->_getTableFromString($tableClass);
  559. $t->_cascadeDelete($this->getTableClass(), $pk);
  560. }
  561. }
  562. /**
  563. * Execute the DELETE (this may throw an exception)
  564. */
  565. $result = $this->_getTable()->delete($where);
  566. /**
  567. * Execute post-DELETE logic
  568. */
  569. $this->_postDelete();
  570. /**
  571. * Reset all fields to null to indicate that the row is not there
  572. */
  573. $this->_data = array_combine(
  574. array_keys($this->_data),
  575. array_fill(0, count($this->_data), null)
  576. );
  577. return $result;
  578. }
  579. public function getIterator()
  580. {
  581. return new ArrayIterator((array) $this->_data);
  582. }
  583. /**
  584. * Returns the column/value data as an array.
  585. *
  586. * @return array
  587. */
  588. public function toArray()
  589. {
  590. return (array)$this->_data;
  591. }
  592. /**
  593. * Sets all data in the row from an array.
  594. *
  595. * @param array $data
  596. * @return Zend_Db_Table_Row_Abstract Provides a fluent interface
  597. */
  598. public function setFromArray(array $data)
  599. {
  600. $data = array_intersect_key($data, $this->_data);
  601. foreach ($data as $columnName => $value) {
  602. $this->__set($columnName, $value);
  603. }
  604. return $this;
  605. }
  606. /**
  607. * Refreshes properties from the database.
  608. *
  609. * @return void
  610. */
  611. public function refresh()
  612. {
  613. return $this->_refresh();
  614. }
  615. /**
  616. * Retrieves an instance of the parent table.
  617. *
  618. * @return Zend_Db_Table_Abstract
  619. */
  620. protected function _getTable()
  621. {
  622. if (!$this->_connected) {
  623. require_once 'Zend/Db/Table/Row/Exception.php';
  624. throw new Zend_Db_Table_Row_Exception('Cannot save a Row unless it is connected');
  625. }
  626. return $this->_table;
  627. }
  628. /**
  629. * Retrieves an associative array of primary keys.
  630. *
  631. * @param bool $useDirty
  632. * @return array
  633. */
  634. protected function _getPrimaryKey($useDirty = true)
  635. {
  636. if (!is_array($this->_primary)) {
  637. require_once 'Zend/Db/Table/Row/Exception.php';
  638. throw new Zend_Db_Table_Row_Exception("The primary key must be set as an array");
  639. }
  640. $primary = array_flip($this->_primary);
  641. if ($useDirty) {
  642. $array = array_intersect_key($this->_data, $primary);
  643. } else {
  644. $array = array_intersect_key($this->_cleanData, $primary);
  645. }
  646. if (count($primary) != count($array)) {
  647. require_once 'Zend/Db/Table/Row/Exception.php';
  648. throw new Zend_Db_Table_Row_Exception("The specified Table '$this->_tableClass' does not have the same primary key as the Row");
  649. }
  650. return $array;
  651. }
  652. /**
  653. * Constructs where statement for retrieving row(s).
  654. *
  655. * @param bool $useDirty
  656. * @return array
  657. */
  658. protected function _getWhereQuery($useDirty = true)
  659. {
  660. $where = array();
  661. $db = $this->_getTable()->getAdapter();
  662. $primaryKey = $this->_getPrimaryKey($useDirty);
  663. $info = $this->_getTable()->info();
  664. $metadata = $info[Zend_Db_Table_Abstract::METADATA];
  665. // retrieve recently updated row using primary keys
  666. $where = array();
  667. foreach ($primaryKey as $column => $value) {
  668. $tableName = $db->quoteIdentifier($info[Zend_Db_Table_Abstract::NAME], true);
  669. $type = $metadata[$column]['DATA_TYPE'];
  670. $columnName = $db->quoteIdentifier($column, true);
  671. $where[] = $db->quoteInto("{$tableName}.{$columnName} = ?", $value, $type);
  672. }
  673. return $where;
  674. }
  675. /**
  676. * Refreshes properties from the database.
  677. *
  678. * @return void
  679. */
  680. protected function _refresh()
  681. {
  682. $where = $this->_getWhereQuery();
  683. $row = $this->_getTable()->fetchRow($where);
  684. if (null === $row) {
  685. require_once 'Zend/Db/Table/Row/Exception.php';
  686. throw new Zend_Db_Table_Row_Exception('Cannot refresh row as parent is missing');
  687. }
  688. $this->_data = $row->toArray();
  689. $this->_cleanData = $this->_data;
  690. $this->_modifiedFields = array();
  691. }
  692. /**
  693. * Allows pre-insert logic to be applied to row.
  694. * Subclasses may override this method.
  695. *
  696. * @return void
  697. */
  698. protected function _insert()
  699. {
  700. }
  701. /**
  702. * Allows post-insert logic to be applied to row.
  703. * Subclasses may override this method.
  704. *
  705. * @return void
  706. */
  707. protected function _postInsert()
  708. {
  709. }
  710. /**
  711. * Allows pre-update logic to be applied to row.
  712. * Subclasses may override this method.
  713. *
  714. * @return void
  715. */
  716. protected function _update()
  717. {
  718. }
  719. /**
  720. * Allows post-update logic to be applied to row.
  721. * Subclasses may override this method.
  722. *
  723. * @return void
  724. */
  725. protected function _postUpdate()
  726. {
  727. }
  728. /**
  729. * Allows pre-delete logic to be applied to row.
  730. * Subclasses may override this method.
  731. *
  732. * @return void
  733. */
  734. protected function _delete()
  735. {
  736. }
  737. /**
  738. * Allows post-delete logic to be applied to row.
  739. * Subclasses may override this method.
  740. *
  741. * @return void
  742. */
  743. protected function _postDelete()
  744. {
  745. }
  746. /**
  747. * Prepares a table reference for lookup.
  748. *
  749. * Ensures all reference keys are set and properly formatted.
  750. *
  751. * @param Zend_Db_Table_Abstract $dependentTable
  752. * @param Zend_Db_Table_Abstract $parentTable
  753. * @param string $ruleKey
  754. * @return array
  755. */
  756. protected function _prepareReference(Zend_Db_Table_Abstract $dependentTable, Zend_Db_Table_Abstract $parentTable, $ruleKey)
  757. {
  758. $parentTableName = (get_class($parentTable) === 'Zend_Db_Table') ? $parentTable->getDefinitionConfigName() : get_class($parentTable);
  759. $map = $dependentTable->getReference($parentTableName, $ruleKey);
  760. if (!isset($map[Zend_Db_Table_Abstract::REF_COLUMNS])) {
  761. $parentInfo = $parentTable->info();
  762. $map[Zend_Db_Table_Abstract::REF_COLUMNS] = array_values((array) $parentInfo['primary']);
  763. }
  764. $map[Zend_Db_Table_Abstract::COLUMNS] = (array) $map[Zend_Db_Table_Abstract::COLUMNS];
  765. $map[Zend_Db_Table_Abstract::REF_COLUMNS] = (array) $map[Zend_Db_Table_Abstract::REF_COLUMNS];
  766. return $map;
  767. }
  768. /**
  769. * Query a dependent table to retrieve rows matching the current row.
  770. *
  771. * @param string|Zend_Db_Table_Abstract $dependentTable
  772. * @param string OPTIONAL $ruleKey
  773. * @param Zend_Db_Table_Select OPTIONAL $select
  774. * @return Zend_Db_Table_Rowset_Abstract Query result from $dependentTable
  775. * @throws Zend_Db_Table_Row_Exception If $dependentTable is not a table or is not loadable.
  776. */
  777. public function findDependentRowset($dependentTable, $ruleKey = null, Zend_Db_Table_Select $select = null)
  778. {
  779. $db = $this->_getTable()->getAdapter();
  780. if (is_string($dependentTable)) {
  781. $dependentTable = $this->_getTableFromString($dependentTable);
  782. }
  783. if (!$dependentTable instanceof Zend_Db_Table_Abstract) {
  784. $type = gettype($dependentTable);
  785. if ($type == 'object') {
  786. $type = get_class($dependentTable);
  787. }
  788. require_once 'Zend/Db/Table/Row/Exception.php';
  789. throw new Zend_Db_Table_Row_Exception("Dependent table must be a Zend_Db_Table_Abstract, but it is $type");
  790. }
  791. // even if we are interacting between a table defined in a class and a
  792. // table via extension, ensure to persist the definition
  793. if (($tableDefinition = $this->_table->getDefinition()) !== null
  794. && ($dependentTable->getDefinition() == null)) {
  795. $dependentTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition));
  796. }
  797. if ($select === null) {
  798. $select = $dependentTable->select();
  799. } else {
  800. $select->setTable($dependentTable);
  801. }
  802. $map = $this->_prepareReference($dependentTable, $this->_getTable(), $ruleKey);
  803. for ($i = 0; $i < count($map[Zend_Db_Table_Abstract::COLUMNS]); ++$i) {
  804. $parentColumnName = $db->foldCase($map[Zend_Db_Table_Abstract::REF_COLUMNS][$i]);
  805. $value = $this->_data[$parentColumnName];
  806. // Use adapter from dependent table to ensure correct query construction
  807. $dependentDb = $dependentTable->getAdapter();
  808. $dependentColumnName = $dependentDb->foldCase($map[Zend_Db_Table_Abstract::COLUMNS][$i]);
  809. $dependentColumn = $dependentDb->quoteIdentifier($dependentColumnName, true);
  810. $dependentInfo = $dependentTable->info();
  811. $type = $dependentInfo[Zend_Db_Table_Abstract::METADATA][$dependentColumnName]['DATA_TYPE'];
  812. $select->where("$dependentColumn = ?", $value, $type);
  813. }
  814. return $dependentTable->fetchAll($select);
  815. }
  816. /**
  817. * Query a parent table to retrieve the single row matching the current row.
  818. *
  819. * @param string|Zend_Db_Table_Abstract $parentTable
  820. * @param string OPTIONAL $ruleKey
  821. * @param Zend_Db_Table_Select OPTIONAL $select
  822. * @return Zend_Db_Table_Row_Abstract Query result from $parentTable
  823. * @throws Zend_Db_Table_Row_Exception If $parentTable is not a table or is not loadable.
  824. */
  825. public function findParentRow($parentTable, $ruleKey = null, Zend_Db_Table_Select $select = null)
  826. {
  827. $db = $this->_getTable()->getAdapter();
  828. if (is_string($parentTable)) {
  829. $parentTable = $this->_getTableFromString($parentTable);
  830. }
  831. if (!$parentTable instanceof Zend_Db_Table_Abstract) {
  832. $type = gettype($parentTable);
  833. if ($type == 'object') {
  834. $type = get_class($parentTable);
  835. }
  836. require_once 'Zend/Db/Table/Row/Exception.php';
  837. throw new Zend_Db_Table_Row_Exception("Parent table must be a Zend_Db_Table_Abstract, but it is $type");
  838. }
  839. // even if we are interacting between a table defined in a class and a
  840. // table via extension, ensure to persist the definition
  841. if (($tableDefinition = $this->_table->getDefinition()) !== null
  842. && ($parentTable->getDefinition() == null)) {
  843. $parentTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition));
  844. }
  845. if ($select === null) {
  846. $select = $parentTable->select();
  847. } else {
  848. $select->setTable($parentTable);
  849. }
  850. $map = $this->_prepareReference($this->_getTable(), $parentTable, $ruleKey);
  851. // iterate the map, creating the proper wheres
  852. for ($i = 0; $i < count($map[Zend_Db_Table_Abstract::COLUMNS]); ++$i) {
  853. $dependentColumnName = $db->foldCase($map[Zend_Db_Table_Abstract::COLUMNS][$i]);
  854. $value = $this->_data[$dependentColumnName];
  855. // Use adapter from parent table to ensure correct query construction
  856. $parentDb = $parentTable->getAdapter();
  857. $parentColumnName = $parentDb->foldCase($map[Zend_Db_Table_Abstract::REF_COLUMNS][$i]);
  858. $parentColumn = $parentDb->quoteIdentifier($parentColumnName, true);
  859. $parentInfo = $parentTable->info();
  860. // determine where part
  861. $type = $parentInfo[Zend_Db_Table_Abstract::METADATA][$parentColumnName]['DATA_TYPE'];
  862. $nullable = $parentInfo[Zend_Db_Table_Abstract::METADATA][$parentColumnName]['NULLABLE'];
  863. if ($value === null && $nullable == true) {
  864. $select->where("$parentColumn IS NULL");
  865. } elseif ($value === null && $nullable == false) {
  866. return null;
  867. } else {
  868. $select->where("$parentColumn = ?", $value, $type);
  869. }
  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. $intersectionTable = $this->_getTableFromString($intersectionTable);
  888. }
  889. if (!$intersectionTable instanceof Zend_Db_Table_Abstract) {
  890. $type = gettype($intersectionTable);
  891. if ($type == 'object') {
  892. $type = get_class($intersectionTable);
  893. }
  894. require_once 'Zend/Db/Table/Row/Exception.php';
  895. throw new Zend_Db_Table_Row_Exception("Intersection table must be a Zend_Db_Table_Abstract, but it is $type");
  896. }
  897. // even if we are interacting between a table defined in a class and a
  898. // table via extension, ensure to persist the definition
  899. if (($tableDefinition = $this->_table->getDefinition()) !== null
  900. && ($intersectionTable->getDefinition() == null)) {
  901. $intersectionTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition));
  902. }
  903. if (is_string($matchTable)) {
  904. $matchTable = $this->_getTableFromString($matchTable);
  905. }
  906. if (! $matchTable instanceof Zend_Db_Table_Abstract) {
  907. $type = gettype($matchTable);
  908. if ($type == 'object') {
  909. $type = get_class($matchTable);
  910. }
  911. require_once 'Zend/Db/Table/Row/Exception.php';
  912. throw new Zend_Db_Table_Row_Exception("Match table must be a Zend_Db_Table_Abstract, but it is $type");
  913. }
  914. // even if we are interacting between a table defined in a class and a
  915. // table via extension, ensure to persist the definition
  916. if (($tableDefinition = $this->_table->getDefinition()) !== null
  917. && ($matchTable->getDefinition() == null)) {
  918. $matchTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition));
  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), array(), $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. if (!class_exists($rowsetClass)) {
  963. try {
  964. require_once 'Zend/Loader.php';
  965. Zend_Loader::loadClass($rowsetClass);
  966. } catch (Zend_Exception $e) {
  967. require_once 'Zend/Db/Table/Row/Exception.php';
  968. throw new Zend_Db_Table_Row_Exception($e->getMessage(), $e->getCode(), $e);
  969. }
  970. }
  971. $rowset = new $rowsetClass($config);
  972. return $rowset;
  973. }
  974. /**
  975. * Turn magic function calls into non-magic function calls
  976. * to the above methods.
  977. *
  978. * @param string $method
  979. * @param array $args OPTIONAL Zend_Db_Table_Select query modifier
  980. * @return Zend_Db_Table_Row_Abstract|Zend_Db_Table_Rowset_Abstract
  981. * @throws Zend_Db_Table_Row_Exception If an invalid method is called.
  982. */
  983. public function __call($method, array $args)
  984. {
  985. $matches = array();
  986. if (count($args) && $args[0] instanceof Zend_Db_Table_Select) {
  987. $select = $args[0];
  988. } else {
  989. $select = null;
  990. }
  991. /**
  992. * Recognize methods for Has-Many cases:
  993. * findParent<Class>()
  994. * findParent<Class>By<Rule>()
  995. * Use the non-greedy pattern repeat modifier e.g. \w+?
  996. */
  997. if (preg_match('/^findParent(\w+?)(?:By(\w+))?$/', $method, $matches)) {
  998. $class = $matches[1];
  999. $ruleKey1 = isset($matches[2]) ? $matches[2] : null;
  1000. return $this->findParentRow($class, $ruleKey1, $select);
  1001. }
  1002. /**
  1003. * Recognize methods for Many-to-Many cases:
  1004. * find<Class1>Via<Class2>()
  1005. * find<Class1>Via<Class2>By<Rule>()
  1006. * find<Class1>Via<Class2>By<Rule1>And<Rule2>()
  1007. * Use the non-greedy pattern repeat modifier e.g. \w+?
  1008. */
  1009. if (preg_match('/^find(\w+?)Via(\w+?)(?:By(\w+?)(?:And(\w+))?)?$/', $method, $matches)) {
  1010. $class = $matches[1];
  1011. $viaClass = $matches[2];
  1012. $ruleKey1 = isset($matches[3]) ? $matches[3] : null;
  1013. $ruleKey2 = isset($matches[4]) ? $matches[4] : null;
  1014. return $this->findManyToManyRowset($class, $viaClass, $ruleKey1, $ruleKey2, $select);
  1015. }
  1016. /**
  1017. * Recognize methods for Belongs-To cases:
  1018. * find<Class>()
  1019. * find<Class>By<Rule>()
  1020. * Use the non-greedy pattern repeat modifier e.g. \w+?
  1021. */
  1022. if (preg_match('/^find(\w+?)(?:By(\w+))?$/', $method, $matches)) {
  1023. $class = $matches[1];
  1024. $ruleKey1 = isset($matches[2]) ? $matches[2] : null;
  1025. return $this->findDependentRowset($class, $ruleKey1, $select);
  1026. }
  1027. require_once 'Zend/Db/Table/Row/Exception.php';
  1028. throw new Zend_Db_Table_Row_Exception("Unrecognized method '$method()'");
  1029. }
  1030. /**
  1031. * _getTableFromString
  1032. *
  1033. * @param string $tableName
  1034. * @return Zend_Db_Table_Abstract
  1035. */
  1036. protected function _getTableFromString($tableName)
  1037. {
  1038. if ($this->_table instanceof Zend_Db_Table_Abstract) {
  1039. $tableDefinition = $this->_table->getDefinition();
  1040. if ($tableDefinition !== null && $tableDefinition->hasTableConfig($tableName)) {
  1041. return new Zend_Db_Table($tableName, $tableDefinition);
  1042. }
  1043. }
  1044. // assume the tableName is the class name
  1045. if (!class_exists($tableName)) {
  1046. try {
  1047. require_once 'Zend/Loader.php';
  1048. Zend_Loader::loadClass($tableName);
  1049. } catch (Zend_Exception $e) {
  1050. require_once 'Zend/Db/Table/Row/Exception.php';
  1051. throw new Zend_Db_Table_Row_Exception($e->getMessage(), $e->getCode(), $e);
  1052. }
  1053. }
  1054. $options = array();
  1055. if (($table = $this->_getTable())) {
  1056. $options['db'] = $table->getAdapter();
  1057. }
  1058. if (isset($tableDefinition) && $tableDefinition !== null) {
  1059. $options[Zend_Db_Table_Abstract::DEFINITION] = $tableDefinition;
  1060. }
  1061. return new $tableName($options);
  1062. }
  1063. }