00001 <?php
00002
00031 abstract class SDatabaseModel extends SModel2 {
00032
00033 private $DBI = null;
00034
00035 private $tableName;
00036
00037 private $primaryKey;
00038
00039 private $primaryKeyIsAuto;
00040
00041 private $linkage;
00042
00043 private $_reset;
00044
00045 private $_inTransaction = false;
00046
00047
00048 private static $_cache = array();
00049
00050
00051
00052
00053
00070 public function populate($constraints, $multi = false) {
00071
00072 if($multi)
00073 list($result, $mapping) = self::getListBaseMulti($this->DBI,
00074 $constraints,
00075 array(1),
00076 array(),
00077 false,
00078 get_class($this),
00079 false);
00080 else
00081 $result = self::getListBase($this->DBI,
00082 $constraints,
00083 array(1),
00084 array(),
00085 false,
00086 get_class($this),
00087 false);
00088
00089 if(!$result) {
00090 $this->setError('Error attempting to populate');
00091 return false;
00092 }
00093
00094
00095 if($result->numRows() == 0) {
00096 $this->setError('No object found for constraints ' . print_r($constraints, true));
00097 return false;
00098 }
00099
00100
00101 if($multi) {
00102 if(!$this->setAttrsFromMultiDBResult($result->nextRow(), $mapping))
00103 return false;
00104 }
00105 else {
00106 if(!$this->setAttrsFromDBResult($result->nextRow()))
00107 return false;
00108 }
00109
00110 $this->updateBase();
00111
00112 return true;
00113 }
00114
00130 protected function populateWith(&$row, $mapping = null, $as = null) {
00131 $toDo = array();
00132 foreach($row as $fullAttr => $value) {
00133 $dotPos = strrpos($fullAttr, '.');
00134 $attr = substr($fullAttr, $dotPos + 1);
00135 $prefix = substr($fullAttr, 0, $dotPos);
00136 if($as !== null && $prefix != $as)
00137 continue;
00138 if($mapping != null) {
00139 if(!isset($mapping[$prefix][0]))
00140 continue;
00141 $table = $mapping[$prefix][0];
00142 }
00143 else
00144 $table = $prefix;
00145 if($table != $this->tableName)
00146 continue;
00147 unset($row[$fullAttr]);
00148 $this->set($attr, $value);
00149 $foreign = $this->getAttributeForeign($attr);
00150 if(count($foreign) > 0 && isset($mapping["$prefix.$foreign[object]"]) && $value != null) {
00151 $cls = $this->getAttributeType($foreign['object']);
00152 if(self::$_cache === null || !isset(self::$_cache[$cls][$value])) {
00153 $obj = new $cls();
00154 $toDo[] = array($foreign['object'], $obj, "$prefix.$foreign[object]", $value);
00155 }
00156 else
00157 $this->set($foreign['object'], self::$_cache[$cls][$value]);
00158 }
00159 }
00160
00161 foreach($toDo as $td) {
00162 $td[1]->populateWith($row, $mapping, $td[2]);
00163 if(self::$_cache !== null)
00164 self::$_cache[get_class($td[1])][$td[3]] = $td[1];
00165 $this->set($td[0], $td[1]);
00166 }
00167
00168 $this->updateBase();
00169
00170 return true;
00171 }
00172
00186 protected function setAttrsFromDBResult($dbResult) {
00187
00188 foreach($this->getAttributes(true) as $attrName => $attrProps) {
00189 if($attrProps['commit'] == self::COMMIT_FIRST)
00190 $this->changeAttribute($attrName, 'commit', self::COMMIT_CHANGED);
00191
00192 $dbAttrName = 'Base.' . $attrName;
00193 if(isset($dbResult[$dbAttrName])) {
00194
00195
00196 switch($attrProps['type']) {
00197 case 'int':
00198 case 'string':
00199 case 'float':
00200 case 'time':
00201
00202 $this->set($attrName, $dbResult[$dbAttrName]);
00203 break;
00204 default:
00205
00206 $cls = $this->attrProps['type'];
00207 $obj = new $cls();
00208 if ($obj == null) {
00209 $this->setError("Could not instantiate class '$cls'"
00210 . " while processing attribute '$attrName'");
00211 return false;
00212 }
00213
00214 $obj->fromDB($dbResult[$dbAttrName]);
00215
00216 $this->set($attrName, $obj);
00217 break;
00218 }
00219 }
00220 }
00221
00222 return true;
00223 }
00224
00242 protected function setAttrsFromMultiDBResult($dbResult, $mapping = null, $as = null) {
00243 $tableName = ($mapping == null ? $this->tableName : ($as == null ? 'Base' : $as));
00244
00245 foreach($this->getAttributes(true) as $attrName => $attrProps) {
00246 if($attrProps['commit'] == self::COMMIT_FIRST)
00247 $this->changeAttribute($attrName, 'commit', self::COMMIT_CHANGED);
00248
00249 if(isset($dbResult[$tableName . '.' . $attrName])) {
00250
00251
00252 switch($attrProps['type']) {
00253 case 'int':
00254 case 'string':
00255 case 'float':
00256 case 'time':
00257
00258 $this->set($attrName, $dbResult[$tableName . '.' . $attrName]);
00259 if(count($attrProps['foreign']) > 0) {
00260 $cls = $this->getAttributeType($attrProps['foreign']['object']);
00261 $obj = new $cls();
00262 if ($obj == null) {
00263 $this->setError("Could not instantiate class '$cls'"
00264 . " while processing attribute '$attrName'");
00265 return false;
00266 }
00267 $obj->populateWith($dbResult, $mapping, "Base.{$attrProps['foreign']['object']}");
00268
00269 $this->set($attrProps['foreign']['object'], $obj);
00270 }
00271 break;
00272 default:
00273
00274 $cls = $this->attrProps['type'];
00275 $obj = new $cls();
00276 if ($obj == null) {
00277 $this->setError("Could not instantiate class '$cls'"
00278 . " while processing attribute '$attrName'");
00279 return false;
00280 }
00281
00282 $obj->fromDB($dbResult[$tableName . '.' . $attrName]);
00283
00284 $this->set($attrName, $obj);
00285 break;
00286 }
00287 }
00288 }
00289
00290 return true;
00291 }
00292
00308 public function populateFromResults($results) {
00309 return $this->setAttrsFromDBResult($results);
00310 }
00311
00327 public function populateFromResultsMulti($results, $mapping = null, $as = null) {
00328 return $this->setAttrsFromMultiDBResult($results, $mapping, $as);
00329 }
00330
00370 public function commit($updateObj = false) {
00371
00372 $DBI = $this->DBI;
00373
00374
00375 if($this->isMarkedForDeletion()) {
00376 if(!$this->isStored()) {
00377 $this->setError('Attempted to delete an object that is not stored.');
00378 return false;
00379 }
00380 $DBI->startTransaction($updateObj ? null : array($this, 'finishDelete'));
00381 if(!$DBI->delete($this->getTableName(), $this->getConstraintForPrimaryKey())) {
00382 $DBI->cancelTransaction();
00383 return false;
00384 }
00385 if(!$this->onDelete()) {
00386 $DBI->cancelTransaction();
00387 return false;
00388 }
00389
00390
00391 if($updateObj)
00392 $this->finishDelete(true);
00393 $DBI->commitTransaction();
00394 return true;
00395 }
00396
00397
00398 if(!$this->isValid())
00399 return false;
00400
00401 if($this->isStored() || $this->_inTransaction) {
00402
00403 $DBI->startTransaction($updateObj ? null : array($this, 'finishUpdate'));
00404 list($okay, $reset, $what) = $this->prepareUpdate(true);
00405 if(!$okay) {
00406 $DBI->cancelTransaction();
00407 return false;
00408 }
00409 if(count($what) > 0) {
00410 if(($constraints = $this->getConstraintForPrimaryKey(true)) === false ||
00411 !$DBI->update($this->getTableName(), $what, $constraints))
00412 {
00413 $DBI->cancelTransaction();
00414 return false;
00415 }
00416 if(!$this->onUpdate($what)) {
00417 $DBI->cancelTransaction();
00418 return false;
00419 }
00420 }
00421
00422 $this->_reset = $reset;
00423 if($updateObj)
00424 $this->finishUpdate(true);
00425 }
00426 else {
00427
00428 $DBI->startTransaction($updateObj ? null : array($this, 'finishInsert'));
00429 list($okay, $reset, $what) = $this->prepareUpdate(false);
00430 if(!$okay) {
00431 $DBI->cancelTransaction();
00432 return false;
00433 }
00434 if(($result = $DBI->insert($this->getTableName(), $what)) === false) {
00435 $DBI->cancelTransaction();
00436 if ($DBI->hasError) {
00437 $DBIerror = $DBI->getError();
00438 $this->setError("DBI Error: ".$DBIerror[0]);
00439 }
00440 return false;
00441 }
00442
00443 $pk = $this->getPrimaryKey();
00444 if($pk && ($this->primaryKeyIsAuto && !is_array($pk)))
00445 $this->set($pk, $result);
00446
00447 if(!$this->onInsert($what)) {
00448 $DBI->cancelTransaction();
00449 return false;
00450 }
00451
00452 $this->_reset = $reset;
00453 if($updateObj)
00454 $this->finishInsert(true);
00455 }
00456
00457 $this->_inTransaction = true;
00458
00459 $DBI->commitTransaction();
00460
00461 return true;
00462 }
00463
00474 public function finishDelete($status) {
00475 if(!$status)
00476 return;
00477
00478 $this->onDeleteFinish();
00479 $this->nullify();
00480 }
00481
00494 public function finishUpdate($status) {
00495
00496 $this->_inTransaction = false;
00497
00498 if(!$status)
00499 return;
00500
00501
00502 foreach($this->_reset as $r)
00503 $this->changeAttribute($r, 'commit', self::COMMIT_CHANGED);
00504
00505 $this->updateBase();
00506
00507 $this->onUpdateFinish();
00508 }
00509
00522 public function finishInsert($status) {
00523 $this->_inTransaction = false;
00524
00525 if(!$status)
00526 return;
00527
00528
00529 foreach($this->_reset as $r)
00530 $this->changeAttribute($r, 'commit', self::COMMIT_CHANGED);
00531
00532 $this->updateBase();
00533
00534 $this->onInsertFinish();
00535 }
00536
00550 protected function prepareUpdate($isUpdate) {
00551
00552 $what = array();
00553
00554
00555 $reset = array();
00556
00557
00558 $okay = true;
00559
00560 foreach($this->getAttributes(true) as $attrName => $attrProps) {
00561 switch($attrProps['commit']) {
00562 case self::COMMIT_NEVER:
00563 case self::COMMIT_FOREIGN:
00564 case self::COMMIT_NOSOURCE:
00565 continue 2;
00566 case self::COMMIT_FIRST:
00567 $reset[] = $attrName;
00568 if(!$isUpdate)
00569 break;
00570 case self::COMMIT_CHANGED:
00571
00572 if($attrProps['type'] != 'int' && $attrProps['type'] != 'string' && $attrProps['type'] != 'float'
00573 && $attrProps['type'] != 'time')
00574 {
00575 $obj = $this->get($attrName);
00576 if($obj instanceof SModel2 && !$obj->isDirty())
00577 continue 2;
00578 }
00579 else if(count($attrProps['foreign']) > 0) {
00580 $tgt = $this->get($attrProps['foreign']['object']);
00581 if($tgt != null) {
00582 if(!($tgt instanceof SDatabaseModel))
00583 continue 2;
00584 if(!$tgt->isDirty() && $this->get($attrName) == $tgt->get($attrProps['foreign']['on']))
00585 continue 2;
00586 }
00587 }
00588 else if(!$this->isChanged($attrName))
00589 continue 2;
00590 break;
00591 }
00592 switch($attrProps['type']) {
00593 case 'int':
00594 case 'float':
00595 case 'string':
00596 if($attrProps['array']) {
00597 $this->setError('Cannot commit an array to a database field.');
00598 $okay = false;
00599 }
00600 else if(count($attrProps['foreign']) > 0) {
00601 $tgt = $this->get($attrProps['foreign']['object']);
00602 if($tgt && $tgt instanceof SDatabaseModel) {
00603 if(!$tgt->commit()) {
00604 $this->getErrorFrom($tgt);
00605 $this->setError("Failed to commit child entity for $attrName; bailing");
00606 return array(false, array(), array());
00607 }
00608 $fKey = $tgt->get($attrProps['foreign']['on']);
00609 if($fKey != $this->get($attrName)) {
00610 $this->set($attrName, $fKey);
00611 $what[$attrName] = $fKey;
00612 }
00613 }
00614 else
00615 $what[$attrName] = $this->get($attrName);
00616 }
00617 else
00618 $what[$attrName] = $this->get($attrName);
00619 break;
00620 case 'time':
00621 if($attrProps['array']) {
00622 $this->setError('Cannot commit an array to a database field.');
00623 $okay = false;
00624 }
00625 else {
00626 $val = $this->get($attrName);
00627 if(is_array($val))
00628 $val = $val[0];
00629 if(strtolower($val) == 'now()' || strtolower($val) == 'current_timestamp')
00630 $what[$attrName] = array($val);
00631 else
00632 $what[$attrName] = $val;
00633 }
00634 break;
00635 default:
00636 if($attrProps['array']) {
00637 $this->setError('Cannot commit an array of objects to a database field.');
00638 $okay = false;
00639 }
00640 else {
00641 $obj = $this->get($attrName);
00642 if($obj instanceOf SModel2) {
00643 if(!$obj->commit())
00644 $this->getErrorFrom($obj);
00645 else {
00646 $this->set($fieldToUpdate, $obj->get($obj->getPrimaryKey()));
00647 $what[$fieldToUpdate] = $this->get($fieldToUpdate);
00648 }
00649 }
00650 }
00651 break;
00652 }
00653 }
00654
00655 return array($okay, $reset, $what);
00656 }
00657
00670 public function getConstraintForPrimaryKey($skipStorageCheck = false) {
00671 if(!$skipStorageCheck && !$this->isStored()) {
00672 $this->setError('I am not stored! Probably an internal error...');
00673 return false;
00674 }
00675 if($this->primaryKey == '') {
00676 $this->setError('No primary key set; cannot commit');
00677 return false;
00678 }
00679
00680 if (is_array($this->primaryKey)) {
00681 $return = array();
00682 foreach($this->primaryKey as $pk) {
00683 $return[$pk] = $this->get($pk);
00684 }
00685 return $return;
00686 }
00687
00688 else
00689 return array($this->primaryKey => $this->get($this->primaryKey));
00690 }
00691
00702 public function copy($asNew = false) {
00703 $obj = parent::copy($asNew);
00704
00705 if(!$obj)
00706 return null;
00707
00708
00709 if($asNew) {
00710
00711
00712 if(!is_array($obj->primaryKey))
00713 $obj->set($this->primaryKey, $obj->getAttributeDefault($this->primaryKey), false);
00714 }
00715
00716 return $obj;
00717 }
00718
00742 protected static function getListBase($DBI, $constraints, $limit, $order, $count, $objClass, $asObjects = true,
00743 $extraJoins = array(), $groupBy = array(), $extraAttrs = array())
00744 {
00745 STimer::startAvg('getList');
00746 STimer::startAvg('getList:overhead');
00747
00748 $sq = new SQuery();
00749 $sq->setBaseClass($objClass);
00750 $sq->setMode(SQuery::MODE_PREFIX);
00751 $sq->setCriteria($constraints);
00752 $sq->setLimit($limit);
00753 $sq->setOrder($order);
00754 $sq->setCount($count);
00755 $sq->setExtraJoins($extraJoins);
00756 $sq->setExtraAttrs($extraAttrs);
00757 $sq->setGroupBy($groupBy);
00758
00759 $query = $sq->buildQuery();
00760 if($query === false) {
00761 self::setStaticError('Error building query');
00762 return null;
00763 }
00764
00765 $results = $DBI->query($query);
00766 if($results === null) {
00767 self::setStaticError('Error during getList()');
00768 return null;
00769 }
00770
00771 if ($count) {
00772 $row = $results->nextRow();
00773 return $row['COUNT(*)'];
00774 }
00775
00776 $attrs = null;
00777 if($asObjects) {
00778 $objects = array();
00779 while(($r = $results->nextRow())) {
00780 STimer::startAvg('getList:item');
00781 $obj = new $objClass;
00782 if($attrs === null)
00783 $attrs = $obj->getAttributes();
00784 foreach($attrs as $a) {
00785 if(isset($r['Base.' . $a]))
00786 $obj->set($a, $r['Base.' . $a]);
00787 }
00788 $obj->updateBase();
00789 $objects[] = $obj;
00790 STimer::endAvg('getList:item');
00791 }
00792 STimer::endAvg('getList');
00793 return $objects;
00794 }
00795 else {
00796 STimer::endAvg('getList');
00797 return $results;
00798 }
00799 }
00800
00821 protected static function getListBaseMulti($DBI, $constraints, $limit, $order, $count, $objClass, $asObjects = true,
00822 $depth = 1, $extraJoins = array(), $groupBy = array(),
00823 $extraAttrs = array(), $extraTables = array())
00824 {
00825 STimer::startAvg('getListMulti');
00826 STimer::startAvg('getListMulti:overhead');
00827
00828 $sq = new SQuery();
00829 $sq->setBaseClass($objClass);
00830 $sq->setMode(SQuery::MODE_MULTI);
00831 $sq->setDepth($depth);
00832 $sq->setCriteria($constraints);
00833 $sq->setLimit($limit);
00834 $sq->setOrder($order);
00835 $sq->setCount($count);
00836 $sq->setGroupBy($groupBy);
00837 $sq->setExtraJoins($extraJoins);
00838 $sq->setExtraTables($extraTables);
00839 $sq->setExtraAttrs($extraAttrs);
00840
00841 $query = $sq->buildQuery();
00842 if($query === false) {
00843 self::setStaticError('Error building query');
00844 return null;
00845 }
00846
00847 $result = $DBI->query($query);
00848 if(!$result) {
00849 self::setStaticError("Error during getListMulti()");
00850 return null;
00851 }
00852 STimer::endAvg('getListMulti:overhead');
00853
00854 if($count) {
00855 $row = $result->nextRow();
00856 return $row['COUNT(*)'];
00857 }
00858
00859 if($objClass != '') {
00860 $objects = array();
00861 self::$_cache = array();
00862 while(($r = $result->nextRow())) {
00863 STimer::startAvg('getListMulti:item');
00864 $obj = new $objClass();
00865 $obj->populateWith($r, $sq->getMapping(), 'Base');
00866 $objects[] = $obj;
00867 STimer::endAvg('getListMulti:item');
00868 }
00869 self::$_cache = array();
00870 STimer::endAvg('getListMulti');
00871 return $objects;
00872 }
00873 else {
00874 STimer::endAvg('getListMulti');
00875 return array($result, $mapping);
00876 }
00877 }
00878
00879
00880
00881
00882
00883
00884
00885
00886
00887
00888
00889
00903 protected function onDelete() {
00904 return true;
00905 }
00906
00914 protected function onDeleteFinish() {
00915 }
00916
00925 protected function onInsert($what) {
00926 return true;
00927 }
00928
00936 protected function onInsertFinish() {
00937 }
00938
00947 protected function onUpdate($what) {
00948 return true;
00949 }
00950
00957 protected function onUpdateFinish() {
00958 }
00959
00960
00961
00962
00963
00974 public function toDB() {
00975 return $this->get($this->primaryKey);
00976 }
00977
00986 public function fromDB($rep) {
00987 $this->populate($rep);
00988 }
00989
00997 public function getFormatted($field, $format) {
00998 if(!$this->attributeExists($field)) {
00999 $this->setError('No such field \'' . $field . '\'');
01000 return false;
01001 }
01002 return DBI2::formatTimestamp($this->$field, $format);
01003 }
01004
01012 public function setFormatted($field, $newValue) {
01013 if(!$this->attributeExists($field)) {
01014 $this->setError('No such field \'' . $field . '\'');
01015 return false;
01016 }
01017 $formattedVal = strftime("%F %T", strtotime($newValue));
01018 $this->$field = $formattedVal;
01019 return true;
01020 }
01021
01029 public function getXML($options = array()) {
01030 if(!isset($options['root']))
01031 $options['root'] = $this->tableName;
01032 return parent::getXML($options);
01033 }
01034
01040 public function getTableName() {
01041 return $this->tableName;
01042 }
01043
01049 public function getPrimaryKey() {
01050 return $this->primaryKey;
01051 }
01052
01061 protected function registerDBI($DBI) {
01062 if(!is_object($DBI) || !($DBI instanceof DBI2)) {
01063 $this->setError('DBI must be object and instance of DBI2!');
01064 return false;
01065 }
01066 $this->DBI = $DBI;
01067 return true;
01068 }
01069
01077 protected function registerTableName($tableName) {
01078 $this->tableName = $tableName;
01079 }
01080
01088 protected function registerPrimaryKey($primaryKey, $isAuto = true) {
01089 $this->primaryKeyIsAuto = $isAuto ? true : false;
01090 $this->primaryKey = $primaryKey;
01091 }
01092
01102 public static function isDatabaseAttributeStatic($info) {
01103 if(!((isset($info['type']) && $info['type'] != 'int' && $info['type'] != 'string'
01104 && $info['type'] != 'float' && $info['type'] != 'time')
01105 || (isset($info['commit']) && $info['commit'] == SModel2::COMMIT_NOSOURCE)
01106 || (isset($info['array']) && $info['array'] == true)))
01107 return true;
01108 else
01109 return false;
01110 }
01111
01120 public function isDatabaseAttribute($attrName) {
01121 $info = $this->getAttributeInfo($attrName);
01122 return self::isDatabaseAttribute_int($info);
01123 }
01124
01125 public function attributesToSQL($objName, $multi = false, $prefix = null) {
01126 $sq = new SQuery;
01127
01128 $sq->setBaseClass($objName);
01129 if($multi)
01130 $sq->setMode(SQuery::MODE_MULTI);
01131 else if($prefix != null) {
01132 $sq->setMode(SQuery::MODE_PREFIX);
01133 $sq->setPrefix($prefix);
01134 }
01135 else
01136 $sq->setMode(SQuery::MODE_SINGLE);
01137
01138 $sq->buildMapping();
01139
01140 return $sq->getAttributeList();
01141 }
01142 }
01143
01144 ?>