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