Overview

Packages

  • Auth
  • Config
  • Controller
  • Date
  • Db
  • Feed
    • Abstract
    • Writers
  • File
    • Decorator
  • Form
    • Element
  • Image
  • Log
    • Writer
  • Net
    • Exception
    • REST
  • None
  • PHP
  • PHPMailer
  • Session
  • Util
  • Validate
    • Validator
  • Zend
    • Db
      • Adapter
      • Expr
      • Profiler
      • Select
      • Statement
      • Table
    • Loader
      • Autoloader
      • PluginLoader
    • Registry

Classes

  • Zend_Db_Table
  • Zend_Db_Table_Abstract
  • Zend_Db_Table_Definition
  • Zend_Db_Table_Row
  • Zend_Db_Table_Row_Abstract
  • Zend_Db_Table_Rowset
  • Zend_Db_Table_Rowset_Abstract
  • Zend_Db_Table_Select

Exceptions

  • Zend_Db_Table_Exception
  • Zend_Db_Table_Row_Exception
  • Zend_Db_Table_Rowset_Exception
  • Zend_Db_Table_Select_Exception
  • Overview
  • Package
  • Class
  • Tree
   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 23484 2010-12-10 03:57:59Z mjh_ca $
  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: }
1204: 
Pry Framework API documentation generated by ApiGen 2.6.1