"Fossies" - the Fresh Open Source Software Archive

Member "icingaweb2-2.11.4/library/vendor/Zend/Db/Table/Row/Abstract.php" (26 Jan 2023, 37639 Bytes) of package /linux/www/icingaweb2-2.11.4.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

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