TableDataGateway.php

浏览该文件的文档。
00001 <?php
00003 // FleaPHP Framework
00004 //
00005 // Copyright (c) 2005 - 2008 QeeYuan China Inc. (http://www.qeeyuan.com)
00006 //
00007 // 许可协议,请查看源代码中附带的 LICENSE.txt 文件,
00008 // 或者访问 http://www.fleaphp.org/ 获得详细信息。
00010 
00020 // {{{ includes
00021 FLEA::loadClass('FLEA_Db_TableLink');
00022 // }}}
00023 
00024 // {{{ constants
00028 define('HAS_ONE',       1);
00029 
00033 define('BELONGS_TO',    2);
00034 
00038 define('HAS_MANY',      3);
00039 
00043 define('MANY_TO_MANY',  4);
00044 // }}}
00045 
00059 class FLEA_Db_TableDataGateway
00060 {
00066     var $schema = '';
00067 
00073     var $tableName = null;
00074 
00080     var $fullTableName = null;
00081 
00087     var $primaryKey = null;
00088 
00094     var $hasOne = null;
00095 
00101     var $belongsTo = null;
00102 
00108     var $hasMany = null;
00109 
00115     var $manyToMany = null;
00116 
00125     var $meta = null;
00126 
00132     var $fields = null;
00133 
00141     var $autoValidating = false;
00142 
00148     var $verifier = null;
00149 
00155     var $validateRules = null;
00156 
00165     var $createdTimeFields = array('CREATED', 'CREATED_ON', 'CREATED_AT');
00166 
00175     var $updatedTimeFields = array('UPDATED', 'UPDATED_ON', 'UPDATED_AT');
00176 
00184     var $autoLink = true;
00185 
00194     var $dbo = null;
00195 
00204     var $links = array();
00205 
00212     var $qtableName;
00213 
00220     var $qpk;
00221 
00225     var $pka;
00226 
00233     var $qpka;
00234 
00242     var $lastValidationResult;
00243 
00263     function FLEA_Db_TableDataGateway($params = null)
00264     {
00265         if (!empty($params['schema'])) {
00266             $this->schema = $params['schema'];
00267         }
00268         if (!empty($params['tableName'])) {
00269             $this->tableName = $params['tableName'];
00270         }
00271 
00272         if (!empty($params['primaryKey'])) {
00273             $this->primaryKey = $params['primaryKey'];
00274         }
00275 
00276         // 初始化验证服务对象
00277         if (isset($params['autoValidating'])) {
00278             $this->autoValidating = $params['autoValidating'];
00279         }
00280         if ($this->autoValidating) {
00281             if (!empty($params['verifierProvider'])) {
00282                 $provider = $params['verifierProvider'];
00283             } else {
00284                 $provider = FLEA::getAppInf('helper.verifier');
00285             }
00286             if (!empty($provider)) {
00287                 $this->verifier =& FLEA::getSingleton($provider);
00288             }
00289         }
00290 
00291         // 当 skipInit 为 true 时,不初始化表数据入口对象
00292         if (isset($params['skipConnect']) && $params['skipConnect'] != false) {
00293             return;
00294         }
00295 
00296         // 初始化数据访问对象
00297         if (!isset($params['dbo'])) {
00298             if (isset($params['dbDSN'])) {
00299                 $dbo =& FLEA::getDBO($params['dbDSN']);
00300             } else {
00301                 $dbo =& FLEA::getDBO();
00302             }
00303         } else {
00304             $dbo =& $params['dbo'];
00305         }
00306         $this->setDBO($dbo);
00307 
00308         // 当 skipCreateLinks 不为 true 时,建立关联
00309         if (!isset($params['skipCreateLinks']) || $params['skipCreateLinks'] == false) {
00310             $this->relink();
00311         }
00312     }
00313 
00321     function setDBO(& $dbo)
00322     {
00323         $this->dbo =& $dbo;
00324 
00325         if (empty($this->schema) && !empty($dbo->dsn['schema'])) {
00326             $this->schema = $dbo->dsn['schema'];
00327         }
00328         if (empty($this->fullTableName)) {
00329             $this->fullTableName = $dbo->dsn['prefix'] . $this->tableName;
00330         }
00331         $this->qtableName = $dbo->qtable($this->fullTableName, $this->schema);
00332 
00333         if (!$this->_prepareMeta()) {
00334             return false;
00335         }
00336         $this->fields = array_keys($this->meta);
00337 
00338         if (is_array($this->validateRules)) {
00339             foreach ($this->validateRules as $fieldName => $rules) {
00340                 $fieldName = strtoupper($fieldName);
00341                 if (!isset($this->meta[$fieldName])) { continue; }
00342                 foreach ((array)$rules as $ruleName => $rule) {
00343                     $this->meta[$fieldName][$ruleName] = $rule;
00344                 }
00345             }
00346         }
00347 
00348         // 如果没有指定主键,则尝试自动获取
00349         if (empty($this->primaryKey)) {
00350             foreach ($this->meta as $field) {
00351                 if ($field['primaryKey']) {
00352                     $this->primaryKey = $field['name'];
00353                     break;
00354                 }
00355             }
00356         }
00357 
00358         if (is_array($this->primaryKey)) {
00359             $this->qpk = array();
00360             $this->pka = array();
00361             $this->qpka = array();
00362             foreach ($this->primaryKey as $pk) {
00363                 $qpk = $dbo->qfield($pk, $this->fullTableName, $this->schema);
00364                 $this->qpk[$pk] = $qpk;
00365                 $pka = 'flea_pkref_' . $pk;
00366                 $this->pka[$pk] = $pka;
00367                 $this->qpka[$pk] = $qpk . ' AS ' . $pka;
00368             }
00369         } else {
00370             $this->qpk = $dbo->qfield($this->primaryKey, $this->fullTableName, $this->schema);
00371             $this->pka = 'flea_pkref_' . $this->primaryKey;
00372             $this->qpka = $this->qpk . ' AS ' . $this->pka;
00373         }
00374 
00375         return true;
00376     }
00377 
00383     function & getDBO()
00384     {
00385         return $this->dbo;
00386     }
00387 
00398     function & find($conditions, $sort = null, $fields = '*', $queryLinks = true)
00399     {
00400         $rowset =& $this->findAll($conditions, $sort, 1, $fields, $queryLinks);
00401         if (is_array($rowset)) {
00402             $row = reset($rowset);
00403         } else {
00404             $row = false;
00405         }
00406         unset($rowset);
00407         return $row;
00408     }
00409 
00421     function & findAll($conditions = null, $sort = null, $limit = null, $fields = '*', $queryLinks = true)
00422     {
00423         list($whereby, $distinct) = $this->getWhere($conditions);
00424         // 处理排序
00425         $sortby = $sort != '' ? " ORDER BY {$sort}" : '';
00426         // 处理 $limit
00427         if (is_array($limit)) {
00428             list($length, $offset) = $limit;
00429         } else {
00430             $length = $limit;
00431             $offset = null;
00432         }
00433 
00434         // 构造从主表查询数据的 SQL 语句
00435         $enableLinks = count($this->links) > 0 && $this->autoLink && $queryLinks;
00436         $fields = $this->dbo->qfields($fields, $this->fullTableName, $this->schema);
00437         if ($enableLinks) {
00438             // 当有关联需要处理时,必须获得主表的主键字段值
00439             $sql = "SELECT {$distinct} {$this->qpka}, {$fields} FROM {$this->qtableName} {$whereby} {$sortby}";
00440         } else {
00441             $sql = "SELECT {$distinct} {$fields} FROM {$this->qtableName} {$whereby} {$sortby}";
00442         }
00443 
00444         // 根据 $length 和 $offset 参数决定是否使用限定结果集的查询
00445         if (null !== $length || null !== $offset) {
00446             $result = $this->dbo->selectLimit($sql, $length, $offset);
00447         } else {
00448             $result = $this->dbo->execute($sql);
00449         }
00450 
00451         if ($enableLinks) {
00456             $pkvs = array();
00457             $assocRowset = null;
00458             $rowset = $this->dbo->getAllWithFieldRefs($result, $this->pka, $pkvs, $assocRowset);
00459             $in = 'IN (' . implode(',', array_map(array(& $this->dbo, 'qstr'), $pkvs)) . ')';
00460         } else {
00461             $rowset = $this->dbo->getAll($result);
00462         }
00463         unset($result);
00464 
00465         // 如果没有关联需要处理或者没有查询结果,则直接返回查询结果
00466         if (!$enableLinks || empty($rowset) || !$this->autoLink) {
00467             return $rowset;
00468         }
00469 
00475         $callback = create_function('& $r, $o, $m', '$r[$m] = null;');
00476         foreach ($this->links as $link) {
00477             /* @var $link FLEA_Db_TableLink */
00478             $mn = $link->mappingName;
00479             if (!$link->enabled || !$link->linkRead) { continue; }
00480             if (!$link->countOnly) {
00481                 array_walk($assocRowset, $callback, $mn);
00482                 $sql = $link->getFindSQL($in);
00483                 $this->dbo->assemble($sql, $assocRowset, $mn, $link->oneToOne, $this->pka, $link->limit);
00484             } else {
00485                 $link->calcCount($assocRowset, $mn, $in);
00486             }
00487         }
00488 
00489         return $rowset;
00490     }
00491 
00499     function assembleRecursionRow($mappingName, & $row, $enabledLinks = null)
00500     {
00501         $assoclink =& $this->getLink($mappingName);
00502         if ($assoclink == false) { return false; }
00503 
00504         $assoclink->init();
00505         $tdg =& $assoclink->assocTDG;
00506         $arow =& $row[$mappingName];
00507 
00508         if (!is_array($enabledLinks)) {
00509             if ($enabledLinks == null) {
00510                 $enabledLinks = array_keys($tdg->links);
00511             } else {
00512                 $enabledLinks = explode(',', $enabledLinks);
00513                 array_walk($enabledLinks, 'trim');
00514                 $enabledLinks = array_filter($enabledLinks, 'strlen');
00515             }
00516         }
00517         $enabledLinks = array_flip($enabledLinks);
00518         $enabledLinks = array_change_key_case($enabledLinks, CASE_LOWER);
00519         $this->enableLinks(array_keys($enabledLinks));
00520 
00521         foreach ($tdg->links as $link) {
00522             /* @var $link FLEA_Db_TableLink */
00523             if (!$link->enabled || !$link->linkRead || !isset($enabledLinks[$link->mappingName])) { continue; }
00524 
00525             $in = array();
00526             switch ($assoclink->type) {
00527             case HAS_ONE:
00528             case BELONGS_TO:
00529                 $pkv = $arow[$link->mainTDG->primaryKey];
00530                 $in[] = $pkv;
00531                 $assocRowset = array($pkv => & $arow);
00532                 $arow[$link->mappingName] = null;
00533                 break;
00534             case HAS_MANY:
00535             case MANY_TO_MANY:
00536                 $assocRowset = array();
00537                 foreach (array_keys($arow) as $offset) {
00538                     $pkv = $arow[$offset][$link->mainTDG->primaryKey];
00539                     $in[] = $pkv;
00540                     $assocRowset[$pkv] = & $arow[$offset];
00541                     $arow[$offset][$link->mappingName] = null;
00542                 }
00543             }
00544             $in = 'IN (' . implode(',', array_map(array(& $this->dbo, 'qstr'), $in)) . ')';
00545 
00546             $sql = $link->getFindSQL($in);
00547             $this->dbo->assemble($sql, $assocRowset, $link->mappingName, $link->oneToOne, $link->mainTDG->pka, $link->limit);
00548         }
00549 
00550         return true;
00551     }
00552 
00553 
00561     function assembleRecursionRowset($mappingName, & $rowset, $enabledLinks = null)
00562     {
00563         $assoclink =& $this->getLink($mappingName);
00564         if ($assoclink == false) { return false; }
00565 
00566         $assoclink->init();
00567         $tdg =& $assoclink->assocTDG;
00568         $arowset = array();
00569         foreach (array_keys($rowset) as $offset) {
00570             $arowset[] =& $rowset[$offset][$mappingName];
00571         }
00572         $keys = array_keys($arowset);
00573 
00574         if (!is_array($enabledLinks)) {
00575             if ($enabledLinks == null) {
00576                 $enabledLinks = array_keys($tdg->links);
00577             } else {
00578                 $enabledLinks = explode(',', $enabledLinks);
00579                 array_walk($enabledLinks, 'trim');
00580                 $enabledLinks = array_filter($enabledLinks, 'strlen');
00581             }
00582         }
00583         $enabledLinks = array_flip($enabledLinks);
00584         $enabledLinks = array_change_key_case($enabledLinks, CASE_LOWER);
00585         $this->enableLinks(array_keys($enabledLinks));
00586 
00587         foreach ($tdg->links as $link) {
00588             /* @var $link FLEA_Db_TableLink */
00589             if (!$link->enabled || !$link->linkRead || !isset($enabledLinks[$link->mappingName])) { continue; }
00590 
00591             $in = array();
00592             $assocRowset = array();
00593             switch ($assoclink->type) {
00594             case HAS_ONE:
00595             case BELONGS_TO:
00596                 foreach ($keys as $key) {
00597                     $pkv = $arowset[$key][$link->mainTDG->primaryKey];
00598                     $in[] = $pkv;
00599                     $assocRowset[$pkv] =& $arowset[$key];
00600                     $arowset[$key][$link->mappingName] = null;
00601                 }
00602                 break;
00603             case HAS_MANY:
00604             case MANY_TO_MANY:
00605                 foreach ($keys as $key) {
00606                     foreach (array_keys($arowset[$key]) as $offset) {
00607                         $pkv = $arowset[$key][$offset][$link->mainTDG->primaryKey];
00608                         $in[] = $pkv;
00609                         $assocRowset[$pkv] = & $arowset[$key][$offset];
00610                         $arow[$key][$offset][$link->mappingName] = null;
00611                     }
00612                 }
00613             }
00614             $in = 'IN (' . implode(',', array_map(array(& $this->dbo, 'qstr'), $in)) . ')';
00615 
00616             $sql = $link->getFindSQL($in);
00617             $this->dbo->assemble($sql, $assocRowset, $link->mappingName, $link->oneToOne, $link->mainTDG->pka, $link->limit);
00618         }
00619 
00620         return true;
00621     }
00622 
00633     function & findByField($field, $value, $sort = null, $fields = '*')
00634     {
00635         return $this->find(array($field => $value), $sort, $fields);
00636     }
00637 
00649     function & findAllByField($field, $value, $sort = null, $limit = null, $fields = '*')
00650     {
00651         return $this->findAll(array($field => $value), $sort, $limit, $fields);
00652     }
00653 
00666     function & findAllByPkvs($pkvs, $conditions = null, $sort = null, $limit = null, $fields = '*', $queryLinks = true)
00667     {
00668         $in = array('in()' => $pkvs);
00669         if (empty($conditions)) {
00670             $conditions = $in;
00671         } else {
00672             if (!is_array($conditions)) {
00673                 $conditions = array($in, $conditions);
00674             } else {
00675                 array_push($conditions, $in);
00676             }
00677         }
00678 
00679         return $this->findAll($conditions, $sort, $limit, $fields, $queryLinks);
00680     }
00681 
00690     function & findBySql($sql, $limit = null)
00691     {
00692         // 处理 $limit
00693         if (is_array($limit)) {
00694             list($length, $offset) = $limit;
00695         } else {
00696             $length = $limit;
00697             $offset = null;
00698         }
00699         if (is_null($length) && is_null($offset)) {
00700             return $this->dbo->getAll($sql);
00701         }
00702 
00703         $result = $this->dbo->selectLimit($sql, $length, $offset);
00704         if ($result) {
00705             $rowset = $this->dbo->getAll($result);
00706         } else {
00707             $rowset = false;
00708         }
00709         return $rowset;
00710     }
00711 
00720     function findCount($conditions = null, $fields = null)
00721     {
00722         list($whereby, $distinct) = $this->getWhere($conditions);
00723         if (is_null($fields)) {
00724             $fields = $this->qpk;
00725         } else {
00726             $fields = $this->dbo->qfields($fields, $this->fullTableName);
00727         }
00728         $sql = "SELECT {$distinct}COUNT({$fields}) FROM {$this->qtableName}{$whereby}";
00729         return (int)$this->dbo->getOne($sql);
00730     }
00731 
00743     function save(& $row, $saveLinks = true, $updateCounter = true)
00744     {
00745         if (empty($row[$this->primaryKey])) {
00746             return $this->create($row, $saveLinks, $updateCounter);
00747         } else {
00748             return $this->update($row, $saveLinks, $updateCounter);
00749         }
00750     }
00751 
00760     function saveRowset(& $rowset, $saveLinks = true)
00761     {
00762         $this->dbo->startTrans();
00763         foreach ($rowset as $row) {
00764             if (!$this->save($row, $saveLinks, false)) {
00765                 $this->dbo->completeTrans(false);
00766                 return false;
00767             }
00768         }
00769         $this->dbo->completeTrans();
00770         return true;
00771     }
00772 
00780     function replace(& $row) {
00781         $this->_setCreatedTimeFields($row);
00782         $fields = '';
00783         $values = '';
00784         foreach ($row as $field => $value) {
00785             if (!isset($this->meta[strtoupper($field)])) { continue; }
00786             $fields .= $this->dbo->qfield($field) . ', ';
00787             $values .= $this->dbo->qstr($value) . ', ';
00788         }
00789         $fields = substr($fields, 0, -2);
00790         $values = substr($values, 0, -2);
00791         $sql = "REPLACE INTO {$this->fullTableName} ({$fields}) VALUES ({$values})";
00792         if (!$this->dbo->execute($sql)) { return false; }
00793 
00794         if (!empty($row[$this->primaryKey])) {
00795             return $row[$this->primaryKey];
00796         }
00797 
00798         $insertid = $this->dbo->insertId();
00799         return $insertid;
00800     }
00801 
00809     function replaceRowset(& $rowset)
00810     {
00811         $ids = array();
00812         $this->dbo->startTrans();
00813         foreach ($rowset as $row) {
00814             $id = $this->replace($row, false);
00815             if (!$id) {
00816                 $this->dbo->completeTrans(false);
00817                 return false;
00818             }
00819             $ids[] = $id;
00820         }
00821         $this->dbo->completeTrans();
00822         return $ids;
00823     }
00824 
00835     function update(& $row, $saveLinks = true)
00836     {
00837         if (!$this->_beforeUpdate($row)) {
00838             return false;
00839         }
00840 
00841         // 检查是否提供了主键值
00842         if (!isset($row[$this->primaryKey])) {
00843             FLEA::loadClass('FLEA_Db_Exception_MissingPrimaryKey');
00844             return __THROW(new FLEA_Db_Exception_MissingPrimaryKey($this->primaryKey));
00845         }
00846 
00847         // 自动填写记录的最后更新时间字段
00848         $this->_setUpdatedTimeFields($row);
00849 
00850         // 如果提供了验证器,则进行数据验证
00851         if ($this->autoValidating && $this->verifier != null) {
00852             if (!$this->checkRowData($row, true)) {
00853                 // 验证失败抛出异常
00854                 FLEA::loadClass('FLEA_Exception_ValidationFailed');
00855                 return __THROW(new FLEA_Exception_ValidationFailed($this->getLastValidation(), $row));
00856             }
00857         }
00858 
00859         // 开始事务
00860         $this->dbo->startTrans();
00861 
00862         // 调用 _beforeUpdateDb() 事件
00863         if (!$this->_beforeUpdateDb($row)) {
00864             $this->dbo->completeTrans(false);
00865             return false;
00866         }
00867 
00868         // 生成 SQL 语句
00869         $pkv = $row[$this->primaryKey];
00870         unset($row[$this->primaryKey]);
00871         list($pairs, $values) = $this->dbo->getPlaceholderPair($row, $this->fields);
00872         $row[$this->primaryKey] = $pkv;
00873 
00874         if (!empty($pairs)) {
00875             $pairs = implode(',', $pairs);
00876             $sql = "UPDATE {$this->qtableName} SET {$pairs} WHERE {$this->qpk} = " . $this->dbo->qstr($pkv);
00877 
00878             // 执行更新操作
00879             if (!$this->dbo->execute($sql, $values)) {
00880                 $this->dbo->completeTrans(false);
00881                 return false;
00882             }
00883         }
00884 
00885         // 处理对关联数据的更新
00886         if ($this->autoLink && $saveLinks) {
00887             foreach (array_keys($this->links) as $linkKey) {
00888                 $link =& $this->links[$linkKey];
00889                 /* @var $link FLEA_Db_TableLink */
00890                 // 跳过不需要处理的关联
00891                 if (!$link->enabled || !$link->linkUpdate || !isset($row[$link->mappingName]) || !is_array($row[$link->mappingName])) {
00892                     continue;
00893                 }
00894 
00895                 if (!$link->saveAssocData($row[$link->mappingName], $pkv)) {
00896                     $this->dbo->completeTrans(false);
00897                     return false;
00898                 }
00899             }
00900         }
00901 
00902         $this->_updateCounterCache($row);
00903 
00904         // 提交事务
00905         $this->dbo->completeTrans();
00906 
00907         $this->_afterUpdateDb($row);
00908 
00909         return true;
00910     }
00911 
00920     function updateRowset(& $rowset, $saveLinks = true)
00921     {
00922         $this->dbo->startTrans();
00923         foreach ($rowset as $row) {
00924             if (!$this->update($row, $saveLinks, false)) {
00925                 $this->dbo->completeTrans(false);
00926                 return false;
00927             }
00928         }
00929         $this->dbo->completeTrans();
00930         return true;
00931     }
00932 
00943     function updateByConditions($conditions, & $row)
00944     {
00945         $whereby = $this->getWhere($conditions, false);
00946         $this->_setUpdatedTimeFields($row);
00947 
00948         list($pairs, $values) = $this->dbo->getPlaceholderPair($row, $this->fields);
00949         $pairs = implode(',', $pairs);
00950         $sql = "UPDATE {$this->qtableName} SET {$pairs} {$whereby}";
00951         return $this->dbo->execute($sql, $values);
00952     }
00953 
00965     function updateField($conditions, $field, $value)
00966     {
00967         $row = array($field => $value);
00968         return $this->updateByConditions($conditions, $row);
00969     }
00970 
00982     function incrField($conditions, $field, $incr = 1)
00983     {
00984         $field = $this->dbo->qfield($field, $this->fullTableName, $this->schema);
00985         $incr = (int)$incr;
00986 
00987         $row = array();
00988         $this->_setUpdatedTimeFields($row);
00989         list($pairs, $values) = $this->dbo->getPlaceholderPair($row, $this->fields);
00990         $pairs = implode(',', $pairs);
00991         if ($pairs) {
00992             $pairs = ', ' . $pairs;
00993         }
00994 
00995         $whereby = $this->getWhere($conditions, false);
00996         $sql = "UPDATE {$this->qtableName} SET {$field} = {$field} + {$incr}{$pairs} {$whereby}";
00997         return $this->dbo->execute($sql, $values);
00998     }
00999 
01011     function decrField($conditions, $field, $decr = 1)
01012     {
01013         $field = $this->dbo->qfield($field, $this->fullTableName, $this->schema);
01014         $decr = (int)$decr;
01015 
01016         $row = array();
01017         $this->_setUpdatedTimeFields($row);
01018         list($pairs, $values) = $this->dbo->getPlaceholderPair($row, $this->fields);
01019         $pairs = implode(',', $pairs);
01020         if ($pairs) {
01021             $pairs = ', ' . $pairs;
01022         }
01023 
01024         $whereby = $this->getWhere($conditions, false);
01025         $sql = "UPDATE {$this->qtableName} SET {$field} = {$field}- {$decr}{$pairs} {$whereby}";
01026         return $this->dbo->execute($sql, $values);
01027     }
01028 
01039     function create(& $row, $saveLinks = true)
01040     {
01041         if (!$this->_beforeCreate($row)) {
01042             return false;
01043         }
01044 
01045         // 自动设置日期字段
01046         $this->_setCreatedTimeFields($row);
01047 
01048         // 处理主键
01049         $mpk = strtoupper($this->primaryKey);
01050         $insertId = null;
01051         $unsetpk = true;
01052         if (isset($this->meta[$mpk]['autoIncrement']) && $this->meta[$mpk]['autoIncrement'])
01053         {
01054             if (isset($row[$this->primaryKey])) {
01055                 if (empty($row[$this->primaryKey])) {
01056                     // 如果主键字段是自增,而提供的记录数据虽然包含主键字段,
01057                     // 但却是空值,则删除这个空值
01058                     unset($row[$this->primaryKey]);
01059                 } else {
01060                     $unsetpk = false;
01061                 }
01062             }
01063         } else {
01064             // 如果主键字段不是自增字段,并且没有提供主键字段值时,则获取一个新的主键字段值
01065             if (!isset($row[$this->primaryKey]) || empty($row[$this->primaryKey])) {
01066                 $insertId = $this->newInsertId();
01067                 $row[$this->primaryKey] = $insertId;
01068             } else {
01069                 // 使用开发者提交的主键字段值
01070                 $insertId = $row[$this->primaryKey];
01071                 $unsetpk = false;
01072             }
01073         }
01074 
01075         // 自动验证数据
01076         if ($this->autoValidating && $this->verifier != null) {
01077             if (!$this->checkRowData($row)) {
01078                 FLEA::loadClass('FLEA_Exception_ValidationFailed');
01079                 __THROW(new FLEA_Exception_ValidationFailed($this->getLastValidation(), $row));
01080                 return false;
01081             }
01082         }
01083 
01084         // 调用 _beforeCreateDb() 事件
01085         $this->dbo->startTrans();
01086 
01087         if (!$this->_beforeCreateDb($row)) {
01088             if ($unsetpk) { unset($row[$this->primaryKey]); }
01089             $this->dbo->completeTrans(false);
01090             return false;
01091         }
01092 
01093         // 生成 SQL 语句
01094         list($holders, $values) = $this->dbo->getPlaceholder($row, $this->fields);
01095         $holders = implode(',', $holders);
01096         $fields = $this->dbo->qfields(array_keys($values));
01097         $sql = "INSERT INTO {$this->qtableName} ({$fields}) VALUES ({$holders})";
01098 
01099         // 插入数据
01100         if (!$this->dbo->Execute($sql, $values, true)) {
01101             if ($unsetpk) { unset($row[$this->primaryKey]); }
01102             $this->dbo->completeTrans(false);
01103             return false;
01104         }
01105 
01106         // 如果提交的数据中没有主键字段值,则尝试获取新插入记录的主键值
01107         if (is_null($insertId)) {
01108             $insertId = $this->dbo->insertId();
01109             if (!$insertId) {
01110                 if ($unsetpk) { unset($row[$this->primaryKey]); }
01111                 $this->dbo->completeTrans(false);
01112                 FLEA::loadClass('FLEA_Db_Exception_InvalidInsertID');
01113                 return __THROW(new FLEA_Db_Exception_InvalidInsertID());
01114             }
01115         }
01116 
01117         // 处理关联数据表
01118         if ($this->autoLink && $saveLinks) {
01119             foreach (array_keys($this->links) as $linkKey) {
01120                 $link =& $this->links[$linkKey];
01121                 /* @var $link FLEA_Db_TableLink */
01122                 if (!$link->enabled || !$link->linkCreate || !isset($row[$link->mappingName]) || !is_array($row[$link->mappingName])) {
01123                     // 跳过没有关联数据的关联和不需要处理的关联
01124                     continue;
01125                 }
01126 
01127                 if (!$link->saveAssocData($row[$link->mappingName], $insertId)) {
01128                     if ($unsetpk) { unset($row[$this->primaryKey]); }
01129                     $this->dbo->completeTrans(false);
01130                     return false;
01131                 }
01132             }
01133         }
01134 
01135         $row[$this->primaryKey] = $insertId;
01136         $this->_updateCounterCache($row);
01137 
01138         // 提交事务
01139         $this->dbo->CompleteTrans();
01140 
01141         $this->_afterCreateDb($row);
01142         if ($unsetpk) { unset($row[$this->primaryKey]); }
01143 
01144         return $insertId;
01145     }
01146 
01155     function createRowset(& $rowset, $saveLinks = true)
01156     {
01157         $insertids = array();
01158         $this->dbo->startTrans();
01159         foreach ($rowset as $row) {
01160             $insertid = $this->create($row, $saveLinks, false);
01161             if (!$insertid) {
01162                 $this->dbo->completeTrans(false);
01163                 return false;
01164             }
01165             $insertids[] = $insertid;
01166         }
01167         $this->dbo->completeTrans();
01168         return $insertids;
01169     }
01170 
01180     function remove(& $row)
01181     {
01182         if (!$this->_beforeRemove($row)) {
01183             return false;
01184         }
01185 
01186         if (!isset($row[$this->primaryKey])) {
01187             FLEA::loadClass('FLEA_Db_Exception_MissingPrimaryKey');
01188             __THROW(new FLEA_Db_Exception_MissingPrimaryKey($this->primaryKey));
01189             return false;
01190         }
01191         $ret = $this->removeByPkv($row[$this->primaryKey]);
01192         if ($ret) {
01193             $this->_afterRemoveDb($row);
01194         }
01195         return $ret;
01196     }
01197 
01207     function removeByPkv($pkv)
01208     {
01209         $this->dbo->startTrans();
01210 
01211         if (!$this->_beforeRemoveDbByPkv($pkv)) {
01212             $this->dbo->completeTrans(false);
01213             return false;
01214         }
01215 
01219         $qpkv = $this->dbo->qstr($pkv);
01220 
01221         // 处理关联数据表
01222         $counterCacheLinks = array();
01223         if ($this->autoLink) {
01224             foreach (array_keys($this->links) as $linkKey) {
01225                 $link =& $this->links[$linkKey];
01226                 /* @var $link FLEA_Db_TableLink */
01227                 if (!$link->enabled) { continue; }
01228                 switch ($link->type) {
01229                 case MANY_TO_MANY:
01230                     /* @var $link FLEA_Db_ManyToManyLink */
01231                     if (!$link->deleteMiddleTableDataByMainForeignKey($qpkv)) {
01232                         $this->dbo->completeTrans(false);
01233                         return false;
01234                     }
01235                     break;
01236                 case HAS_ONE:
01237                 case HAS_MANY:
01244                     /* @var $link FLEA_Db_HasOneLink */
01245                     if ($link->deleteByForeignKey($qpkv) === false) {
01246                         $this->dbo->completeTrans(false);
01247                         return false;
01248                     }
01249                     break;
01250                 case BELONGS_TO:
01251                     if ($link->counterCache) {
01252                         $counterCacheLinks[] = $link->foreignKey;
01253                     }
01254                 }
01255             }
01256         }
01257 
01258         if (!empty($counterCacheLinks)) {
01259             $counterCacheLinks[] = $this->primaryKey;
01260             $row = $this->find(array($this->primaryKey => $pkv), null, $counterCacheLinks, false);
01261         }
01262 
01263         // 删除主表数据
01264         $sql = "DELETE FROM {$this->qtableName} WHERE {$this->qpk} = {$qpkv}";
01265         if ($this->dbo->execute($sql) == false) {
01266             $this->dbo->completeTrans(false);
01267             return false;
01268         }
01269 
01270         if (!empty($counterCacheLinks)) {
01271             $this->_updateCounterCache($row);
01272         }
01273 
01274         // 提交事务
01275         $this->dbo->completeTrans();
01276 
01277         $this->_afterRemoveDbByPkv($pkv);
01278 
01279         return true;
01280     }
01281 
01289     function removeByConditions($conditions)
01290     {
01291         $rowset = $this->findAll($conditions, null, null, $this->primaryKey, false);
01292         $count = 0;
01293         $this->dbo->startTrans();
01294         foreach ($rowset as $row) {
01295             if (!$this->removeByPkv($row[$this->primaryKey], false)) { break; }
01296             $count++;
01297         }
01298         $this->dbo->completeTrans();
01299         $rows = $this->dbo->affectedRows();
01300         if ($rows > 0) { return $count; }
01301         return 0;
01302     }
01303 
01311     function removeByPkvs($pkvs)
01312     {
01313         $ret = true;
01314         $this->dbo->startTrans();
01315         foreach ($pkvs as $id) {
01316             $ret = $this->removeByPkv($id, false);
01317             if ($ret === false) { break; }
01318         }
01319         $this->dbo->completeTrans();
01320         return $ret;
01321     }
01322 
01328     function removeAll()
01329     {
01330         $sql = "DELETE FROM {$this->qtableName}";
01331         $ret = $this->execute($sql);
01332         return $ret;
01333     }
01334 
01340     function removeAllWithLinks()
01341     {
01342         $this->dbo->startTrans();
01343 
01344         // 处理关联数据表
01345         if ($this->autoLink) {
01346             foreach (array_keys($this->links) as $linkKey) {
01347                 $link =& $this->links[$linkKey];
01348                 /* @var $link FLEA_Db_TableLink */
01349                 switch ($link->type) {
01350                 case MANY_TO_MANY:
01351                     /* @var $link FLEA_Db_ManyToManyLink */
01352                     $link->init();
01353                     $sql = "DELETE FROM {$link->qjoinTable}";
01354                     break;
01355                 case HAS_ONE:
01356                 case HAS_MANY:
01357                     $link->init();
01358                     $sql = "DELETE FROM {$link->assocTDG->qtableName}";
01359                     break;
01360                 default:
01361                     continue;
01362                 }
01363                 if ($this->dbo->execute($sql) == false) {
01364                     $this->dbo->completeTrans(false);
01365                     return false;
01366                 }
01367             }
01368         }
01369 
01370         $sql = "DELETE FROM {$this->qtableName}";
01371         if ($this->dbo->execute($sql) == false) {
01372             $this->dbo->completeTrans(false);
01373             return false;
01374         }
01375 
01376         // 提交事务
01377         $this->dbo->completeTrans();
01378 
01379         return true;
01380     }
01381 
01387     function enableLinks($links = null)
01388     {
01389         $this->autoLink = true;
01390         if (is_null($links)) {
01391             $links = array_keys($this->links);
01392         } elseif (!is_array($links)) {
01393             $links = explode(',', $links);
01394             $links = array_filter(array_map('trim', $links), 'strlen');
01395         }
01396 
01397         foreach ($links as $name) {
01398             $name = strtoupper($name);
01399             if (isset($this->links[$name])) {
01400                 $this->links[$name]->enabled = true;
01401             }
01402         }
01403     }
01404 
01413     function enableLink($linkName)
01414     {
01415         $link =& $this->getLink($linkName);
01416         if ($link) { $link->enabled = true; }
01417         $this->autoLink = true;
01418         return $link;
01419     }
01420 
01426     function disableLinks($links = null)
01427     {
01428         if (is_null($links)) {
01429             $links = array_keys($this->links);
01430             $this->autoLink = false;
01431         } elseif (!is_array($links)) {
01432             $links = explode(',', $links);
01433             $links = array_filter(array_map('trim', $links), 'strlen');
01434         }
01435 
01436         foreach ($links as $name) {
01437             $name = strtoupper($name);
01438             if (isset($this->links[$name])) {
01439                 $this->links[$name]->enabled = false;
01440             }
01441         }
01442     }
01443 
01451     function disableLink($linkName)
01452     {
01453         $link =& $this->getLink($linkName);
01454         if ($link) { $link->enabled = false; }
01455         return $link;
01456     }
01457 
01461     function clearLinks()
01462     {
01463         $this->links = array();
01464     }
01465 
01469     function relink()
01470     {
01471         $this->clearLinks();
01472         $this->createLink($this->hasOne,     HAS_ONE);
01473         $this->createLink($this->belongsTo,  BELONGS_TO);
01474         $this->createLink($this->hasMany,    HAS_MANY);
01475         $this->createLink($this->manyToMany, MANY_TO_MANY);
01476     }
01477 
01485     function & getLink($linkName)
01486     {
01487         $linkName = strtoupper($linkName);
01488         if (isset($this->links[$linkName])) {
01489             return $this->links[$linkName];
01490         }
01491 
01492         FLEA::loadClass('FLEA_Db_Exception_MissingLink');
01493         __THROW(new FLEA_Db_Exception_MissingLink($linkName));
01494         $ret = false;
01495         return $ret;
01496     }
01497 
01505     function & getLinkTable($linkName)
01506     {
01507         $link =& $this->getLink($linkName);
01508         $link->init();
01509         return $link->assocTDG;
01510     }
01511 
01519     function & existsLink($name)
01520     {
01521         $name = strtoupper($name);
01522         return isset($this->links[$name]);
01523     }
01524 
01533     function createLink($defines, $type)
01534     {
01535         if (!is_array($defines)) { return; }
01536         if (!is_array(reset($defines))) {
01537             $defines = array($defines);
01538         }
01539 
01540         // 创建关联对象
01541         foreach ($defines as $define) {
01542             if (!is_array($define)) { continue; }
01543             // 构造连接对象实例
01544             $link =& FLEA_Db_TableLink::createLink($define, $type, $this);
01545             $this->links[strtoupper($link->name)] =& $link;
01546         }
01547     }
01548 
01554     function removeLink($linkName)
01555     {
01556         $linkName = strtoupper($linkName);
01557         if (isset($this->links[$linkName])) {
01558             unset($this->links[$linkName]);
01559         }
01560     }
01561 
01572     function checkRowData(& $row, $skip = 0) {
01573         if (is_null($this->verifier)) { return false; }
01574         $this->lastValidationResult = $this->verifier->checkAll($row, $this->meta, $skip);
01575         return empty($this->lastValidationResult);
01576     }
01577 
01585     function getLastValidation($info = null) {
01586         if (is_null($info)) { return $this->lastValidationResult; }
01587 
01588         $arr = array();
01589         foreach ($this->lastValidationResult as $field => $check) {
01590             if (empty($check['rule'][$info])) {
01591                 $arr[] = $field;
01592             } else {
01593                 $arr[] = $check['rule'][$info];
01594             }
01595         }
01596         return $arr;
01597     }
01598 
01604     function newInsertId() {
01605         return $this->dbo->nextId($this->fullTableName . '_seq');
01606     }
01607 
01616     function execute($sql, $inputarr = false)
01617     {
01618         return $this->dbo->execute($sql, $inputarr);
01619     }
01620 
01629     function qinto($sql, $params = null)
01630     {
01631         if (!is_array($params)) {
01632             FLEA::loadClass('FLEA_Exception_TypeMismatch');
01633             return __THROW(new FLEA_Exception_TypeMismatch('$params', 'array', gettype($params)));
01634         }
01635         $arr = explode('?', $sql);
01636         $sql = array_shift($arr);
01637         foreach ($params as $value) {
01638             $sql .= $this->dbo->qstr($value) . array_shift($arr);
01639         }
01640         return $sql;
01641     }
01642     
01662     function parseWhere($where, $args = null)
01663     {
01664         if (!is_array($args)) {
01665             $args = array();
01666         }
01667         if (is_array($where)) {
01668             return $this->_parseWhereArray($where);
01669         } else {
01670             return $this->_parseWhereString($where, $args);
01671         }
01672     }
01673     
01681     function _parseWhereArray($where)
01682     {
01691         $parts = array();
01692         $callback = array($this->dbo, 'qstr');
01693         $next_op = '';
01694 
01695         foreach ($where as $key => $value) {
01696             if (is_int($key)) {
01697                 $parts[] = $value;
01698                 if ($value == ')') {
01699                     $next_op = 'AND';
01700                 } else {
01701                     $next_op = '';
01702                 }
01703             } else {
01704                 if ($next_op != '') {
01705                     $parts[] = $next_op;
01706                 }
01707                 $field = $this->_parseWhereQfield(array('', $key));
01708                 if (is_array($value)) {
01709                     $value = array_map($callback, $value);
01710                     $parts[] = $field . ' IN (' . implode(',', $value) . ')';
01711                 } else {
01712                     $value = $this->dbo->qstr($value);
01713                     $parts[] = $field . ' = ' . $value;
01714                 }
01715                 $next_op = 'AND';
01716             }
01717         }
01718 
01719         return implode(' ', $parts);
01720     }
01721     
01730     function _parseWhereString($where, $args = null)
01731     {
01741         // 首先从查询条件中提取出可以识别的字段名
01742         if (strpos($where, '[') !== false) {
01743             // 提取字段名
01744             $where = preg_replace_callback('/\[([a-z0-9_\-\.]+)\]/i', array($this, '_parseWhereQfield'), $where);
01745         }
01746         
01747         return $this->qinto($where, $args);
01748     }
01749 
01757     function _parseWhereQfield($matches)
01758     {
01759         $p = explode('.', $matches[1]);
01760         switch (count($p)) {
01761         case 3:
01762             list($schema, $table, $field) = $p;
01763             if ($table == $this->tableName) {
01764                 $table = $this->fullTableName;
01765             }
01766             return $this->dbo->qfield($field, $table, $schema);
01767         case 2:
01768             list($table, $field) = $p;
01769             if ($table == $this->tableName) {
01770                 $table = $this->fullTableName;
01771             }
01772             return $this->dbo->qfield($field, $table);
01773         default:
01774             return $this->dbo->qfield($p[0]);
01775         }
01776     }
01777 
01785     function qstr($value)
01786     {
01787         return $this->dbo->qstr($value);
01788     }
01789 
01798     function qfield($fieldName, $tableName = null)
01799     {
01800         if (is_null($tableName)) {
01801             $tableName = $this->fullTableName;
01802         }
01803         return $this->dbo->qfield($fieldName, $tableName, $this->schema);
01804     }
01805 
01815     function qfields($fieldsName, $tableName = null, $returnArray = false)
01816     {
01817         if (is_null($tableName)) {
01818             $tableName = $this->fullTableName;
01819         }
01820         return $this->dbo->qfields($fieldsName, $tableName, $this->schema, $returnArray);
01821     }
01822 
01831     function getWhere($conditions, $queryLinks = true) {
01832         // 处理查询条件
01833         $where = FLEA_Db_SqlHelper::parseConditions($conditions, $this);
01834         $sqljoin = '';
01835         $distinct = '';
01836 
01837         do {
01838             if (!is_array($where)) {
01839                 $whereby = $where != '' ? " WHERE {$where}" : '';
01840                 break;
01841             }
01842 
01843             $arr = $where;
01844