Abstract.php 39 KB

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