File indexing completed on 2025-01-19 05:21:02
0001 <?php 0002 /** 0003 * Zend Framework 0004 * 0005 * LICENSE 0006 * 0007 * This source file is subject to the new BSD license that is bundled 0008 * with this package in the file LICENSE.txt. 0009 * It is also available through the world-wide-web at this URL: 0010 * http://framework.zend.com/license/new-bsd 0011 * If you did not receive a copy of the license and are unable to 0012 * obtain it through the world-wide-web, please send an email 0013 * to license@zend.com so we can send you a copy immediately. 0014 * 0015 * @category Zend 0016 * @package Zend_Db 0017 * @subpackage Table 0018 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0019 * @license http://framework.zend.com/license/new-bsd New BSD License 0020 * @version $Id$ 0021 */ 0022 0023 /** 0024 * @see Zend_Db 0025 */ 0026 // require_once 'Zend/Db.php'; 0027 0028 /** 0029 * @category Zend 0030 * @package Zend_Db 0031 * @subpackage Table 0032 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0033 * @license http://framework.zend.com/license/new-bsd New BSD License 0034 */ 0035 abstract class Zend_Db_Table_Row_Abstract implements ArrayAccess, IteratorAggregate 0036 { 0037 0038 /** 0039 * The data for each column in the row (column_name => value). 0040 * The keys must match the physical names of columns in the 0041 * table for which this row is defined. 0042 * 0043 * @var array 0044 */ 0045 protected $_data = array(); 0046 0047 /** 0048 * This is set to a copy of $_data when the data is fetched from 0049 * a database, specified as a new tuple in the constructor, or 0050 * when dirty data is posted to the database with save(). 0051 * 0052 * @var array 0053 */ 0054 protected $_cleanData = array(); 0055 0056 /** 0057 * Tracks columns where data has been updated. Allows more specific insert and 0058 * update operations. 0059 * 0060 * @var array 0061 */ 0062 protected $_modifiedFields = array(); 0063 0064 /** 0065 * Zend_Db_Table_Abstract parent class or instance. 0066 * 0067 * @var Zend_Db_Table_Abstract 0068 */ 0069 protected $_table = null; 0070 0071 /** 0072 * Connected is true if we have a reference to a live 0073 * Zend_Db_Table_Abstract object. 0074 * This is false after the Rowset has been deserialized. 0075 * 0076 * @var boolean 0077 */ 0078 protected $_connected = true; 0079 0080 /** 0081 * A row is marked read only if it contains columns that are not physically represented within 0082 * the database schema (e.g. evaluated columns/Zend_Db_Expr columns). This can also be passed 0083 * as a run-time config options as a means of protecting row data. 0084 * 0085 * @var boolean 0086 */ 0087 protected $_readOnly = false; 0088 0089 /** 0090 * Name of the class of the Zend_Db_Table_Abstract object. 0091 * 0092 * @var string 0093 */ 0094 protected $_tableClass = null; 0095 0096 /** 0097 * Primary row key(s). 0098 * 0099 * @var array 0100 */ 0101 protected $_primary; 0102 0103 /** 0104 * Constructor. 0105 * 0106 * Supported params for $config are:- 0107 * - table = class name or object of type Zend_Db_Table_Abstract 0108 * - data = values of columns in this row. 0109 * 0110 * @param array $config OPTIONAL Array of user-specified config options. 0111 * @return void 0112 * @throws Zend_Db_Table_Row_Exception 0113 */ 0114 public function __construct(array $config = array()) 0115 { 0116 if (isset($config['table']) && $config['table'] instanceof Zend_Db_Table_Abstract) { 0117 $this->_table = $config['table']; 0118 $this->_tableClass = get_class($this->_table); 0119 } elseif ($this->_tableClass !== null) { 0120 $this->_table = $this->_getTableFromString($this->_tableClass); 0121 } 0122 0123 if (isset($config['data'])) { 0124 if (!is_array($config['data'])) { 0125 // require_once 'Zend/Db/Table/Row/Exception.php'; 0126 throw new Zend_Db_Table_Row_Exception('Data must be an array'); 0127 } 0128 $this->_data = $config['data']; 0129 } 0130 if (isset($config['stored']) && $config['stored'] === true) { 0131 $this->_cleanData = $this->_data; 0132 } 0133 0134 if (isset($config['readOnly']) && $config['readOnly'] === true) { 0135 $this->setReadOnly(true); 0136 } 0137 0138 // Retrieve primary keys from table schema 0139 if (($table = $this->_getTable())) { 0140 $info = $table->info(); 0141 $this->_primary = (array) $info['primary']; 0142 } 0143 0144 $this->init(); 0145 } 0146 0147 /** 0148 * Transform a column name from the user-specified form 0149 * to the physical form used in the database. 0150 * You can override this method in a custom Row class 0151 * to implement column name mappings, for example inflection. 0152 * 0153 * @param string $columnName Column name given. 0154 * @return string The column name after transformation applied (none by default). 0155 * @throws Zend_Db_Table_Row_Exception if the $columnName is not a string. 0156 */ 0157 protected function _transformColumn($columnName) 0158 { 0159 if (!is_string($columnName)) { 0160 // require_once 'Zend/Db/Table/Row/Exception.php'; 0161 throw new Zend_Db_Table_Row_Exception('Specified column is not a string'); 0162 } 0163 // Perform no transformation by default 0164 return $columnName; 0165 } 0166 0167 /** 0168 * Retrieve row field value 0169 * 0170 * @param string $columnName The user-specified column name. 0171 * @return string The corresponding column value. 0172 * @throws Zend_Db_Table_Row_Exception if the $columnName is not a column in the row. 0173 */ 0174 public function __get($columnName) 0175 { 0176 $columnName = $this->_transformColumn($columnName); 0177 if (!array_key_exists($columnName, $this->_data)) { 0178 // require_once 'Zend/Db/Table/Row/Exception.php'; 0179 throw new Zend_Db_Table_Row_Exception("Specified column \"$columnName\" is not in the row"); 0180 } 0181 return $this->_data[$columnName]; 0182 } 0183 0184 /** 0185 * Set row field value 0186 * 0187 * @param string $columnName The column key. 0188 * @param mixed $value The value for the property. 0189 * @return void 0190 * @throws Zend_Db_Table_Row_Exception 0191 */ 0192 public function __set($columnName, $value) 0193 { 0194 $columnName = $this->_transformColumn($columnName); 0195 if (!array_key_exists($columnName, $this->_data)) { 0196 // require_once 'Zend/Db/Table/Row/Exception.php'; 0197 throw new Zend_Db_Table_Row_Exception("Specified column \"$columnName\" is not in the row"); 0198 } 0199 $this->_data[$columnName] = $value; 0200 $this->_modifiedFields[$columnName] = true; 0201 } 0202 0203 /** 0204 * Unset row field value 0205 * 0206 * @param string $columnName The column key. 0207 * @return Zend_Db_Table_Row_Abstract 0208 * @throws Zend_Db_Table_Row_Exception 0209 */ 0210 public function __unset($columnName) 0211 { 0212 $columnName = $this->_transformColumn($columnName); 0213 if (!array_key_exists($columnName, $this->_data)) { 0214 // require_once 'Zend/Db/Table/Row/Exception.php'; 0215 throw new Zend_Db_Table_Row_Exception("Specified column \"$columnName\" is not in the row"); 0216 } 0217 if ($this->isConnected() && in_array($columnName, $this->_table->info('primary'))) { 0218 // require_once 'Zend/Db/Table/Row/Exception.php'; 0219 throw new Zend_Db_Table_Row_Exception("Specified column \"$columnName\" is a primary key and should not be unset"); 0220 } 0221 unset($this->_data[$columnName]); 0222 return $this; 0223 } 0224 0225 /** 0226 * Test existence of row field 0227 * 0228 * @param string $columnName The column key. 0229 * @return boolean 0230 */ 0231 public function __isset($columnName) 0232 { 0233 $columnName = $this->_transformColumn($columnName); 0234 return array_key_exists($columnName, $this->_data); 0235 } 0236 0237 /** 0238 * Store table, primary key and data in serialized object 0239 * 0240 * @return array 0241 */ 0242 public function __sleep() 0243 { 0244 return array('_tableClass', '_primary', '_data', '_cleanData', '_readOnly' ,'_modifiedFields'); 0245 } 0246 0247 /** 0248 * Setup to do on wakeup. 0249 * A de-serialized Row should not be assumed to have access to a live 0250 * database connection, so set _connected = false. 0251 * 0252 * @return void 0253 */ 0254 public function __wakeup() 0255 { 0256 $this->_connected = false; 0257 } 0258 0259 /** 0260 * Proxy to __isset 0261 * Required by the ArrayAccess implementation 0262 * 0263 * @param string $offset 0264 * @return boolean 0265 */ 0266 public function offsetExists($offset) 0267 { 0268 return $this->__isset($offset); 0269 } 0270 0271 /** 0272 * Proxy to __get 0273 * Required by the ArrayAccess implementation 0274 * 0275 * @param string $offset 0276 * @return string 0277 */ 0278 public function offsetGet($offset) 0279 { 0280 return $this->__get($offset); 0281 } 0282 0283 /** 0284 * Proxy to __set 0285 * Required by the ArrayAccess implementation 0286 * 0287 * @param string $offset 0288 * @param mixed $value 0289 */ 0290 public function offsetSet($offset, $value) 0291 { 0292 $this->__set($offset, $value); 0293 } 0294 0295 /** 0296 * Proxy to __unset 0297 * Required by the ArrayAccess implementation 0298 * 0299 * @param string $offset 0300 */ 0301 public function offsetUnset($offset) 0302 { 0303 return $this->__unset($offset); 0304 } 0305 0306 /** 0307 * Initialize object 0308 * 0309 * Called from {@link __construct()} as final step of object instantiation. 0310 * 0311 * @return void 0312 */ 0313 public function init() 0314 { 0315 } 0316 0317 /** 0318 * Returns the table object, or null if this is disconnected row 0319 * 0320 * @return Zend_Db_Table_Abstract|null 0321 */ 0322 public function getTable() 0323 { 0324 return $this->_table; 0325 } 0326 0327 /** 0328 * Set the table object, to re-establish a live connection 0329 * to the database for a Row that has been de-serialized. 0330 * 0331 * @param Zend_Db_Table_Abstract $table 0332 * @return boolean 0333 * @throws Zend_Db_Table_Row_Exception 0334 */ 0335 public function setTable(Zend_Db_Table_Abstract $table = null) 0336 { 0337 if ($table == null) { 0338 $this->_table = null; 0339 $this->_connected = false; 0340 return false; 0341 } 0342 0343 $tableClass = get_class($table); 0344 if (! $table instanceof $this->_tableClass) { 0345 // require_once 'Zend/Db/Table/Row/Exception.php'; 0346 throw new Zend_Db_Table_Row_Exception("The specified Table is of class $tableClass, expecting class to be instance of $this->_tableClass"); 0347 } 0348 0349 $this->_table = $table; 0350 $this->_tableClass = $tableClass; 0351 0352 $info = $this->_table->info(); 0353 0354 if ($info['cols'] != array_keys($this->_data)) { 0355 // require_once 'Zend/Db/Table/Row/Exception.php'; 0356 throw new Zend_Db_Table_Row_Exception('The specified Table does not have the same columns as the Row'); 0357 } 0358 0359 if (! array_intersect((array) $this->_primary, $info['primary']) == (array) $this->_primary) { 0360 0361 // require_once 'Zend/Db/Table/Row/Exception.php'; 0362 throw new Zend_Db_Table_Row_Exception("The specified Table '$tableClass' does not have the same primary key as the Row"); 0363 } 0364 0365 $this->_connected = true; 0366 return true; 0367 } 0368 0369 /** 0370 * Query the class name of the Table object for which this 0371 * Row was created. 0372 * 0373 * @return string 0374 */ 0375 public function getTableClass() 0376 { 0377 return $this->_tableClass; 0378 } 0379 0380 /** 0381 * Test the connected status of the row. 0382 * 0383 * @return boolean 0384 */ 0385 public function isConnected() 0386 { 0387 return $this->_connected; 0388 } 0389 0390 /** 0391 * Test the read-only status of the row. 0392 * 0393 * @return boolean 0394 */ 0395 public function isReadOnly() 0396 { 0397 return $this->_readOnly; 0398 } 0399 0400 /** 0401 * Set the read-only status of the row. 0402 * 0403 * @param boolean $flag 0404 * @return boolean 0405 */ 0406 public function setReadOnly($flag) 0407 { 0408 $this->_readOnly = (bool) $flag; 0409 } 0410 0411 /** 0412 * Returns an instance of the parent table's Zend_Db_Table_Select object. 0413 * 0414 * @return Zend_Db_Table_Select 0415 */ 0416 public function select() 0417 { 0418 return $this->getTable()->select(); 0419 } 0420 0421 /** 0422 * Saves the properties to the database. 0423 * 0424 * This performs an intelligent insert/update, and reloads the 0425 * properties with fresh data from the table on success. 0426 * 0427 * @return mixed The primary key value(s), as an associative array if the 0428 * key is compound, or a scalar if the key is single-column. 0429 */ 0430 public function save() 0431 { 0432 /** 0433 * If the _cleanData array is empty, 0434 * this is an INSERT of a new row. 0435 * Otherwise it is an UPDATE. 0436 */ 0437 if (empty($this->_cleanData)) { 0438 return $this->_doInsert(); 0439 } else { 0440 return $this->_doUpdate(); 0441 } 0442 } 0443 0444 /** 0445 * @return mixed The primary key value(s), as an associative array if the 0446 * key is compound, or a scalar if the key is single-column. 0447 */ 0448 protected function _doInsert() 0449 { 0450 /** 0451 * A read-only row cannot be saved. 0452 */ 0453 if ($this->_readOnly === true) { 0454 // require_once 'Zend/Db/Table/Row/Exception.php'; 0455 throw new Zend_Db_Table_Row_Exception('This row has been marked read-only'); 0456 } 0457 0458 /** 0459 * Run pre-INSERT logic 0460 */ 0461 $this->_insert(); 0462 0463 /** 0464 * Execute the INSERT (this may throw an exception) 0465 */ 0466 $data = array_intersect_key($this->_data, $this->_modifiedFields); 0467 $primaryKey = $this->_getTable()->insert($data); 0468 0469 /** 0470 * Normalize the result to an array indexed by primary key column(s). 0471 * The table insert() method may return a scalar. 0472 */ 0473 if (is_array($primaryKey)) { 0474 $newPrimaryKey = $primaryKey; 0475 } else { 0476 //ZF-6167 Use tempPrimaryKey temporary to avoid that zend encoding fails. 0477 $tempPrimaryKey = (array) $this->_primary; 0478 $newPrimaryKey = array(current($tempPrimaryKey) => $primaryKey); 0479 } 0480 0481 /** 0482 * Save the new primary key value in _data. The primary key may have 0483 * been generated by a sequence or auto-increment mechanism, and this 0484 * merge should be done before the _postInsert() method is run, so the 0485 * new values are available for logging, etc. 0486 */ 0487 $this->_data = array_merge($this->_data, $newPrimaryKey); 0488 0489 /** 0490 * Run post-INSERT logic 0491 */ 0492 $this->_postInsert(); 0493 0494 /** 0495 * Update the _cleanData to reflect that the data has been inserted. 0496 */ 0497 $this->_refresh(); 0498 0499 return $primaryKey; 0500 } 0501 0502 /** 0503 * @return mixed The primary key value(s), as an associative array if the 0504 * key is compound, or a scalar if the key is single-column. 0505 */ 0506 protected function _doUpdate() 0507 { 0508 /** 0509 * A read-only row cannot be saved. 0510 */ 0511 if ($this->_readOnly === true) { 0512 // require_once 'Zend/Db/Table/Row/Exception.php'; 0513 throw new Zend_Db_Table_Row_Exception('This row has been marked read-only'); 0514 } 0515 0516 /** 0517 * Get expressions for a WHERE clause 0518 * based on the primary key value(s). 0519 */ 0520 $where = $this->_getWhereQuery(false); 0521 0522 /** 0523 * Run pre-UPDATE logic 0524 */ 0525 $this->_update(); 0526 0527 /** 0528 * Compare the data to the modified fields array to discover 0529 * which columns have been changed. 0530 */ 0531 $diffData = array_intersect_key($this->_data, $this->_modifiedFields); 0532 0533 /** 0534 * Were any of the changed columns part of the primary key? 0535 */ 0536 $pkDiffData = array_intersect_key($diffData, array_flip((array)$this->_primary)); 0537 0538 /** 0539 * Execute cascading updates against dependent tables. 0540 * Do this only if primary key value(s) were changed. 0541 */ 0542 if (count($pkDiffData) > 0) { 0543 $depTables = $this->_getTable()->getDependentTables(); 0544 if (!empty($depTables)) { 0545 $pkNew = $this->_getPrimaryKey(true); 0546 $pkOld = $this->_getPrimaryKey(false); 0547 foreach ($depTables as $tableClass) { 0548 $t = $this->_getTableFromString($tableClass); 0549 $t->_cascadeUpdate($this->getTableClass(), $pkOld, $pkNew); 0550 } 0551 } 0552 } 0553 0554 /** 0555 * Execute the UPDATE (this may throw an exception) 0556 * Do this only if data values were changed. 0557 * Use the $diffData variable, so the UPDATE statement 0558 * includes SET terms only for data values that changed. 0559 */ 0560 if (count($diffData) > 0) { 0561 $this->_getTable()->update($diffData, $where); 0562 } 0563 0564 /** 0565 * Run post-UPDATE logic. Do this before the _refresh() 0566 * so the _postUpdate() function can tell the difference 0567 * between changed data and clean (pre-changed) data. 0568 */ 0569 $this->_postUpdate(); 0570 0571 /** 0572 * Refresh the data just in case triggers in the RDBMS changed 0573 * any columns. Also this resets the _cleanData. 0574 */ 0575 $this->_refresh(); 0576 0577 /** 0578 * Return the primary key value(s) as an array 0579 * if the key is compound or a scalar if the key 0580 * is a scalar. 0581 */ 0582 $primaryKey = $this->_getPrimaryKey(true); 0583 if (count($primaryKey) == 1) { 0584 return current($primaryKey); 0585 } 0586 0587 return $primaryKey; 0588 } 0589 0590 /** 0591 * Deletes existing rows. 0592 * 0593 * @return int The number of rows deleted. 0594 */ 0595 public function delete() 0596 { 0597 /** 0598 * A read-only row cannot be deleted. 0599 */ 0600 if ($this->_readOnly === true) { 0601 // require_once 'Zend/Db/Table/Row/Exception.php'; 0602 throw new Zend_Db_Table_Row_Exception('This row has been marked read-only'); 0603 } 0604 0605 $where = $this->_getWhereQuery(); 0606 0607 /** 0608 * Execute pre-DELETE logic 0609 */ 0610 $this->_delete(); 0611 0612 /** 0613 * Execute cascading deletes against dependent tables 0614 */ 0615 $depTables = $this->_getTable()->getDependentTables(); 0616 if (!empty($depTables)) { 0617 $pk = $this->_getPrimaryKey(); 0618 foreach ($depTables as $tableClass) { 0619 $t = $this->_getTableFromString($tableClass); 0620 $t->_cascadeDelete($this->getTableClass(), $pk); 0621 } 0622 } 0623 0624 /** 0625 * Execute the DELETE (this may throw an exception) 0626 */ 0627 $result = $this->_getTable()->delete($where); 0628 0629 /** 0630 * Execute post-DELETE logic 0631 */ 0632 $this->_postDelete(); 0633 0634 /** 0635 * Reset all fields to null to indicate that the row is not there 0636 */ 0637 $this->_data = array_combine( 0638 array_keys($this->_data), 0639 array_fill(0, count($this->_data), null) 0640 ); 0641 0642 return $result; 0643 } 0644 0645 public function getIterator() 0646 { 0647 return new ArrayIterator((array) $this->_data); 0648 } 0649 0650 /** 0651 * Returns the column/value data as an array. 0652 * 0653 * @return array 0654 */ 0655 public function toArray() 0656 { 0657 return (array)$this->_data; 0658 } 0659 0660 /** 0661 * Sets all data in the row from an array. 0662 * 0663 * @param array $data 0664 * @return Zend_Db_Table_Row_Abstract Provides a fluent interface 0665 */ 0666 public function setFromArray(array $data) 0667 { 0668 $data = array_intersect_key($data, $this->_data); 0669 0670 foreach ($data as $columnName => $value) { 0671 $this->__set($columnName, $value); 0672 } 0673 0674 return $this; 0675 } 0676 0677 /** 0678 * Refreshes properties from the database. 0679 * 0680 * @return void 0681 */ 0682 public function refresh() 0683 { 0684 return $this->_refresh(); 0685 } 0686 0687 /** 0688 * Retrieves an instance of the parent table. 0689 * 0690 * @return Zend_Db_Table_Abstract 0691 */ 0692 protected function _getTable() 0693 { 0694 if (!$this->_connected) { 0695 // require_once 'Zend/Db/Table/Row/Exception.php'; 0696 throw new Zend_Db_Table_Row_Exception('Cannot save a Row unless it is connected'); 0697 } 0698 return $this->_table; 0699 } 0700 0701 /** 0702 * Retrieves an associative array of primary keys. 0703 * 0704 * @param bool $useDirty 0705 * @return array 0706 */ 0707 protected function _getPrimaryKey($useDirty = true) 0708 { 0709 if (!is_array($this->_primary)) { 0710 // require_once 'Zend/Db/Table/Row/Exception.php'; 0711 throw new Zend_Db_Table_Row_Exception("The primary key must be set as an array"); 0712 } 0713 0714 $primary = array_flip($this->_primary); 0715 if ($useDirty) { 0716 $array = array_intersect_key($this->_data, $primary); 0717 } else { 0718 $array = array_intersect_key($this->_cleanData, $primary); 0719 } 0720 if (count($primary) != count($array)) { 0721 // require_once 'Zend/Db/Table/Row/Exception.php'; 0722 throw new Zend_Db_Table_Row_Exception("The specified Table '$this->_tableClass' does not have the same primary key as the Row"); 0723 } 0724 return $array; 0725 } 0726 0727 /** 0728 * Retrieves an associative array of primary keys. 0729 * 0730 * @param bool $useDirty 0731 * @return array 0732 */ 0733 public function getPrimaryKey($useDirty = true) 0734 { 0735 return $this->_getPrimaryKey($useDirty); 0736 } 0737 0738 /** 0739 * Constructs where statement for retrieving row(s). 0740 * 0741 * @param bool $useDirty 0742 * @return array 0743 */ 0744 protected function _getWhereQuery($useDirty = true) 0745 { 0746 $where = array(); 0747 $db = $this->_getTable()->getAdapter(); 0748 $primaryKey = $this->_getPrimaryKey($useDirty); 0749 $info = $this->_getTable()->info(); 0750 $metadata = $info[Zend_Db_Table_Abstract::METADATA]; 0751 0752 // retrieve recently updated row using primary keys 0753 $where = array(); 0754 foreach ($primaryKey as $column => $value) { 0755 $tableName = $db->quoteIdentifier($info[Zend_Db_Table_Abstract::NAME], true); 0756 $type = $metadata[$column]['DATA_TYPE']; 0757 $columnName = $db->quoteIdentifier($column, true); 0758 $where[] = $db->quoteInto("{$tableName}.{$columnName} = ?", $value, $type); 0759 } 0760 return $where; 0761 } 0762 0763 /** 0764 * Refreshes properties from the database. 0765 * 0766 * @return void 0767 */ 0768 protected function _refresh() 0769 { 0770 $where = $this->_getWhereQuery(); 0771 $row = $this->_getTable()->fetchRow($where); 0772 0773 if (null === $row) { 0774 // require_once 'Zend/Db/Table/Row/Exception.php'; 0775 throw new Zend_Db_Table_Row_Exception('Cannot refresh row as parent is missing'); 0776 } 0777 0778 $this->_data = $row->toArray(); 0779 $this->_cleanData = $this->_data; 0780 $this->_modifiedFields = array(); 0781 } 0782 0783 /** 0784 * Allows pre-insert logic to be applied to row. 0785 * Subclasses may override this method. 0786 * 0787 * @return void 0788 */ 0789 protected function _insert() 0790 { 0791 } 0792 0793 /** 0794 * Allows post-insert logic to be applied to row. 0795 * Subclasses may override this method. 0796 * 0797 * @return void 0798 */ 0799 protected function _postInsert() 0800 { 0801 } 0802 0803 /** 0804 * Allows pre-update logic to be applied to row. 0805 * Subclasses may override this method. 0806 * 0807 * @return void 0808 */ 0809 protected function _update() 0810 { 0811 } 0812 0813 /** 0814 * Allows post-update logic to be applied to row. 0815 * Subclasses may override this method. 0816 * 0817 * @return void 0818 */ 0819 protected function _postUpdate() 0820 { 0821 } 0822 0823 /** 0824 * Allows pre-delete logic to be applied to row. 0825 * Subclasses may override this method. 0826 * 0827 * @return void 0828 */ 0829 protected function _delete() 0830 { 0831 } 0832 0833 /** 0834 * Allows post-delete logic to be applied to row. 0835 * Subclasses may override this method. 0836 * 0837 * @return void 0838 */ 0839 protected function _postDelete() 0840 { 0841 } 0842 0843 /** 0844 * Prepares a table reference for lookup. 0845 * 0846 * Ensures all reference keys are set and properly formatted. 0847 * 0848 * @param Zend_Db_Table_Abstract $dependentTable 0849 * @param Zend_Db_Table_Abstract $parentTable 0850 * @param string $ruleKey 0851 * @return array 0852 */ 0853 protected function _prepareReference(Zend_Db_Table_Abstract $dependentTable, Zend_Db_Table_Abstract $parentTable, $ruleKey) 0854 { 0855 $parentTableName = (get_class($parentTable) === 'Zend_Db_Table') ? $parentTable->getDefinitionConfigName() : get_class($parentTable); 0856 $map = $dependentTable->getReference($parentTableName, $ruleKey); 0857 0858 if (!isset($map[Zend_Db_Table_Abstract::REF_COLUMNS])) { 0859 $parentInfo = $parentTable->info(); 0860 $map[Zend_Db_Table_Abstract::REF_COLUMNS] = array_values((array) $parentInfo['primary']); 0861 } 0862 0863 $map[Zend_Db_Table_Abstract::COLUMNS] = (array) $map[Zend_Db_Table_Abstract::COLUMNS]; 0864 $map[Zend_Db_Table_Abstract::REF_COLUMNS] = (array) $map[Zend_Db_Table_Abstract::REF_COLUMNS]; 0865 0866 return $map; 0867 } 0868 0869 /** 0870 * Query a dependent table to retrieve rows matching the current row. 0871 * 0872 * @param string|Zend_Db_Table_Abstract $dependentTable 0873 * @param string OPTIONAL $ruleKey 0874 * @param Zend_Db_Table_Select OPTIONAL $select 0875 * @return Zend_Db_Table_Rowset_Abstract Query result from $dependentTable 0876 * @throws Zend_Db_Table_Row_Exception If $dependentTable is not a table or is not loadable. 0877 */ 0878 public function findDependentRowset($dependentTable, $ruleKey = null, Zend_Db_Table_Select $select = null) 0879 { 0880 $db = $this->_getTable()->getAdapter(); 0881 0882 if (is_string($dependentTable)) { 0883 $dependentTable = $this->_getTableFromString($dependentTable); 0884 } 0885 0886 if (!$dependentTable instanceof Zend_Db_Table_Abstract) { 0887 $type = gettype($dependentTable); 0888 if ($type == 'object') { 0889 $type = get_class($dependentTable); 0890 } 0891 // require_once 'Zend/Db/Table/Row/Exception.php'; 0892 throw new Zend_Db_Table_Row_Exception("Dependent table must be a Zend_Db_Table_Abstract, but it is $type"); 0893 } 0894 0895 // even if we are interacting between a table defined in a class and a 0896 // table via extension, ensure to persist the definition 0897 if (($tableDefinition = $this->_table->getDefinition()) !== null 0898 && ($dependentTable->getDefinition() == null)) { 0899 $dependentTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition)); 0900 } 0901 0902 if ($select === null) { 0903 $select = $dependentTable->select(); 0904 } else { 0905 $select->setTable($dependentTable); 0906 } 0907 0908 $map = $this->_prepareReference($dependentTable, $this->_getTable(), $ruleKey); 0909 0910 for ($i = 0; $i < count($map[Zend_Db_Table_Abstract::COLUMNS]); ++$i) { 0911 $parentColumnName = $db->foldCase($map[Zend_Db_Table_Abstract::REF_COLUMNS][$i]); 0912 $value = $this->_data[$parentColumnName]; 0913 // Use adapter from dependent table to ensure correct query construction 0914 $dependentDb = $dependentTable->getAdapter(); 0915 $dependentColumnName = $dependentDb->foldCase($map[Zend_Db_Table_Abstract::COLUMNS][$i]); 0916 $dependentColumn = $dependentDb->quoteIdentifier($dependentColumnName, true); 0917 $dependentInfo = $dependentTable->info(); 0918 $type = $dependentInfo[Zend_Db_Table_Abstract::METADATA][$dependentColumnName]['DATA_TYPE']; 0919 $select->where("$dependentColumn = ?", $value, $type); 0920 } 0921 0922 return $dependentTable->fetchAll($select); 0923 } 0924 0925 /** 0926 * Query a parent table to retrieve the single row matching the current row. 0927 * 0928 * @param string|Zend_Db_Table_Abstract $parentTable 0929 * @param string OPTIONAL $ruleKey 0930 * @param Zend_Db_Table_Select OPTIONAL $select 0931 * @return Zend_Db_Table_Row_Abstract Query result from $parentTable 0932 * @throws Zend_Db_Table_Row_Exception If $parentTable is not a table or is not loadable. 0933 */ 0934 public function findParentRow($parentTable, $ruleKey = null, Zend_Db_Table_Select $select = null) 0935 { 0936 $db = $this->_getTable()->getAdapter(); 0937 0938 if (is_string($parentTable)) { 0939 $parentTable = $this->_getTableFromString($parentTable); 0940 } 0941 0942 if (!$parentTable instanceof Zend_Db_Table_Abstract) { 0943 $type = gettype($parentTable); 0944 if ($type == 'object') { 0945 $type = get_class($parentTable); 0946 } 0947 // require_once 'Zend/Db/Table/Row/Exception.php'; 0948 throw new Zend_Db_Table_Row_Exception("Parent table must be a Zend_Db_Table_Abstract, but it is $type"); 0949 } 0950 0951 // even if we are interacting between a table defined in a class and a 0952 // table via extension, ensure to persist the definition 0953 if (($tableDefinition = $this->_table->getDefinition()) !== null 0954 && ($parentTable->getDefinition() == null)) { 0955 $parentTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition)); 0956 } 0957 0958 if ($select === null) { 0959 $select = $parentTable->select(); 0960 } else { 0961 $select->setTable($parentTable); 0962 } 0963 0964 $map = $this->_prepareReference($this->_getTable(), $parentTable, $ruleKey); 0965 0966 // iterate the map, creating the proper wheres 0967 for ($i = 0; $i < count($map[Zend_Db_Table_Abstract::COLUMNS]); ++$i) { 0968 $dependentColumnName = $db->foldCase($map[Zend_Db_Table_Abstract::COLUMNS][$i]); 0969 $value = $this->_data[$dependentColumnName]; 0970 // Use adapter from parent table to ensure correct query construction 0971 $parentDb = $parentTable->getAdapter(); 0972 $parentColumnName = $parentDb->foldCase($map[Zend_Db_Table_Abstract::REF_COLUMNS][$i]); 0973 $parentColumn = $parentDb->quoteIdentifier($parentColumnName, true); 0974 $parentInfo = $parentTable->info(); 0975 0976 // determine where part 0977 $type = $parentInfo[Zend_Db_Table_Abstract::METADATA][$parentColumnName]['DATA_TYPE']; 0978 $nullable = $parentInfo[Zend_Db_Table_Abstract::METADATA][$parentColumnName]['NULLABLE']; 0979 if ($value === null && $nullable == true) { 0980 $select->where("$parentColumn IS NULL"); 0981 } elseif ($value === null && $nullable == false) { 0982 return null; 0983 } else { 0984 $select->where("$parentColumn = ?", $value, $type); 0985 } 0986 0987 } 0988 0989 return $parentTable->fetchRow($select); 0990 } 0991 0992 /** 0993 * @param string|Zend_Db_Table_Abstract $matchTable 0994 * @param string|Zend_Db_Table_Abstract $intersectionTable 0995 * @param string OPTIONAL $callerRefRule 0996 * @param string OPTIONAL $matchRefRule 0997 * @param Zend_Db_Table_Select OPTIONAL $select 0998 * @return Zend_Db_Table_Rowset_Abstract Query result from $matchTable 0999 * @throws Zend_Db_Table_Row_Exception If $matchTable or $intersectionTable is not a table class or is not loadable. 1000 */ 1001 public function findManyToManyRowset($matchTable, $intersectionTable, $callerRefRule = null, 1002 $matchRefRule = null, Zend_Db_Table_Select $select = null) 1003 { 1004 $db = $this->_getTable()->getAdapter(); 1005 1006 if (is_string($intersectionTable)) { 1007 $intersectionTable = $this->_getTableFromString($intersectionTable); 1008 } 1009 1010 if (!$intersectionTable instanceof Zend_Db_Table_Abstract) { 1011 $type = gettype($intersectionTable); 1012 if ($type == 'object') { 1013 $type = get_class($intersectionTable); 1014 } 1015 // require_once 'Zend/Db/Table/Row/Exception.php'; 1016 throw new Zend_Db_Table_Row_Exception("Intersection table must be a Zend_Db_Table_Abstract, but it is $type"); 1017 } 1018 1019 // even if we are interacting between a table defined in a class and a 1020 // table via extension, ensure to persist the definition 1021 if (($tableDefinition = $this->_table->getDefinition()) !== null 1022 && ($intersectionTable->getDefinition() == null)) { 1023 $intersectionTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition)); 1024 } 1025 1026 if (is_string($matchTable)) { 1027 $matchTable = $this->_getTableFromString($matchTable); 1028 } 1029 1030 if (! $matchTable instanceof Zend_Db_Table_Abstract) { 1031 $type = gettype($matchTable); 1032 if ($type == 'object') { 1033 $type = get_class($matchTable); 1034 } 1035 // require_once 'Zend/Db/Table/Row/Exception.php'; 1036 throw new Zend_Db_Table_Row_Exception("Match table must be a Zend_Db_Table_Abstract, but it is $type"); 1037 } 1038 1039 // even if we are interacting between a table defined in a class and a 1040 // table via extension, ensure to persist the definition 1041 if (($tableDefinition = $this->_table->getDefinition()) !== null 1042 && ($matchTable->getDefinition() == null)) { 1043 $matchTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition)); 1044 } 1045 1046 if ($select === null) { 1047 $select = $matchTable->select(); 1048 } else { 1049 $select->setTable($matchTable); 1050 } 1051 1052 // Use adapter from intersection table to ensure correct query construction 1053 $interInfo = $intersectionTable->info(); 1054 $interDb = $intersectionTable->getAdapter(); 1055 $interName = $interInfo['name']; 1056 $interSchema = isset($interInfo['schema']) ? $interInfo['schema'] : null; 1057 $matchInfo = $matchTable->info(); 1058 $matchName = $matchInfo['name']; 1059 $matchSchema = isset($matchInfo['schema']) ? $matchInfo['schema'] : null; 1060 1061 $matchMap = $this->_prepareReference($intersectionTable, $matchTable, $matchRefRule); 1062 1063 for ($i = 0; $i < count($matchMap[Zend_Db_Table_Abstract::COLUMNS]); ++$i) { 1064 $interCol = $interDb->quoteIdentifier('i' . '.' . $matchMap[Zend_Db_Table_Abstract::COLUMNS][$i], true); 1065 $matchCol = $interDb->quoteIdentifier('m' . '.' . $matchMap[Zend_Db_Table_Abstract::REF_COLUMNS][$i], true); 1066 $joinCond[] = "$interCol = $matchCol"; 1067 } 1068 $joinCond = implode(' AND ', $joinCond); 1069 1070 $select->from(array('i' => $interName), array(), $interSchema) 1071 ->joinInner(array('m' => $matchName), $joinCond, Zend_Db_Select::SQL_WILDCARD, $matchSchema) 1072 ->setIntegrityCheck(false); 1073 1074 $callerMap = $this->_prepareReference($intersectionTable, $this->_getTable(), $callerRefRule); 1075 1076 for ($i = 0; $i < count($callerMap[Zend_Db_Table_Abstract::COLUMNS]); ++$i) { 1077 $callerColumnName = $db->foldCase($callerMap[Zend_Db_Table_Abstract::REF_COLUMNS][$i]); 1078 $value = $this->_data[$callerColumnName]; 1079 $interColumnName = $interDb->foldCase($callerMap[Zend_Db_Table_Abstract::COLUMNS][$i]); 1080 $interCol = $interDb->quoteIdentifier("i.$interColumnName", true); 1081 $interInfo = $intersectionTable->info(); 1082 $type = $interInfo[Zend_Db_Table_Abstract::METADATA][$interColumnName]['DATA_TYPE']; 1083 $select->where($interDb->quoteInto("$interCol = ?", $value, $type)); 1084 } 1085 1086 $stmt = $select->query(); 1087 1088 $config = array( 1089 'table' => $matchTable, 1090 'data' => $stmt->fetchAll(Zend_Db::FETCH_ASSOC), 1091 'rowClass' => $matchTable->getRowClass(), 1092 'readOnly' => false, 1093 'stored' => true 1094 ); 1095 1096 $rowsetClass = $matchTable->getRowsetClass(); 1097 if (!class_exists($rowsetClass)) { 1098 try { 1099 // require_once 'Zend/Loader.php'; 1100 Zend_Loader::loadClass($rowsetClass); 1101 } catch (Zend_Exception $e) { 1102 // require_once 'Zend/Db/Table/Row/Exception.php'; 1103 throw new Zend_Db_Table_Row_Exception($e->getMessage(), $e->getCode(), $e); 1104 } 1105 } 1106 $rowset = new $rowsetClass($config); 1107 return $rowset; 1108 } 1109 1110 /** 1111 * Turn magic function calls into non-magic function calls 1112 * to the above methods. 1113 * 1114 * @param string $method 1115 * @param array $args OPTIONAL Zend_Db_Table_Select query modifier 1116 * @return Zend_Db_Table_Row_Abstract|Zend_Db_Table_Rowset_Abstract 1117 * @throws Zend_Db_Table_Row_Exception If an invalid method is called. 1118 */ 1119 public function __call($method, array $args) 1120 { 1121 $matches = array(); 1122 1123 if (count($args) && $args[0] instanceof Zend_Db_Table_Select) { 1124 $select = $args[0]; 1125 } else { 1126 $select = null; 1127 } 1128 1129 /** 1130 * Recognize methods for Has-Many cases: 1131 * findParent<Class>() 1132 * findParent<Class>By<Rule>() 1133 * Use the non-greedy pattern repeat modifier e.g. \w+? 1134 */ 1135 if (preg_match('/^findParent(\w+?)(?:By(\w+))?$/', $method, $matches)) { 1136 $class = $matches[1]; 1137 $ruleKey1 = isset($matches[2]) ? $matches[2] : null; 1138 return $this->findParentRow($class, $ruleKey1, $select); 1139 } 1140 1141 /** 1142 * Recognize methods for Many-to-Many cases: 1143 * find<Class1>Via<Class2>() 1144 * find<Class1>Via<Class2>By<Rule>() 1145 * find<Class1>Via<Class2>By<Rule1>And<Rule2>() 1146 * Use the non-greedy pattern repeat modifier e.g. \w+? 1147 */ 1148 if (preg_match('/^find(\w+?)Via(\w+?)(?:By(\w+?)(?:And(\w+))?)?$/', $method, $matches)) { 1149 $class = $matches[1]; 1150 $viaClass = $matches[2]; 1151 $ruleKey1 = isset($matches[3]) ? $matches[3] : null; 1152 $ruleKey2 = isset($matches[4]) ? $matches[4] : null; 1153 return $this->findManyToManyRowset($class, $viaClass, $ruleKey1, $ruleKey2, $select); 1154 } 1155 1156 /** 1157 * Recognize methods for Belongs-To cases: 1158 * find<Class>() 1159 * find<Class>By<Rule>() 1160 * Use the non-greedy pattern repeat modifier e.g. \w+? 1161 */ 1162 if (preg_match('/^find(\w+?)(?:By(\w+))?$/', $method, $matches)) { 1163 $class = $matches[1]; 1164 $ruleKey1 = isset($matches[2]) ? $matches[2] : null; 1165 return $this->findDependentRowset($class, $ruleKey1, $select); 1166 } 1167 1168 // require_once 'Zend/Db/Table/Row/Exception.php'; 1169 throw new Zend_Db_Table_Row_Exception("Unrecognized method '$method()'"); 1170 } 1171 1172 1173 /** 1174 * _getTableFromString 1175 * 1176 * @param string $tableName 1177 * @return Zend_Db_Table_Abstract 1178 */ 1179 protected function _getTableFromString($tableName) 1180 { 1181 return Zend_Db_Table_Abstract::getTableFromString($tableName, $this->_table); 1182 } 1183 1184 }