PageRenderTime 494ms CodeModel.GetById 124ms app.highlight 187ms RepoModel.GetById 114ms app.codeStats 0ms

/library/Zend/Db/Table/Abstract.php

https://bitbucket.org/baruffaldi/cms-php-bfcms
PHP | 1194 lines | 584 code | 121 blank | 489 comment | 92 complexity | 15fdf71384bb4e090403a266736c5d17 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-2008 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 6320 2007-09-12 00:27:22Z bkarwin $
  21 */
  22
  23/**
  24 * @see Zend_Db_Adapter_Abstract
  25 */
  26require_once 'Zend/Db/Adapter/Abstract.php';
  27
  28/**
  29 * @see Zend_Db_Adapter_Abstract
  30 */
  31require_once 'Zend/Db/Select.php';
  32
  33/**
  34 * @see Zend_Db
  35 */
  36require_once 'Zend/Db.php';
  37
  38/**
  39 * Class for SQL table interface.
  40 *
  41 * @category   Zend
  42 * @package    Zend_Db
  43 * @subpackage Table
  44 * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
  45 * @license    http://framework.zend.com/license/new-bsd     New BSD License
  46 */
  47abstract class Zend_Db_Table_Abstract
  48{
  49
  50    const ADAPTER          = 'db';
  51    const SCHEMA           = 'schema';
  52    const NAME             = 'name';
  53    const PRIMARY          = 'primary';
  54    const COLS             = 'cols';
  55    const METADATA         = 'metadata';
  56    const METADATA_CACHE   = 'metadataCache';
  57    const ROW_CLASS        = 'rowClass';
  58    const ROWSET_CLASS     = 'rowsetClass';
  59    const REFERENCE_MAP    = 'referenceMap';
  60    const DEPENDENT_TABLES = 'dependentTables';
  61    const SEQUENCE         = 'sequence';
  62
  63    const COLUMNS          = 'columns';
  64    const REF_TABLE_CLASS  = 'refTableClass';
  65    const REF_COLUMNS      = 'refColumns';
  66    const ON_DELETE        = 'onDelete';
  67    const ON_UPDATE        = 'onUpdate';
  68
  69    const CASCADE          = 'cascade';
  70    const RESTRICT         = 'restrict';
  71    const SET_NULL         = 'setNull';
  72
  73    /**
  74     * Default Zend_Db_Adapter_Abstract object.
  75     *
  76     * @var Zend_Db_Adapter_Abstract
  77     */
  78    protected static $_defaultDb;
  79
  80    /**
  81     * Default cache for information provided by the adapter's describeTable() method.
  82     *
  83     * @var Zend_Cache_Core
  84     */
  85    protected static $_defaultMetadataCache = null;
  86
  87    /**
  88     * Zend_Db_Adapter_Abstract object.
  89     *
  90     * @var Zend_Db_Adapter_Abstract
  91     */
  92    protected $_db;
  93
  94    /**
  95     * The schema name (default null means current schema)
  96     *
  97     * @var array
  98     */
  99    protected $_schema = null;
 100
 101    /**
 102     * The table name.
 103     *
 104     * @var array
 105     */
 106    protected $_name = null;
 107
 108    /**
 109     * The table column names derived from Zend_Db_Adapter_Abstract::describeTable().
 110     *
 111     * @var array
 112     */
 113    protected $_cols;
 114
 115    /**
 116     * The primary key column or columns.
 117     * A compound key should be declared as an array.
 118     * You may declare a single-column primary key
 119     * as a string.
 120     *
 121     * @var mixed
 122     */
 123    protected $_primary = null;
 124
 125    /**
 126     * If your primary key is a compound key, and one of the columns uses
 127     * an auto-increment or sequence-generated value, set _identity
 128     * to the ordinal index in the $_primary array for that column.
 129     * Note this index is the position of the column in the primary key,
 130     * not the position of the column in the table.  The primary key
 131     * array is 1-based.
 132     *
 133     * @var integer
 134     */
 135    protected $_identity = 1;
 136
 137    /**
 138     * Define the logic for new values in the primary key.
 139     * May be a string, boolean true, or boolean false.
 140     *
 141     * @var mixed
 142     */
 143    protected $_sequence = true;
 144
 145    /**
 146     * Information provided by the adapter's describeTable() method.
 147     *
 148     * @var array
 149     */
 150    protected $_metadata = array();
 151
 152    /**
 153     * Cache for information provided by the adapter's describeTable() method.
 154     *
 155     * @var Zend_Cache_Core
 156     */
 157    protected $_metadataCache = null;
 158
 159    /**
 160     * Classname for row
 161     *
 162     * @var string
 163     */
 164    protected $_rowClass = 'Zend_Db_Table_Row';
 165
 166    /**
 167     * Classname for rowset
 168     *
 169     * @var string
 170     */
 171    protected $_rowsetClass = 'Zend_Db_Table_Rowset';
 172
 173    /**
 174     * Associative array map of declarative referential integrity rules.
 175     * This array has one entry per foreign key in the current table.
 176     * Each key is a mnemonic name for one reference rule.
 177     *
 178     * Each value is also an associative array, with the following keys:
 179     * - columns       = array of names of column(s) in the child table.
 180     * - refTableClass = class name of the parent table.
 181     * - refColumns    = array of names of column(s) in the parent table,
 182     *                   in the same order as those in the 'columns' entry.
 183     * - onDelete      = "cascade" means that a delete in the parent table also
 184     *                   causes a delete of referencing rows in the child table.
 185     * - onUpdate      = "cascade" means that an update of primary key values in
 186     *                   the parent table also causes an update of referencing
 187     *                   rows in the child table.
 188     *
 189     * @var array
 190     */
 191    protected $_referenceMap = array();
 192
 193    /**
 194     * Simple array of class names of tables that are "children" of the current
 195     * table, in other words tables that contain a foreign key to this one.
 196     * Array elements are not table names; they are class names of classes that
 197     * extend Zend_Db_Table_Abstract.
 198     *
 199     * @var array
 200     */
 201    protected $_dependentTables = array();
 202
 203    /**
 204     * Constructor.
 205     *
 206     * Supported params for $config are:
 207     * - db              = user-supplied instance of database connector,
 208     *                     or key name of registry instance.
 209     * - name            = table name.
 210     * - primary         = string or array of primary key(s).
 211     * - rowClass        = row class name.
 212     * - rowsetClass     = rowset class name.
 213     * - referenceMap    = array structure to declare relationship
 214     *                     to parent tables.
 215     * - dependentTables = array of child tables.
 216     * - metadataCache   = cache for information from adapter describeTable().
 217     *
 218     * @param  mixed $config Array of user-specified config options, or just the Db Adapter.
 219     * @return void
 220     */
 221    public function __construct($config = array())
 222    {
 223        /**
 224         * Allow a scalar argument to be the Adapter object or Registry key.
 225         */
 226        if (!is_array($config)) {
 227            $config = array(self::ADAPTER => $config);
 228        }
 229
 230        foreach ($config as $key => $value) {
 231            switch ($key) {
 232                case self::ADAPTER:
 233                    $this->_setAdapter($value);
 234                    break;
 235                case self::SCHEMA:
 236                    $this->_schema = (string) $value;
 237                    break;
 238                case self::NAME:
 239                    $this->_name = (string) $value;
 240                    break;
 241                case self::PRIMARY:
 242                    $this->_primary = (array) $value;
 243                    break;
 244                case self::ROW_CLASS:
 245                    $this->setRowClass($value);
 246                    break;
 247                case self::ROWSET_CLASS:
 248                    $this->setRowsetClass($value);
 249                    break;
 250                case self::REFERENCE_MAP:
 251                    $this->setReferences($value);
 252                    break;
 253                case self::DEPENDENT_TABLES:
 254                    $this->setDependentTables($value);
 255                    break;
 256                case self::METADATA_CACHE:
 257                    $this->_setMetadataCache($value);
 258                    break;
 259                case self::SEQUENCE:
 260                    $this->_setSequence($value);
 261                    break;
 262                default:
 263                    // ignore unrecognized configuration directive
 264                    break;
 265            }
 266        }
 267
 268        $this->_setup();
 269        $this->init();
 270    }
 271
 272    /**
 273     * @param  string $classname
 274     * @return Zend_Db_Table_Abstract Provides a fluent interface
 275     */
 276    public function setRowClass($classname)
 277    {
 278        $this->_rowClass = (string) $classname;
 279
 280        return $this;
 281    }
 282
 283    /**
 284     * @return string
 285     */
 286    public function getRowClass()
 287    {
 288        return $this->_rowClass;
 289    }
 290
 291    /**
 292     * @param  string $classname
 293     * @return Zend_Db_Table_Abstract Provides a fluent interface
 294     */
 295    public function setRowsetClass($classname)
 296    {
 297        $this->_rowsetClass = (string) $classname;
 298
 299        return $this;
 300    }
 301
 302    /**
 303     * @return string
 304     */
 305    public function getRowsetClass()
 306    {
 307        return $this->_rowsetClass;
 308    }
 309
 310    /**
 311     * @param array $referenceMap
 312     * @return Zend_Db_Table_Abstract Provides a fluent interface
 313     */
 314    public function setReferences(array $referenceMap)
 315    {
 316        $this->_referenceMap = $referenceMap;
 317
 318        return $this;
 319    }
 320
 321    /**
 322     * @param string $tableClassname
 323     * @param string $ruleKey OPTIONAL
 324     * @return array
 325     * @throws Zend_Db_Table_Exception
 326     */
 327    public function getReference($tableClassname, $ruleKey = null)
 328    {
 329        $thisClass = get_class($this);
 330        $refMap = $this->_getReferenceMapNormalized();
 331        if ($ruleKey !== null) {
 332            if (!isset($refMap[$ruleKey])) {
 333                require_once "Zend/Db/Table/Exception.php";
 334                throw new Zend_Db_Table_Exception("No reference rule \"$ruleKey\" from table $thisClass to table $tableClassname");
 335            }
 336            if ($refMap[$ruleKey][self::REF_TABLE_CLASS] != $tableClassname) {
 337                require_once "Zend/Db/Table/Exception.php";
 338                throw new Zend_Db_Table_Exception("Reference rule \"$ruleKey\" does not reference table $tableClassname");
 339            }
 340            return $refMap[$ruleKey];
 341        }
 342        foreach ($refMap as $reference) {
 343            if ($reference[self::REF_TABLE_CLASS] == $tableClassname) {
 344                return $reference;
 345            }
 346        }
 347        require_once "Zend/Db/Table/Exception.php";
 348        throw new Zend_Db_Table_Exception("No reference from table $thisClass to table $tableClassname");
 349    }
 350
 351    /**
 352     * @param  array $dependentTables
 353     * @return Zend_Db_Table_Abstract Provides a fluent interface
 354     */
 355    public function setDependentTables(array $dependentTables)
 356    {
 357        $this->_dependentTables = $dependentTables;
 358
 359        return $this;
 360    }
 361
 362    /**
 363     * @return array
 364     */
 365    public function getDependentTables()
 366    {
 367        return $this->_dependentTables;
 368    }
 369
 370    /**
 371     * Sets the default Zend_Db_Adapter_Abstract for all Zend_Db_Table objects.
 372     *
 373     * @param  mixed $db Either an Adapter object, or a string naming a Registry key
 374     * @return void
 375     */
 376    public static final function setDefaultAdapter($db = null)
 377    {
 378        self::$_defaultDb = self::_setupAdapter($db);
 379    }
 380
 381    /**
 382     * Gets the default Zend_Db_Adapter_Abstract for all Zend_Db_Table objects.
 383     *
 384     * @return Zend_Db_Adapter_Abstract or null
 385     */
 386    public static final function getDefaultAdapter()
 387    {
 388        return self::$_defaultDb;
 389    }
 390
 391    /**
 392     * @param  mixed $db Either an Adapter object, or a string naming a Registry key
 393     * @return Zend_Db_Table_Abstract Provides a fluent interface
 394     */
 395    protected final function _setAdapter($db)
 396    {
 397        $this->_db = self::_setupAdapter($db);
 398        return $this;
 399    }
 400
 401    /**
 402     * Gets the Zend_Db_Adapter_Abstract for this particular Zend_Db_Table object.
 403     *
 404     * @return Zend_Db_Adapter_Abstract
 405     */
 406    public final function getAdapter()
 407    {
 408        return $this->_db;
 409    }
 410
 411    /**
 412     * @param  mixed $db Either an Adapter object, or a string naming a Registry key
 413     * @return Zend_Db_Adapter_Abstract
 414     * @throws Zend_Db_Table_Exception
 415     */
 416    protected static final function _setupAdapter($db)
 417    {
 418        if ($db === null) {
 419            return null;
 420        }
 421        if (is_string($db)) {
 422            require_once 'Zend/Registry.php';
 423            $db = Zend_Registry::get($db);
 424        }
 425        if (!$db instanceof Zend_Db_Adapter_Abstract) {
 426            require_once 'Zend/Db/Table/Exception.php';
 427            throw new Zend_Db_Table_Exception('Argument must be of type Zend_Db_Adapter_Abstract, or a Registry key where a Zend_Db_Adapter_Abstract object is stored');
 428        }
 429        return $db;
 430    }
 431
 432    /**
 433     * Sets the default metadata cache for information returned by Zend_Db_Adapter_Abstract::describeTable().
 434     *
 435     * If $defaultMetadataCache is null, then no metadata cache is used by default.
 436     *
 437     * @param  mixed $metadataCache Either a Cache object, or a string naming a Registry key
 438     * @return void
 439     */
 440    public static function setDefaultMetadataCache($metadataCache = null)
 441    {
 442        self::$_defaultMetadataCache = self::_setupMetadataCache($metadataCache);
 443    }
 444
 445    /**
 446     * Gets the default metadata cache for information returned by Zend_Db_Adapter_Abstract::describeTable().
 447     *
 448     * @return Zend_Cache_Core or null
 449     */
 450    public static function getDefaultMetadataCache()
 451    {
 452        return self::$_defaultMetadataCache;
 453    }
 454
 455    /**
 456     * Sets the metadata cache for information returned by Zend_Db_Adapter_Abstract::describeTable().
 457     *
 458     * If $metadataCache is null, then no metadata cache is used. Since there is no opportunity to reload metadata
 459     * after instantiation, this method need not be public, particularly because that it would have no effect
 460     * results in unnecessary API complexity. To configure the metadata cache, use the metadataCache configuration
 461     * option for the class constructor upon instantiation.
 462     *
 463     * @param  mixed $metadataCache Either a Cache object, or a string naming a Registry key
 464     * @return Zend_Db_Table_Abstract Provides a fluent interface
 465     */
 466    protected function _setMetadataCache($metadataCache)
 467    {
 468        $this->_metadataCache = self::_setupMetadataCache($metadataCache);
 469        return $this;
 470    }
 471
 472    /**
 473     * Gets the metadata cache for information returned by Zend_Db_Adapter_Abstract::describeTable().
 474     *
 475     * @return Zend_Cache_Core or null
 476     */
 477    public function getMetadataCache()
 478    {
 479        return $this->_metadataCache;
 480    }
 481
 482    /**
 483     * @param mixed $metadataCache Either a Cache object, or a string naming a Registry key
 484     * @return Zend_Cache_Core
 485     * @throws Zend_Db_Table_Exception
 486     */
 487    protected static final function _setupMetadataCache($metadataCache)
 488    {
 489        if ($metadataCache === null) {
 490            return null;
 491        }
 492        if (is_string($metadataCache)) {
 493            require_once 'Zend/Registry.php';
 494            $metadataCache = Zend_Registry::get($metadataCache);
 495        }
 496        if (!$metadataCache instanceof Zend_Cache_Core) {
 497            require_once 'Zend/Db/Table/Exception.php';
 498            throw new Zend_Db_Table_Exception('Argument must be of type Zend_Cache_Core, or a Registry key where a Zend_Cache_Core object is stored');
 499        }
 500        return $metadataCache;
 501    }
 502
 503    /**
 504     * Sets the sequence member, which defines the behavior for generating
 505     * primary key values in new rows.
 506     * - If this is a string, then the string names the sequence object.
 507     * - If this is boolean true, then the key uses an auto-incrementing
 508     *   or identity mechanism.
 509     * - If this is boolean false, then the key is user-defined.
 510     *   Use this for natural keys, for example.
 511     *
 512     * @param mixed $sequence
 513     * @return Zend_Db_Table_Adapter_Abstract Provides a fluent interface
 514     */
 515    protected function _setSequence($sequence)
 516    {
 517        $this->_sequence = $sequence;
 518
 519        return $this;
 520    }
 521
 522    /**
 523     * Turnkey for initialization of a table object.
 524     * Calls other protected methods for individual tasks, to make it easier
 525     * for a subclass to override part of the setup logic.
 526     *
 527     * @return void
 528     */
 529    protected function _setup()
 530    {
 531        $this->_setupDatabaseAdapter();
 532        $this->_setupTableName();
 533        $this->_setupMetadata();
 534        $this->_setupPrimaryKey();
 535    }
 536
 537    /**
 538     * Initialize database adapter.
 539     *
 540     * @return void
 541     */
 542    protected function _setupDatabaseAdapter()
 543    {
 544        if (! $this->_db) {
 545            $this->_db = self::getDefaultAdapter();
 546            if (!$this->_db instanceof Zend_Db_Adapter_Abstract) {
 547                require_once 'Zend/Db/Table/Exception.php';
 548                throw new Zend_Db_Table_Exception('No adapter found for ' . get_class($this));
 549            }
 550        }
 551    }
 552
 553    /**
 554     * Initialize table and schema names.
 555     *
 556     * If the table name is not set in the class definition,
 557     * use the class name itself as the table name.
 558     *
 559     * A schema name provided with the table name (e.g., "schema.table") overrides
 560     * any existing value for $this->_schema.
 561     *
 562     * @return void
 563     */
 564    protected function _setupTableName()
 565    {
 566        if (! $this->_name) {
 567            $this->_name = get_class($this);
 568        } else if (strpos($this->_name, '.')) {
 569            list($this->_schema, $this->_name) = explode('.', $this->_name);
 570        }
 571    }
 572
 573    /**
 574     * Initializes metadata.
 575     *
 576     * If metadata cannot be loaded from cache, adapter's describeTable() method is called to discover metadata
 577     * information. Returns true if and only if the metadata are loaded from cache.
 578     *
 579     * @return boolean
 580     * @throws Zend_Db_Table_Exception
 581     */
 582    protected function _setupMetadata()
 583    {
 584        // Assume that metadata will be loaded from cache
 585        $isMetadataFromCache = true;
 586
 587        // If $this has no metadata cache but the class has a default metadata cache
 588        if (null === $this->_metadataCache && null !== self::$_defaultMetadataCache) {
 589            // Make $this use the default metadata cache of the class
 590            $this->_setMetadataCache(self::$_defaultMetadataCache);
 591        }
 592
 593        // If $this has a metadata cache
 594        if (null !== $this->_metadataCache) {
 595            // Define the cache identifier where the metadata are saved
 596            $cacheId = md5("$this->_schema.$this->_name");
 597        }
 598
 599        // If $this has no metadata cache or metadata cache misses
 600        if (null === $this->_metadataCache || !($metadata = $this->_metadataCache->load($cacheId))) {
 601            // Metadata are not loaded from cache
 602            $isMetadataFromCache = false;
 603            // Fetch metadata from the adapter's describeTable() method
 604            $metadata = $this->_db->describeTable($this->_name, $this->_schema);
 605            // If $this has a metadata cache, then cache the metadata
 606            if (null !== $this->_metadataCache && !$this->_metadataCache->save($metadata, $cacheId)) {
 607                /**
 608                 * @see Zend_Db_Table_Exception
 609                 */
 610                require_once 'Zend/Db/Table/Exception.php';
 611                throw new Zend_Db_Table_Exception('Failed saving metadata to metadataCache');
 612            }
 613        }
 614
 615        // Assign the metadata to $this
 616        $this->_metadata = $metadata;
 617
 618        // Update the columns
 619        $this->_cols = array_keys($this->_metadata);
 620
 621        // Return whether the metadata were loaded from cache
 622        return $isMetadataFromCache;
 623    }
 624
 625    /**
 626     * Initialize primary key from metadata.
 627     * If $_primary is not defined, discover primary keys
 628     * from the information returned by describeTable().
 629     *
 630     * @return void
 631     * @throws Zend_Db_Table_Exception
 632     */
 633    protected function _setupPrimaryKey()
 634    {
 635        if (!$this->_primary) {
 636            $this->_primary = array();
 637            foreach ($this->_metadata as $col) {
 638                if ($col['PRIMARY']) {
 639                    $this->_primary[ $col['PRIMARY_POSITION'] ] = $col['COLUMN_NAME'];
 640                    if ($col['IDENTITY']) {
 641                        $this->_identity = $col['PRIMARY_POSITION'];
 642                    }
 643                }
 644            }
 645            // if no primary key was specified and none was found in the metadata
 646            // then throw an exception.
 647            if (empty($this->_primary)) {
 648                require_once 'Zend/Db/Table/Exception.php';
 649                throw new Zend_Db_Table_Exception('A table must have a primary key, but none was found');
 650            }
 651        } else if (!is_array($this->_primary)) {
 652            $this->_primary = array(1 => $this->_primary);
 653        } else if (isset($this->_primary[0])) {
 654            array_unshift($this->_primary, null);
 655            unset($this->_primary[0]);
 656        }
 657
 658        if (! array_intersect((array) $this->_primary, $this->_cols) == (array) $this->_primary) {
 659            require_once 'Zend/Db/Table/Exception.php';
 660            throw new Zend_Db_Table_Exception("Primary key column(s) ("
 661                . implode(',', (array) $this->_primary)
 662                . ") are not columns in this table ("
 663                . implode(',', $this->_cols)
 664                . ")");
 665        }
 666
 667        $primary    = (array) $this->_primary;
 668        $pkIdentity = $primary[(int) $this->_identity];
 669
 670        /**
 671         * Special case for PostgreSQL: a SERIAL key implicitly uses a sequence
 672         * object whose name is "<table>_<column>_seq".
 673         */
 674        if ($this->_sequence === true && $this->_db instanceof Zend_Db_Adapter_Pdo_Pgsql) {
 675            $this->_sequence = "{$this->_name}_{$pkIdentity}_seq";
 676            if ($this->_schema) {
 677                $this->_sequence = $this->_schema . '.' . $this->_sequence;
 678            }
 679        }
 680    }
 681
 682    /**
 683     * Returns a normalized version of the reference map
 684     *
 685     * @return array
 686     */
 687    protected function _getReferenceMapNormalized()
 688    {
 689        $referenceMapNormalized = array();
 690
 691        foreach ($this->_referenceMap as $rule => $map) {
 692
 693            $referenceMapNormalized[$rule] = array();
 694
 695            foreach ($map as $key => $value) {
 696                switch ($key) {
 697
 698                    // normalize COLUMNS and REF_COLUMNS to arrays
 699                    case self::COLUMNS:
 700                    case self::REF_COLUMNS:
 701                        if (!is_array($value)) {
 702                            $referenceMapNormalized[$rule][$key] = array($value);
 703                        } else {
 704                            $referenceMapNormalized[$rule][$key] = $value;
 705                        }
 706                        break;
 707
 708                    // other values are copied as-is
 709                    default:
 710                        $referenceMapNormalized[$rule][$key] = $value;
 711                        break;
 712                }
 713            }
 714        }
 715
 716        return $referenceMapNormalized;
 717    }
 718
 719    /**
 720     * Initialize object
 721     *
 722     * Called from {@link __construct()} as final step of object instantiation.
 723     *
 724     * @return void
 725     */
 726    public function init()
 727    {
 728    }
 729
 730    /**
 731     * Returns table information.
 732     *
 733     * You can elect to return only a part of this information by supplying its key name,
 734     * otherwise all information is returned as an array.
 735     *
 736     * @param  $key The specific info part to return OPTIONAL
 737     * @return mixed
 738     */
 739    public function info($key = null)
 740    {
 741        $info = array(
 742            self::SCHEMA           => $this->_schema,
 743            self::NAME             => $this->_name,
 744            self::COLS             => (array) $this->_cols,
 745            self::PRIMARY          => (array) $this->_primary,
 746            self::METADATA         => $this->_metadata,
 747            self::ROW_CLASS        => $this->_rowClass,
 748            self::ROWSET_CLASS     => $this->_rowsetClass,
 749            self::REFERENCE_MAP    => $this->_referenceMap,
 750            self::DEPENDENT_TABLES => $this->_dependentTables,
 751            self::SEQUENCE         => $this->_sequence
 752        );
 753        
 754        if ($key === null) {
 755            return $info;
 756        }
 757        
 758        if (!array_key_exists($key, $info)) {
 759            require_once 'Zend/Db/Table/Exception.php';
 760            throw new Zend_Db_Table_Exception('There is no table information for the key "' . $key . '"');
 761        }
 762        
 763        return $info[$key];
 764    }
 765
 766    /**
 767     * Returns an instance of a Zend_Db_Table_Select object.
 768     *
 769     * @return Zend_Db_Table_Select
 770     */
 771    public function select()
 772    {
 773        require_once 'Zend/Db/Table/Select.php';
 774        return new Zend_Db_Table_Select($this);
 775    }
 776
 777    /**
 778     * Inserts a new row.
 779     *
 780     * @param  array  $data  Column-value pairs.
 781     * @return mixed         The primary key of the row inserted.
 782     */
 783    public function insert(array $data)
 784    {
 785        /**
 786         * Zend_Db_Table assumes that if you have a compound primary key
 787         * and one of the columns in the key uses a sequence,
 788         * it's the _first_ column in the compound key.
 789         */
 790        $primary = (array) $this->_primary;
 791        $pkIdentity = $primary[(int)$this->_identity];
 792
 793        /**
 794         * If this table uses a database sequence object and the data does not
 795         * specify a value, then get the next ID from the sequence and add it
 796         * to the row.  We assume that only the first column in a compound
 797         * primary key takes a value from a sequence.
 798         */
 799        if (is_string($this->_sequence) && !isset($data[$pkIdentity])) {
 800            $data[$pkIdentity] = $this->_db->nextSequenceId($this->_sequence);
 801        }
 802
 803        /**
 804         * If the primary key can be generated automatically, and no value was
 805         * specified in the user-supplied data, then omit it from the tuple.
 806         */
 807        if (array_key_exists($pkIdentity, $data) && $data[$pkIdentity] === null) {
 808            unset($data[$pkIdentity]);
 809        }
 810
 811        /**
 812         * INSERT the new row.
 813         */
 814        $tableSpec = ($this->_schema ? $this->_schema . '.' : '') . $this->_name;
 815        $this->_db->insert($tableSpec, $data);
 816
 817        /**
 818         * Fetch the most recent ID generated by an auto-increment
 819         * or IDENTITY column, unless the user has specified a value,
 820         * overriding the auto-increment mechanism.
 821         */
 822        if ($this->_sequence === true && !isset($data[$pkIdentity])) {
 823            $data[$pkIdentity] = $this->_db->lastInsertId();
 824        }
 825
 826        /**
 827         * Return the primary key value if the PK is a single column,
 828         * else return an associative array of the PK column/value pairs.
 829         */
 830        $pkData = array_intersect_key($data, array_flip($primary));
 831        if (count($primary) == 1) {
 832            reset($pkData);
 833            return current($pkData);
 834        }
 835
 836        return $pkData;
 837    }
 838
 839    /**
 840     * Updates existing rows.
 841     *
 842     * @param  array        $data  Column-value pairs.
 843     * @param  array|string $where An SQL WHERE clause, or an array of SQL WHERE clauses.
 844     * @return int          The number of rows updated.
 845     */
 846    public function update(array $data, $where)
 847    {
 848        $tableSpec = ($this->_schema ? $this->_schema . '.' : '') . $this->_name;
 849        return $this->_db->update($tableSpec, $data, $where);
 850    }
 851
 852    /**
 853     * Called by a row object for the parent table's class during save() method.
 854     *
 855     * @param  string $parentTableClassname
 856     * @param  array  $oldPrimaryKey
 857     * @param  array  $newPrimaryKey
 858     * @return int
 859     */
 860    public function _cascadeUpdate($parentTableClassname, array $oldPrimaryKey, array $newPrimaryKey)
 861    {
 862        $rowsAffected = 0;
 863        foreach ($this->_getReferenceMapNormalized() as $map) {
 864            if ($map[self::REF_TABLE_CLASS] == $parentTableClassname && isset($map[self::ON_UPDATE])) {
 865                switch ($map[self::ON_UPDATE]) {
 866                    case self::CASCADE:
 867                        $newRefs = array();
 868                        for ($i = 0; $i < count($map[self::COLUMNS]); ++$i) {
 869                            $col = $this->_db->foldCase($map[self::COLUMNS][$i]);
 870                            $refCol = $this->_db->foldCase($map[self::REF_COLUMNS][$i]);
 871                            if (array_key_exists($refCol, $newPrimaryKey)) {
 872                                $newRefs[$col] = $newPrimaryKey[$refCol];
 873                            }
 874                            $type = $this->_metadata[$col]['DATA_TYPE'];
 875                            $where[] = $this->_db->quoteInto(
 876                                $this->_db->quoteIdentifier($col, true) . ' = ?',
 877                                $oldPrimaryKey[$refCol], $type);
 878                        }
 879                        $rowsAffected += $this->update($newRefs, $where);
 880                        break;
 881                    default:
 882                        // no action
 883                        break;
 884                }
 885            }
 886        }
 887        return $rowsAffected;
 888    }
 889
 890    /**
 891     * Deletes existing rows.
 892     *
 893     * @param  array|string $where SQL WHERE clause(s).
 894     * @return int          The number of rows deleted.
 895     */
 896    public function delete($where)
 897    {
 898        $tableSpec = ($this->_schema ? $this->_schema . '.' : '') . $this->_name;
 899        return $this->_db->delete($tableSpec, $where);
 900    }
 901
 902    /**
 903     * Called by parent table's class during delete() method.
 904     *
 905     * @param  string $parentTableClassname
 906     * @param  array  $primaryKey
 907     * @return int    Number of affected rows
 908     */
 909    public function _cascadeDelete($parentTableClassname, array $primaryKey)
 910    {
 911        $rowsAffected = 0;
 912        foreach ($this->_getReferenceMapNormalized() as $map) {
 913            if ($map[self::REF_TABLE_CLASS] == $parentTableClassname && isset($map[self::ON_DELETE])) {
 914                switch ($map[self::ON_DELETE]) {
 915                    case self::CASCADE:
 916                        for ($i = 0; $i < count($map[self::COLUMNS]); ++$i) {
 917                            $col = $this->_db->foldCase($map[self::COLUMNS][$i]);
 918                            $refCol = $this->_db->foldCase($map[self::REF_COLUMNS][$i]);
 919                            $type = $this->_metadata[$col]['DATA_TYPE'];
 920                            $where[] = $this->_db->quoteInto(
 921                                $this->_db->quoteIdentifier($col, true) . ' = ?',
 922                                $primaryKey[$refCol], $type);
 923                        }
 924                        $rowsAffected += $this->delete($where);
 925                        break;
 926                    default:
 927                        // no action
 928                        break;
 929                }
 930            }
 931        }
 932        return $rowsAffected;
 933    }
 934
 935    /**
 936     * Fetches rows by primary key.  The argument specifies one or more primary
 937     * key value(s).  To find multiple rows by primary key, the argument must
 938     * be an array.
 939     *
 940     * This method accepts a variable number of arguments.  If the table has a
 941     * multi-column primary key, the number of arguments must be the same as
 942     * the number of columns in the primary key.  To find multiple rows in a
 943     * table with a multi-column primary key, each argument must be an array
 944     * with the same number of elements.
 945     *
 946     * The find() method always returns a Rowset object, even if only one row
 947     * was found.
 948     *
 949     * @param  mixed $key The value(s) of the primary keys.
 950     * @return Zend_Db_Table_Rowset_Abstract Row(s) matching the criteria.
 951     * @throws Zend_Db_Table_Exception
 952     */
 953    public function find()
 954    {
 955        $args = func_get_args();
 956        $keyNames = array_values((array) $this->_primary);
 957
 958        if (count($args) < count($keyNames)) {
 959            require_once 'Zend/Db/Table/Exception.php';
 960            throw new Zend_Db_Table_Exception("Too few columns for the primary key");
 961        }
 962
 963        if (count($args) > count($keyNames)) {
 964            require_once 'Zend/Db/Table/Exception.php';
 965            throw new Zend_Db_Table_Exception("Too many columns for the primary key");
 966        }
 967
 968        $whereList = array();
 969        $numberTerms = 0;
 970        foreach ($args as $keyPosition => $keyValues) {
 971            // Coerce the values to an array.
 972            // Don't simply typecast to array, because the values
 973            // might be Zend_Db_Expr objects.
 974            if (!is_array($keyValues)) {
 975                $keyValues = array($keyValues);
 976            }
 977            if ($numberTerms == 0) {
 978                $numberTerms = count($keyValues);
 979            } else if (count($keyValues) != $numberTerms) {
 980                require_once 'Zend/Db/Table/Exception.php';
 981                throw new Zend_Db_Table_Exception("Missing value(s) for the primary key");
 982            }
 983            for ($i = 0; $i < count($keyValues); ++$i) {
 984                if (!isset($whereList[$i])) {
 985                    $whereList[$i] = array();
 986                }
 987                $whereList[$i][$keyPosition] = $keyValues[$i];
 988            }
 989        }
 990
 991        $whereClause = null;
 992        if (count($whereList)) {
 993            $whereOrTerms = array();
 994            foreach ($whereList as $keyValueSets) {
 995                $whereAndTerms = array();
 996                foreach ($keyValueSets as $keyPosition => $keyValue) {
 997                    $type = $this->_metadata[$keyNames[$keyPosition]]['DATA_TYPE'];
 998                    $tableName = $this->_db->quoteTableAs($this->_name);
 999                    $columnName = $this->_db->quoteIdentifier($keyNames[$keyPosition], true);
1000                    $whereAndTerms[] = $this->_db->quoteInto(
1001                        $tableName . '.' . $columnName . ' = ?',
1002                        $keyValue, $type);
1003                }
1004                $whereOrTerms[] = '(' . implode(' AND ', $whereAndTerms) . ')';
1005            }
1006            $whereClause = '(' . implode(' OR ', $whereOrTerms) . ')';
1007        }
1008
1009        return $this->fetchAll($whereClause);
1010    }
1011
1012    /**
1013     * Fetches all rows.
1014     *
1015     * Honors the Zend_Db_Adapter fetch mode.
1016     *
1017     * @param string|array|Zend_Db_Table_Select $where  OPTIONAL An SQL WHERE clause or Zend_Db_Table_Select object.
1018     * @param string|array                      $order  OPTIONAL An SQL ORDER clause.
1019     * @param int                               $count  OPTIONAL An SQL LIMIT count.
1020     * @param int                               $offset OPTIONAL An SQL LIMIT offset.
1021     * @return Zend_Db_Table_Rowset_Abstract The row results per the Zend_Db_Adapter fetch mode.
1022     */
1023    public function fetchAll($where = null, $order = null, $count = null, $offset = null)
1024    {
1025        if (!($where instanceof Zend_Db_Table_Select)) {
1026            $select = $this->select();
1027
1028            if ($where !== null) {
1029                $this->_where($select, $where);
1030            }
1031
1032            if ($order !== null) {
1033                $this->_order($select, $order);
1034            }
1035
1036            if ($count !== null || $offset !== null) {
1037                $select->limit($count, $offset);
1038            }
1039
1040        } else {
1041            $select = $where;
1042        }
1043
1044        $rows = $this->_fetch($select);
1045
1046        $data  = array(
1047            'table'    => $this,
1048            'data'     => $rows,
1049            'readOnly' => $select->isReadOnly(),
1050            'rowClass' => $this->_rowClass,
1051            'stored'   => true
1052        );
1053
1054        @Zend_Loader::loadClass($this->_rowsetClass);
1055        return new $this->_rowsetClass($data);
1056    }
1057
1058    /**
1059     * Fetches one row in an object of type Zend_Db_Table_Row_Abstract,
1060     * or returns Boolean false if no row matches the specified criteria.
1061     *
1062     * @param string|array|Zend_Db_Table_Select $where  OPTIONAL An SQL WHERE clause or Zend_Db_Table_Select object.
1063     * @param string|array                      $order  OPTIONAL An SQL ORDER clause.
1064     * @return Zend_Db_Table_Row_Abstract The row results per the
1065     *     Zend_Db_Adapter fetch mode, or null if no row found.
1066     */
1067    public function fetchRow($where = null, $order = null)
1068    {
1069        if (!($where instanceof Zend_Db_Table_Select)) {
1070            $select = $this->select();
1071
1072            if ($where !== null) {
1073                $this->_where($select, $where);
1074            }
1075
1076            if ($order !== null) {
1077                $this->_order($select, $order);
1078            }
1079
1080            $select->limit(1);
1081
1082        } else {
1083            $select = $where->limit(1);
1084        }
1085
1086        $rows = $this->_fetch($select);
1087
1088        if (count($rows) == 0) {
1089            return null;
1090        }
1091
1092        $data = array(
1093            'table'   => $this,
1094            'data'     => $rows[0],
1095            'readOnly' => $select->isReadOnly(),
1096            'stored'  => true
1097        );
1098
1099        @Zend_Loader::loadClass($this->_rowClass);
1100        return new $this->_rowClass($data);
1101    }
1102
1103    /**
1104     * Fetches a new blank row (not from the database).
1105     *
1106     * @return Zend_Db_Table_Row_Abstract
1107     * @deprecated since 0.9.3 - use createRow() instead.
1108     */
1109    public function fetchNew()
1110    {
1111        return $this->createRow();
1112    }
1113
1114    /**
1115     * Fetches a new blank row (not from the database).
1116     *
1117     * @param  array $data OPTIONAL data to populate in the new row.
1118     * @return Zend_Db_Table_Row_Abstract
1119     */
1120    public function createRow(array $data = array())
1121    {
1122        $defaults = array_combine($this->_cols, array_fill(0, count($this->_cols), null));
1123        $data = array_intersect_key($data, $defaults);
1124        $config = array(
1125            'table'    => $this,
1126            'data'     => $defaults,
1127            'readOnly' => false,
1128            'stored'   => false
1129        );
1130
1131        @Zend_Loader::loadClass($this->_rowClass);
1132        $row = new $this->_rowClass($config);
1133        $row->setFromArray($data);
1134        return $row;
1135    }
1136
1137    /**
1138     * Generate WHERE clause from user-supplied string or array
1139     *
1140     * @param  string|array $where  OPTIONAL An SQL WHERE clause.
1141     * @return Zend_Db_Table_Select
1142     */
1143    protected function _where(Zend_Db_Table_Select $select, $where)
1144    {
1145        $where = (array) $where;
1146
1147        foreach ($where as $key => $val) {
1148            // is $key an int?
1149            if (is_int($key)) {
1150                // $val is the full condition
1151                $select->where($val);
1152            } else {
1153                // $key is the condition with placeholder,
1154                // and $val is quoted into the condition
1155                $select->where($key, $val);
1156            }
1157        }
1158
1159        return $select;
1160    }
1161
1162    /**
1163     * Generate ORDER clause from user-supplied string or array
1164     *
1165     * @param  string|array $order  OPTIONAL An SQL ORDER clause.
1166     * @return Zend_Db_Table_Select
1167     */
1168    protected function _order(Zend_Db_Table_Select $select, $order)
1169    {
1170        if (!is_array($order)) {
1171            $order = array($order);
1172        }
1173
1174        foreach ($order as $val) {
1175            $select->order($val);
1176        }
1177
1178        return $select;
1179    }
1180
1181    /**
1182     * Support method for fetching rows.
1183     *
1184     * @param  Zend_Db_Table_Select $select  query options.
1185     * @return array An array containing the row results in FETCH_ASSOC mode.
1186     */
1187    protected function _fetch(Zend_Db_Table_Select $select)
1188    {
1189        $stmt = $this->_db->query($select);
1190        $data = $stmt->fetchAll(Zend_Db::FETCH_ASSOC);
1191        return $data;
1192    }
1193
1194}