Abstract.php 38 KB

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