web/lib/Zend/Db/Table/Abstract.php
changeset 807 877f952ae2bd
parent 207 621fa6caec0c
child 1230 68c69c656a2c
equal deleted inserted replaced
805:5e7a0fedabdf 807:877f952ae2bd
    13  * to license@zend.com so we can send you a copy immediately.
    13  * to license@zend.com so we can send you a copy immediately.
    14  *
    14  *
    15  * @category   Zend
    15  * @category   Zend
    16  * @package    Zend_Db
    16  * @package    Zend_Db
    17  * @subpackage Table
    17  * @subpackage Table
    18  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
    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
    19  * @license    http://framework.zend.com/license/new-bsd     New BSD License
    20  * @version    $Id: Abstract.php 21078 2010-02-18 18:07:16Z tech13 $
    20  * @version    $Id: Abstract.php 24958 2012-06-15 13:44:04Z adamlundrigan $
    21  */
    21  */
    22 
    22 
    23 /**
    23 /**
    24  * @see Zend_Db_Adapter_Abstract
    24  * @see Zend_Db_Adapter_Abstract
    25  */
    25  */
    39  * Class for SQL table interface.
    39  * Class for SQL table interface.
    40  *
    40  *
    41  * @category   Zend
    41  * @category   Zend
    42  * @package    Zend_Db
    42  * @package    Zend_Db
    43  * @subpackage Table
    43  * @subpackage Table
    44  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
    44  * @copyright  Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
    45  * @license    http://framework.zend.com/license/new-bsd     New BSD License
    45  * @license    http://framework.zend.com/license/new-bsd     New BSD License
    46  */
    46  */
    47 abstract class Zend_Db_Table_Abstract
    47 abstract class Zend_Db_Table_Abstract
    48 {
    48 {
    49 
    49 
    68     const REF_COLUMNS      = 'refColumns';
    68     const REF_COLUMNS      = 'refColumns';
    69     const ON_DELETE        = 'onDelete';
    69     const ON_DELETE        = 'onDelete';
    70     const ON_UPDATE        = 'onUpdate';
    70     const ON_UPDATE        = 'onUpdate';
    71 
    71 
    72     const CASCADE          = 'cascade';
    72     const CASCADE          = 'cascade';
       
    73     const CASCADE_RECURSE  = 'cascadeRecurse';
    73     const RESTRICT         = 'restrict';
    74     const RESTRICT         = 'restrict';
    74     const SET_NULL         = 'setNull';
    75     const SET_NULL         = 'setNull';
    75 
    76 
    76     const DEFAULT_NONE     = 'defaultNone';
    77     const DEFAULT_NONE     = 'defaultNone';
    77     const DEFAULT_CLASS    = 'defaultClass';
    78     const DEFAULT_CLASS    = 'defaultClass';
   742 
   743 
   743     /**
   744     /**
   744      * Initialize database adapter.
   745      * Initialize database adapter.
   745      *
   746      *
   746      * @return void
   747      * @return void
       
   748      * @throws Zend_Db_Table_Exception
   747      */
   749      */
   748     protected function _setupDatabaseAdapter()
   750     protected function _setupDatabaseAdapter()
   749     {
   751     {
   750         if (! $this->_db) {
   752         if (! $this->_db) {
   751             $this->_db = self::getDefaultAdapter();
   753             $this->_db = self::getDefaultAdapter();
   805             // Define the cache identifier where the metadata are saved
   807             // Define the cache identifier where the metadata are saved
   806 
   808 
   807             //get db configuration
   809             //get db configuration
   808             $dbConfig = $this->_db->getConfig();
   810             $dbConfig = $this->_db->getConfig();
   809 
   811 
       
   812             $port = isset($dbConfig['options']['port'])
       
   813                   ? ':'.$dbConfig['options']['port']
       
   814                   : (isset($dbConfig['port'])
       
   815                   ? ':'.$dbConfig['port']
       
   816                   : null);
       
   817 
       
   818             $host = isset($dbConfig['options']['host'])
       
   819                   ? ':'.$dbConfig['options']['host']
       
   820                   : (isset($dbConfig['host'])
       
   821                   ? ':'.$dbConfig['host']
       
   822                   : null);
       
   823 
   810             // Define the cache identifier where the metadata are saved
   824             // Define the cache identifier where the metadata are saved
   811             $cacheId = md5( // port:host/dbname:schema.table (based on availabilty)
   825             $cacheId = md5( // port:host/dbname:schema.table (based on availabilty)
   812                 (isset($dbConfig['options']['port']) ? ':'.$dbConfig['options']['port'] : null)
   826                     $port . $host . '/'. $dbConfig['dbname'] . ':'
   813                 . (isset($dbConfig['options']['host']) ? ':'.$dbConfig['options']['host'] : null)
   827                   . $this->_schema. '.' . $this->_name
   814                 . '/'.$dbConfig['dbname'].':'.$this->_schema.'.'.$this->_name
   828             );
   815                 );
       
   816         }
   829         }
   817 
   830 
   818         // If $this has no metadata cache or metadata cache misses
   831         // If $this has no metadata cache or metadata cache misses
   819         if (null === $this->_metadataCache || !($metadata = $this->_metadataCache->load($cacheId))) {
   832         if (null === $this->_metadataCache || !($metadata = $this->_metadataCache->load($cacheId))) {
   820             // Metadata are not loaded from cache
   833             // Metadata are not loaded from cache
   871             }
   884             }
   872             // if no primary key was specified and none was found in the metadata
   885             // if no primary key was specified and none was found in the metadata
   873             // then throw an exception.
   886             // then throw an exception.
   874             if (empty($this->_primary)) {
   887             if (empty($this->_primary)) {
   875                 require_once 'Zend/Db/Table/Exception.php';
   888                 require_once 'Zend/Db/Table/Exception.php';
   876                 throw new Zend_Db_Table_Exception('A table must have a primary key, but none was found');
   889                 throw new Zend_Db_Table_Exception("A table must have a primary key, but none was found for table '{$this->_name}'");
   877             }
   890             }
   878         } else if (!is_array($this->_primary)) {
   891         } else if (!is_array($this->_primary)) {
   879             $this->_primary = array(1 => $this->_primary);
   892             $this->_primary = array(1 => $this->_primary);
   880         } else if (isset($this->_primary[0])) {
   893         } else if (isset($this->_primary[0])) {
   881             array_unshift($this->_primary, null);
   894             array_unshift($this->_primary, null);
   959      * Returns table information.
   972      * Returns table information.
   960      *
   973      *
   961      * You can elect to return only a part of this information by supplying its key name,
   974      * You can elect to return only a part of this information by supplying its key name,
   962      * otherwise all information is returned as an array.
   975      * otherwise all information is returned as an array.
   963      *
   976      *
   964      * @param  $key The specific info part to return OPTIONAL
   977      * @param  string $key The specific info part to return OPTIONAL
   965      * @return mixed
   978      * @return mixed
       
   979      * @throws Zend_Db_Table_Exception
   966      */
   980      */
   967     public function info($key = null)
   981     public function info($key = null)
   968     {
   982     {
   969         $this->_setupPrimaryKey();
   983         $this->_setupPrimaryKey();
   970 
   984 
  1033          * to the row.  We assume that only the first column in a compound
  1047          * to the row.  We assume that only the first column in a compound
  1034          * primary key takes a value from a sequence.
  1048          * primary key takes a value from a sequence.
  1035          */
  1049          */
  1036         if (is_string($this->_sequence) && !isset($data[$pkIdentity])) {
  1050         if (is_string($this->_sequence) && !isset($data[$pkIdentity])) {
  1037             $data[$pkIdentity] = $this->_db->nextSequenceId($this->_sequence);
  1051             $data[$pkIdentity] = $this->_db->nextSequenceId($this->_sequence);
       
  1052             $pkSuppliedBySequence = true;
  1038         }
  1053         }
  1039 
  1054 
  1040         /**
  1055         /**
  1041          * If the primary key can be generated automatically, and no value was
  1056          * If the primary key can be generated automatically, and no value was
  1042          * specified in the user-supplied data, then omit it from the tuple.
  1057          * specified in the user-supplied data, then omit it from the tuple.
       
  1058          *
       
  1059          * Note: this checks for sensible values in the supplied primary key
       
  1060          * position of the data.  The following values are considered empty:
       
  1061          *   null, false, true, '', array()
  1043          */
  1062          */
  1044         if (array_key_exists($pkIdentity, $data) && $data[$pkIdentity] === null) {
  1063         if (!isset($pkSuppliedBySequence) && array_key_exists($pkIdentity, $data)) {
  1045             unset($data[$pkIdentity]);
  1064             if ($data[$pkIdentity] === null                                        // null
       
  1065                 || $data[$pkIdentity] === ''                                       // empty string
       
  1066                 || is_bool($data[$pkIdentity])                                     // boolean
       
  1067                 || (is_array($data[$pkIdentity]) && empty($data[$pkIdentity]))) {  // empty array
       
  1068                 unset($data[$pkIdentity]);
       
  1069             }
  1046         }
  1070         }
  1047 
  1071 
  1048         /**
  1072         /**
  1049          * INSERT the new row.
  1073          * INSERT the new row.
  1050          */
  1074          */
  1155      * @param  array|string $where SQL WHERE clause(s).
  1179      * @param  array|string $where SQL WHERE clause(s).
  1156      * @return int          The number of rows deleted.
  1180      * @return int          The number of rows deleted.
  1157      */
  1181      */
  1158     public function delete($where)
  1182     public function delete($where)
  1159     {
  1183     {
       
  1184         $depTables = $this->getDependentTables();
       
  1185         if (!empty($depTables)) {
       
  1186             $resultSet = $this->fetchAll($where);
       
  1187             if (count($resultSet) > 0 ) {
       
  1188                 foreach ($resultSet as $row) {
       
  1189                     /**
       
  1190                      * Execute cascading deletes against dependent tables
       
  1191                      */
       
  1192                     foreach ($depTables as $tableClass) {
       
  1193                         $t = self::getTableFromString($tableClass, $this);
       
  1194                         $t->_cascadeDelete($tableClass, $row->getPrimaryKey());
       
  1195                     }
       
  1196                 }
       
  1197             }
       
  1198         }
       
  1199 
  1160         $tableSpec = ($this->_schema ? $this->_schema . '.' : '') . $this->_name;
  1200         $tableSpec = ($this->_schema ? $this->_schema . '.' : '') . $this->_name;
  1161         return $this->_db->delete($tableSpec, $where);
  1201         return $this->_db->delete($tableSpec, $where);
  1162     }
  1202     }
  1163 
  1203 
  1164     /**
  1204     /**
  1168      * @param  array  $primaryKey
  1208      * @param  array  $primaryKey
  1169      * @return int    Number of affected rows
  1209      * @return int    Number of affected rows
  1170      */
  1210      */
  1171     public function _cascadeDelete($parentTableClassname, array $primaryKey)
  1211     public function _cascadeDelete($parentTableClassname, array $primaryKey)
  1172     {
  1212     {
       
  1213         // setup metadata
  1173         $this->_setupMetadata();
  1214         $this->_setupMetadata();
       
  1215         
       
  1216         // get this class name
       
  1217         $thisClass = get_class($this);
       
  1218         if ($thisClass === 'Zend_Db_Table') {
       
  1219             $thisClass = $this->_definitionConfigName;
       
  1220         }
       
  1221         
  1174         $rowsAffected = 0;
  1222         $rowsAffected = 0;
       
  1223         
  1175         foreach ($this->_getReferenceMapNormalized() as $map) {
  1224         foreach ($this->_getReferenceMapNormalized() as $map) {
  1176             if ($map[self::REF_TABLE_CLASS] == $parentTableClassname && isset($map[self::ON_DELETE])) {
  1225             if ($map[self::REF_TABLE_CLASS] == $parentTableClassname && isset($map[self::ON_DELETE])) {
  1177                 switch ($map[self::ON_DELETE]) {
  1226                 
  1178                     case self::CASCADE:
  1227                 $where = array();
  1179                         $where = array();
  1228                 
  1180                         for ($i = 0; $i < count($map[self::COLUMNS]); ++$i) {
  1229                 // CASCADE or CASCADE_RECURSE
  1181                             $col = $this->_db->foldCase($map[self::COLUMNS][$i]);
  1230                 if (in_array($map[self::ON_DELETE], array(self::CASCADE, self::CASCADE_RECURSE))) {
  1182                             $refCol = $this->_db->foldCase($map[self::REF_COLUMNS][$i]);
  1231                     for ($i = 0; $i < count($map[self::COLUMNS]); ++$i) {
  1183                             $type = $this->_metadata[$col]['DATA_TYPE'];
  1232                         $col = $this->_db->foldCase($map[self::COLUMNS][$i]);
  1184                             $where[] = $this->_db->quoteInto(
  1233                         $refCol = $this->_db->foldCase($map[self::REF_COLUMNS][$i]);
  1185                                 $this->_db->quoteIdentifier($col, true) . ' = ?',
  1234                         $type = $this->_metadata[$col]['DATA_TYPE'];
  1186                                 $primaryKey[$refCol], $type);
  1235                         $where[] = $this->_db->quoteInto(
       
  1236                             $this->_db->quoteIdentifier($col, true) . ' = ?',
       
  1237                             $primaryKey[$refCol], $type);
       
  1238                     }
       
  1239                 }
       
  1240                 
       
  1241                 // CASCADE_RECURSE
       
  1242                 if ($map[self::ON_DELETE] == self::CASCADE_RECURSE) {
       
  1243                     
       
  1244                     /**
       
  1245                      * Execute cascading deletes against dependent tables
       
  1246                      */
       
  1247                     $depTables = $this->getDependentTables();
       
  1248                     if (!empty($depTables)) {
       
  1249                         foreach ($depTables as $tableClass) {
       
  1250                             $t = self::getTableFromString($tableClass, $this);
       
  1251                             foreach ($this->fetchAll($where) as $depRow) {
       
  1252                                 $rowsAffected += $t->_cascadeDelete($thisClass, $depRow->getPrimaryKey());
       
  1253                             }
  1187                         }
  1254                         }
  1188                         $rowsAffected += $this->delete($where);
  1255                     }
  1189                         break;
       
  1190                     default:
       
  1191                         // no action
       
  1192                         break;
       
  1193                 }
  1256                 }
       
  1257 
       
  1258                 // CASCADE or CASCADE_RECURSE
       
  1259                 if (in_array($map[self::ON_DELETE], array(self::CASCADE, self::CASCADE_RECURSE))) {
       
  1260                     $rowsAffected += $this->delete($where);
       
  1261                 }
       
  1262                 
  1194             }
  1263             }
  1195         }
  1264         }
  1196         return $rowsAffected;
  1265         return $rowsAffected;
  1197     }
  1266     }
  1198 
  1267 
  1340      * Fetches one row in an object of type Zend_Db_Table_Row_Abstract,
  1409      * Fetches one row in an object of type Zend_Db_Table_Row_Abstract,
  1341      * or returns null if no row matches the specified criteria.
  1410      * or returns null if no row matches the specified criteria.
  1342      *
  1411      *
  1343      * @param string|array|Zend_Db_Table_Select $where  OPTIONAL An SQL WHERE clause or Zend_Db_Table_Select object.
  1412      * @param string|array|Zend_Db_Table_Select $where  OPTIONAL An SQL WHERE clause or Zend_Db_Table_Select object.
  1344      * @param string|array                      $order  OPTIONAL An SQL ORDER clause.
  1413      * @param string|array                      $order  OPTIONAL An SQL ORDER clause.
       
  1414      * @param int                               $offset OPTIONAL An SQL OFFSET value.
  1345      * @return Zend_Db_Table_Row_Abstract|null The row results per the
  1415      * @return Zend_Db_Table_Row_Abstract|null The row results per the
  1346      *     Zend_Db_Adapter fetch mode, or null if no row found.
  1416      *     Zend_Db_Adapter fetch mode, or null if no row found.
  1347      */
  1417      */
  1348     public function fetchRow($where = null, $order = null)
  1418     public function fetchRow($where = null, $order = null, $offset = null)
  1349     {
  1419     {
  1350         if (!($where instanceof Zend_Db_Table_Select)) {
  1420         if (!($where instanceof Zend_Db_Table_Select)) {
  1351             $select = $this->select();
  1421             $select = $this->select();
  1352 
  1422 
  1353             if ($where !== null) {
  1423             if ($where !== null) {
  1356 
  1426 
  1357             if ($order !== null) {
  1427             if ($order !== null) {
  1358                 $this->_order($select, $order);
  1428                 $this->_order($select, $order);
  1359             }
  1429             }
  1360 
  1430 
  1361             $select->limit(1);
  1431             $select->limit(1, ((is_numeric($offset)) ? (int) $offset : null));
  1362 
  1432 
  1363         } else {
  1433         } else {
  1364             $select = $where->limit(1);
  1434             $select = $where->limit(1, $where->getPart(Zend_Db_Select::LIMIT_OFFSET));
  1365         }
  1435         }
  1366 
  1436 
  1367         $rows = $this->_fetch($select);
  1437         $rows = $this->_fetch($select);
  1368 
  1438 
  1369         if (count($rows) == 0) {
  1439         if (count($rows) == 0) {
  1505         $stmt = $this->_db->query($select);
  1575         $stmt = $this->_db->query($select);
  1506         $data = $stmt->fetchAll(Zend_Db::FETCH_ASSOC);
  1576         $data = $stmt->fetchAll(Zend_Db::FETCH_ASSOC);
  1507         return $data;
  1577         return $data;
  1508     }
  1578     }
  1509 
  1579 
       
  1580     public static function getTableFromString($tableName, Zend_Db_Table_Abstract $referenceTable = null)
       
  1581     {
       
  1582         if ($referenceTable instanceof Zend_Db_Table_Abstract) {
       
  1583             $tableDefinition = $referenceTable->getDefinition();
       
  1584 
       
  1585             if ($tableDefinition !== null && $tableDefinition->hasTableConfig($tableName)) {
       
  1586                 return new Zend_Db_Table($tableName, $tableDefinition);
       
  1587             }
       
  1588         }
       
  1589 
       
  1590         // assume the tableName is the class name
       
  1591         if (!class_exists($tableName)) {
       
  1592             try {
       
  1593                 require_once 'Zend/Loader.php';
       
  1594                 Zend_Loader::loadClass($tableName);
       
  1595             } catch (Zend_Exception $e) {
       
  1596                 require_once 'Zend/Db/Table/Row/Exception.php';
       
  1597                 throw new Zend_Db_Table_Row_Exception($e->getMessage(), $e->getCode(), $e);
       
  1598             }
       
  1599         }
       
  1600 
       
  1601         $options = array();
       
  1602 
       
  1603         if ($referenceTable instanceof Zend_Db_Table_Abstract) {
       
  1604             $options['db'] = $referenceTable->getAdapter();
       
  1605         }
       
  1606 
       
  1607         if (isset($tableDefinition) && $tableDefinition !== null) {
       
  1608             $options[Zend_Db_Table_Abstract::DEFINITION] = $tableDefinition;
       
  1609         }
       
  1610 
       
  1611         return new $tableName($options);
       
  1612     }
       
  1613     
  1510 }
  1614 }