PageRenderTime 91ms CodeModel.GetById 51ms app.highlight 19ms RepoModel.GetById 11ms app.codeStats 0ms

/concrete/libraries/3rdparty/Zend/Db/Table/Row/Abstract.php

https://bitbucket.org/selfeky/xclusivescardwebsite
PHP | 1184 lines | 553 code | 138 blank | 493 comment | 94 complexity | 1906ad8e8b8b64d1c4128f6a4b572f29 MD5 | raw file
   1<?php
   2/**
   3 * Zend Framework
   4 *
   5 * LICENSE
   6 *
   7 * This source file is subject to the new BSD license that is bundled
   8 * with this package in the file LICENSE.txt.
   9 * It is also available through the world-wide-web at this URL:
  10 * http://framework.zend.com/license/new-bsd
  11 * If you did not receive a copy of the license and are unable to
  12 * obtain it through the world-wide-web, please send an email
  13 * to license@zend.com so we can send you a copy immediately.
  14 *
  15 * @category   Zend
  16 * @package    Zend_Db
  17 * @subpackage Table
  18 * @copyright  Copyright (c) 2005-2012 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 24831 2012-05-30 12:52:25Z rob $
  21 */
  22
  23/**
  24 * @see Zend_Db
  25 */
  26require_once 'Zend/Db.php';
  27
  28/**
  29 * @category   Zend
  30 * @package    Zend_Db
  31 * @subpackage Table
  32 * @copyright  Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
  33 * @license    http://framework.zend.com/license/new-bsd     New BSD License
  34 */
  35abstract 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     * Retrieves an associative array of primary keys.
 729     *
 730     * @param bool $useDirty
 731     * @return array
 732     */
 733    public function getPrimaryKey($useDirty = true)
 734    {
 735        return $this->_getPrimaryKey($useDirty);
 736    }
 737
 738    /**
 739     * Constructs where statement for retrieving row(s).
 740     *
 741     * @param bool $useDirty
 742     * @return array
 743     */
 744    protected function _getWhereQuery($useDirty = true)
 745    {
 746        $where = array();
 747        $db = $this->_getTable()->getAdapter();
 748        $primaryKey = $this->_getPrimaryKey($useDirty);
 749        $info = $this->_getTable()->info();
 750        $metadata = $info[Zend_Db_Table_Abstract::METADATA];
 751
 752        // retrieve recently updated row using primary keys
 753        $where = array();
 754        foreach ($primaryKey as $column => $value) {
 755            $tableName = $db->quoteIdentifier($info[Zend_Db_Table_Abstract::NAME], true);
 756            $type = $metadata[$column]['DATA_TYPE'];
 757            $columnName = $db->quoteIdentifier($column, true);
 758            $where[] = $db->quoteInto("{$tableName}.{$columnName} = ?", $value, $type);
 759        }
 760        return $where;
 761    }
 762
 763    /**
 764     * Refreshes properties from the database.
 765     *
 766     * @return void
 767     */
 768    protected function _refresh()
 769    {
 770        $where = $this->_getWhereQuery();
 771        $row = $this->_getTable()->fetchRow($where);
 772
 773        if (null === $row) {
 774            require_once 'Zend/Db/Table/Row/Exception.php';
 775            throw new Zend_Db_Table_Row_Exception('Cannot refresh row as parent is missing');
 776        }
 777
 778        $this->_data = $row->toArray();
 779        $this->_cleanData = $this->_data;
 780        $this->_modifiedFields = array();
 781    }
 782
 783    /**
 784     * Allows pre-insert logic to be applied to row.
 785     * Subclasses may override this method.
 786     *
 787     * @return void
 788     */
 789    protected function _insert()
 790    {
 791    }
 792
 793    /**
 794     * Allows post-insert logic to be applied to row.
 795     * Subclasses may override this method.
 796     *
 797     * @return void
 798     */
 799    protected function _postInsert()
 800    {
 801    }
 802
 803    /**
 804     * Allows pre-update logic to be applied to row.
 805     * Subclasses may override this method.
 806     *
 807     * @return void
 808     */
 809    protected function _update()
 810    {
 811    }
 812
 813    /**
 814     * Allows post-update logic to be applied to row.
 815     * Subclasses may override this method.
 816     *
 817     * @return void
 818     */
 819    protected function _postUpdate()
 820    {
 821    }
 822
 823    /**
 824     * Allows pre-delete logic to be applied to row.
 825     * Subclasses may override this method.
 826     *
 827     * @return void
 828     */
 829    protected function _delete()
 830    {
 831    }
 832
 833    /**
 834     * Allows post-delete logic to be applied to row.
 835     * Subclasses may override this method.
 836     *
 837     * @return void
 838     */
 839    protected function _postDelete()
 840    {
 841    }
 842
 843    /**
 844     * Prepares a table reference for lookup.
 845     *
 846     * Ensures all reference keys are set and properly formatted.
 847     *
 848     * @param Zend_Db_Table_Abstract $dependentTable
 849     * @param Zend_Db_Table_Abstract $parentTable
 850     * @param string                 $ruleKey
 851     * @return array
 852     */
 853    protected function _prepareReference(Zend_Db_Table_Abstract $dependentTable, Zend_Db_Table_Abstract $parentTable, $ruleKey)
 854    {
 855        $parentTableName = (get_class($parentTable) === 'Zend_Db_Table') ? $parentTable->getDefinitionConfigName() : get_class($parentTable);
 856        $map = $dependentTable->getReference($parentTableName, $ruleKey);
 857
 858        if (!isset($map[Zend_Db_Table_Abstract::REF_COLUMNS])) {
 859            $parentInfo = $parentTable->info();
 860            $map[Zend_Db_Table_Abstract::REF_COLUMNS] = array_values((array) $parentInfo['primary']);
 861        }
 862
 863        $map[Zend_Db_Table_Abstract::COLUMNS] = (array) $map[Zend_Db_Table_Abstract::COLUMNS];
 864        $map[Zend_Db_Table_Abstract::REF_COLUMNS] = (array) $map[Zend_Db_Table_Abstract::REF_COLUMNS];
 865
 866        return $map;
 867    }
 868
 869    /**
 870     * Query a dependent table to retrieve rows matching the current row.
 871     *
 872     * @param string|Zend_Db_Table_Abstract  $dependentTable
 873     * @param string                         OPTIONAL $ruleKey
 874     * @param Zend_Db_Table_Select           OPTIONAL $select
 875     * @return Zend_Db_Table_Rowset_Abstract Query result from $dependentTable
 876     * @throws Zend_Db_Table_Row_Exception If $dependentTable is not a table or is not loadable.
 877     */
 878    public function findDependentRowset($dependentTable, $ruleKey = null, Zend_Db_Table_Select $select = null)
 879    {
 880        $db = $this->_getTable()->getAdapter();
 881
 882        if (is_string($dependentTable)) {
 883            $dependentTable = $this->_getTableFromString($dependentTable);
 884        }
 885
 886        if (!$dependentTable instanceof Zend_Db_Table_Abstract) {
 887            $type = gettype($dependentTable);
 888            if ($type == 'object') {
 889                $type = get_class($dependentTable);
 890            }
 891            require_once 'Zend/Db/Table/Row/Exception.php';
 892            throw new Zend_Db_Table_Row_Exception("Dependent table must be a Zend_Db_Table_Abstract, but it is $type");
 893        }
 894
 895        // even if we are interacting between a table defined in a class and a
 896        // table via extension, ensure to persist the definition
 897        if (($tableDefinition = $this->_table->getDefinition()) !== null
 898            && ($dependentTable->getDefinition() == null)) {
 899            $dependentTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition));
 900        }
 901
 902        if ($select === null) {
 903            $select = $dependentTable->select();
 904        } else {
 905            $select->setTable($dependentTable);
 906        }
 907
 908        $map = $this->_prepareReference($dependentTable, $this->_getTable(), $ruleKey);
 909
 910        for ($i = 0; $i < count($map[Zend_Db_Table_Abstract::COLUMNS]); ++$i) {
 911            $parentColumnName = $db->foldCase($map[Zend_Db_Table_Abstract::REF_COLUMNS][$i]);
 912            $value = $this->_data[$parentColumnName];
 913            // Use adapter from dependent table to ensure correct query construction
 914            $dependentDb = $dependentTable->getAdapter();
 915            $dependentColumnName = $dependentDb->foldCase($map[Zend_Db_Table_Abstract::COLUMNS][$i]);
 916            $dependentColumn = $dependentDb->quoteIdentifier($dependentColumnName, true);
 917            $dependentInfo = $dependentTable->info();
 918            $type = $dependentInfo[Zend_Db_Table_Abstract::METADATA][$dependentColumnName]['DATA_TYPE'];
 919            $select->where("$dependentColumn = ?", $value, $type);
 920        }
 921
 922        return $dependentTable->fetchAll($select);
 923    }
 924
 925    /**
 926     * Query a parent table to retrieve the single row matching the current row.
 927     *
 928     * @param string|Zend_Db_Table_Abstract $parentTable
 929     * @param string                        OPTIONAL $ruleKey
 930     * @param Zend_Db_Table_Select          OPTIONAL $select
 931     * @return Zend_Db_Table_Row_Abstract   Query result from $parentTable
 932     * @throws Zend_Db_Table_Row_Exception If $parentTable is not a table or is not loadable.
 933     */
 934    public function findParentRow($parentTable, $ruleKey = null, Zend_Db_Table_Select $select = null)
 935    {
 936        $db = $this->_getTable()->getAdapter();
 937
 938        if (is_string($parentTable)) {
 939            $parentTable = $this->_getTableFromString($parentTable);
 940        }
 941
 942        if (!$parentTable instanceof Zend_Db_Table_Abstract) {
 943            $type = gettype($parentTable);
 944            if ($type == 'object') {
 945                $type = get_class($parentTable);
 946            }
 947            require_once 'Zend/Db/Table/Row/Exception.php';
 948            throw new Zend_Db_Table_Row_Exception("Parent table must be a Zend_Db_Table_Abstract, but it is $type");
 949        }
 950
 951        // even if we are interacting between a table defined in a class and a
 952        // table via extension, ensure to persist the definition
 953        if (($tableDefinition = $this->_table->getDefinition()) !== null
 954            && ($parentTable->getDefinition() == null)) {
 955            $parentTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition));
 956        }
 957
 958        if ($select === null) {
 959            $select = $parentTable->select();
 960        } else {
 961            $select->setTable($parentTable);
 962        }
 963
 964        $map = $this->_prepareReference($this->_getTable(), $parentTable, $ruleKey);
 965
 966        // iterate the map, creating the proper wheres
 967        for ($i = 0; $i < count($map[Zend_Db_Table_Abstract::COLUMNS]); ++$i) {
 968            $dependentColumnName = $db->foldCase($map[Zend_Db_Table_Abstract::COLUMNS][$i]);
 969            $value = $this->_data[$dependentColumnName];
 970            // Use adapter from parent table to ensure correct query construction
 971            $parentDb = $parentTable->getAdapter();
 972            $parentColumnName = $parentDb->foldCase($map[Zend_Db_Table_Abstract::REF_COLUMNS][$i]);
 973            $parentColumn = $parentDb->quoteIdentifier($parentColumnName, true);
 974            $parentInfo = $parentTable->info();
 975
 976            // determine where part
 977            $type     = $parentInfo[Zend_Db_Table_Abstract::METADATA][$parentColumnName]['DATA_TYPE'];
 978            $nullable = $parentInfo[Zend_Db_Table_Abstract::METADATA][$parentColumnName]['NULLABLE'];
 979            if ($value === null && $nullable == true) {
 980                $select->where("$parentColumn IS NULL");
 981            } elseif ($value === null && $nullable == false) {
 982                return null;
 983            } else {
 984                $select->where("$parentColumn = ?", $value, $type);
 985            }
 986
 987        }
 988
 989        return $parentTable->fetchRow($select);
 990    }
 991
 992    /**
 993     * @param  string|Zend_Db_Table_Abstract  $matchTable
 994     * @param  string|Zend_Db_Table_Abstract  $intersectionTable
 995     * @param  string                         OPTIONAL $callerRefRule
 996     * @param  string                         OPTIONAL $matchRefRule
 997     * @param  Zend_Db_Table_Select           OPTIONAL $select
 998     * @return Zend_Db_Table_Rowset_Abstract Query result from $matchTable
 999     * @throws Zend_Db_Table_Row_Exception If $matchTable or $intersectionTable is not a table class or is not loadable.
1000     */
1001    public function findManyToManyRowset($matchTable, $intersectionTable, $callerRefRule = null,
1002                                         $matchRefRule = null, Zend_Db_Table_Select $select = null)
1003    {
1004        $db = $this->_getTable()->getAdapter();
1005
1006        if (is_string($intersectionTable)) {
1007            $intersectionTable = $this->_getTableFromString($intersectionTable);
1008        }
1009
1010        if (!$intersectionTable instanceof Zend_Db_Table_Abstract) {
1011            $type = gettype($intersectionTable);
1012            if ($type == 'object') {
1013                $type = get_class($intersectionTable);
1014            }
1015            require_once 'Zend/Db/Table/Row/Exception.php';
1016            throw new Zend_Db_Table_Row_Exception("Intersection table must be a Zend_Db_Table_Abstract, but it is $type");
1017        }
1018
1019        // even if we are interacting between a table defined in a class and a
1020        // table via extension, ensure to persist the definition
1021        if (($tableDefinition = $this->_table->getDefinition()) !== null
1022            && ($intersectionTable->getDefinition() == null)) {
1023            $intersectionTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition));
1024        }
1025
1026        if (is_string($matchTable)) {
1027            $matchTable = $this->_getTableFromString($matchTable);
1028        }
1029
1030        if (! $matchTable instanceof Zend_Db_Table_Abstract) {
1031            $type = gettype($matchTable);
1032            if ($type == 'object') {
1033                $type = get_class($matchTable);
1034            }
1035            require_once 'Zend/Db/Table/Row/Exception.php';
1036            throw new Zend_Db_Table_Row_Exception("Match table must be a Zend_Db_Table_Abstract, but it is $type");
1037        }
1038
1039        // even if we are interacting between a table defined in a class and a
1040        // table via extension, ensure to persist the definition
1041        if (($tableDefinition = $this->_table->getDefinition()) !== null
1042            && ($matchTable->getDefinition() == null)) {
1043            $matchTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition));
1044        }
1045
1046        if ($select === null) {
1047            $select = $matchTable->select();
1048        } else {
1049            $select->setTable($matchTable);
1050        }
1051
1052        // Use adapter from intersection table to ensure correct query construction
1053        $interInfo = $intersectionTable->info();
1054        $interDb   = $intersectionTable->getAdapter();
1055        $interName = $interInfo['name'];
1056        $interSchema = isset($interInfo['schema']) ? $interInfo['schema'] : null;
1057        $matchInfo = $matchTable->info();
1058        $matchName = $matchInfo['name'];
1059        $matchSchema = isset($matchInfo['schema']) ? $matchInfo['schema'] : null;
1060
1061        $matchMap = $this->_prepareReference($intersectionTable, $matchTable, $matchRefRule);
1062
1063        for ($i = 0; $i < count($matchMap[Zend_Db_Table_Abstract::COLUMNS]); ++$i) {
1064            $interCol = $interDb->quoteIdentifier('i' . '.' . $matchMap[Zend_Db_Table_Abstract::COLUMNS][$i], true);
1065            $matchCol = $interDb->quoteIdentifier('m' . '.' . $matchMap[Zend_Db_Table_Abstract::REF_COLUMNS][$i], true);
1066            $joinCond[] = "$interCol = $matchCol";
1067        }
1068        $joinCond = implode(' AND ', $joinCond);
1069
1070        $select->from(array('i' => $interName), array(), $interSchema)
1071               ->joinInner(array('m' => $matchName), $joinCond, Zend_Db_Select::SQL_WILDCARD, $matchSchema)
1072               ->setIntegrityCheck(false);
1073
1074        $callerMap = $this->_prepareReference($intersectionTable, $this->_getTable(), $callerRefRule);
1075
1076        for ($i = 0; $i < count($callerMap[Zend_Db_Table_Abstract::COLUMNS]); ++$i) {
1077            $callerColumnName = $db->foldCase($callerMap[Zend_Db_Table_Abstract::REF_COLUMNS][$i]);
1078            $value = $this->_data[$callerColumnName];
1079            $interColumnName = $interDb->foldCase($callerMap[Zend_Db_Table_Abstract::COLUMNS][$i]);
1080            $interCol = $interDb->quoteIdentifier("i.$interColumnName", true);
1081            $interInfo = $intersectionTable->info();
1082            $type = $interInfo[Zend_Db_Table_Abstract::METADATA][$interColumnName]['DATA_TYPE'];
1083            $select->where($interDb->quoteInto("$interCol = ?", $value, $type));
1084        }
1085
1086        $stmt = $select->query();
1087
1088        $config = array(
1089            'table'    => $matchTable,
1090            'data'     => $stmt->fetchAll(Zend_Db::FETCH_ASSOC),
1091            'rowClass' => $matchTable->getRowClass(),
1092            'readOnly' => false,
1093            'stored'   => true
1094        );
1095
1096        $rowsetClass = $matchTable->getRowsetClass();
1097        if (!class_exists($rowsetClass)) {
1098            try {
1099                require_once 'Zend/Loader.php';
1100                Zend_Loader::loadClass($rowsetClass);
1101            } catch (Zend_Exception $e) {
1102                require_once 'Zend/Db/Table/Row/Exception.php';
1103                throw new Zend_Db_Table_Row_Exception($e->getMessage(), $e->getCode(), $e);
1104            }
1105        }
1106        $rowset = new $rowsetClass($config);
1107        return $rowset;
1108    }
1109
1110    /**
1111     * Turn magic function calls into non-magic function calls
1112     * to the above methods.
1113     *
1114     * @param string $method
1115     * @param array $args OPTIONAL Zend_Db_Table_Select query modifier
1116     * @return Zend_Db_Table_Row_Abstract|Zend_Db_Table_Rowset_Abstract
1117     * @throws Zend_Db_Table_Row_Exception If an invalid method is called.
1118     */
1119    public function __call($method, array $args)
1120    {
1121        $matches = array();
1122
1123        if (count($args) && $args[0] instanceof Zend_Db_Table_Select) {
1124            $select = $args[0];
1125        } else {
1126            $select = null;
1127        }
1128
1129        /**
1130         * Recognize methods for Has-Many cases:
1131         * findParent<Class>()
1132         * findParent<Class>By<Rule>()
1133         * Use the non-greedy pattern repeat modifier e.g. \w+?
1134         */
1135        if (preg_match('/^findParent(\w+?)(?:By(\w+))?$/', $method, $matches)) {
1136            $class    = $matches[1];
1137            $ruleKey1 = isset($matches[2]) ? $matches[2] : null;
1138            return $this->findParentRow($class, $ruleKey1, $select);
1139        }
1140
1141        /**
1142         * Recognize methods for Many-to-Many cases:
1143         * find<Class1>Via<Class2>()
1144         * find<Class1>Via<Class2>By<Rule>()
1145         * find<Class1>Via<Class2>By<Rule1>And<Rule2>()
1146         * Use the non-greedy pattern repeat modifier e.g. \w+?
1147         */
1148        if (preg_match('/^find(\w+?)Via(\w+?)(?:By(\w+?)(?:And(\w+))?)?$/', $method, $matches)) {
1149            $class    = $matches[1];
1150            $viaClass = $matches[2];
1151            $ruleKey1 = isset($matches[3]) ? $matches[3] : null;
1152            $ruleKey2 = isset($matches[4]) ? $matches[4] : null;
1153            return $this->findManyToManyRowset($class, $viaClass, $ruleKey1, $ruleKey2, $select);
1154        }
1155
1156        /**
1157         * Recognize methods for Belongs-To cases:
1158         * find<Class>()
1159         * find<Class>By<Rule>()
1160         * Use the non-greedy pattern repeat modifier e.g. \w+?
1161         */
1162        if (preg_match('/^find(\w+?)(?:By(\w+))?$/', $method, $matches)) {
1163            $class    = $matches[1];
1164            $ruleKey1 = isset($matches[2]) ? $matches[2] : null;
1165            return $this->findDependentRowset($class, $ruleKey1, $select);
1166        }
1167
1168        require_once 'Zend/Db/Table/Row/Exception.php';
1169        throw new Zend_Db_Table_Row_Exception("Unrecognized method '$method()'");
1170    }
1171
1172
1173    /**
1174     * _getTableFromString
1175     *
1176     * @param string $tableName
1177     * @return Zend_Db_Table_Abstract
1178     */
1179    protected function _getTableFromString($tableName)
1180    {
1181        return Zend_Db_Table_Abstract::getTableFromString($tableName, $this->_table);
1182    }
1183
1184}