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 }