00001 <?php
00023 abstract class spunQ_StorableObject extends spunQ_DataObject {
00024
00037 public static function insertMultiple(array $objects, spunQ_IDatabaseConnection $connection = NULL) {
00038 if (empty($objects)) {
00039 return NULL;
00040 }
00041 if ($connection === NULL) {
00042 $connection = db();
00043 }
00044 $type = reset($objects)->_getType();
00045 $memberNames = array_keys($type->getOwnMembersRecursive());
00046 foreach ($objects as $object) {
00047 foreach ($memberNames as $memberName) {
00048 $object->remoteValues[$memberName] = $object->$memberName;
00049 }
00050 }
00051 try {
00052 $connection->insert($objects, $type);
00053 foreach ($objects as $object) {
00054 # Members have been persisted.
00055 $object->locallyModifiedMembers = array();
00056 }
00057 } catch (Exception $e) {
00058 foreach ($objects as $object) {
00059 # Delete remote values cache, we cannot know what was
00060 # inserted into the database.
00061 $object->remoteValues = array();
00062 }
00063 throw $e;
00064 }
00065 return NULL;
00066 }
00067
00077 public static function updateMultiple(array $objects) {
00078 if (spunQ::inMode(spunQ::MODE_DEVELOPMENT)) {
00079 $connection = NULL;
00080 foreach ($objects as $object) {
00081 if ($connection === NULL) {
00082 $connection = $object->getConnection();
00083 } else {
00084 if ($object->getConnection() !== $connection) {
00085 throw new spunQ_InvalidArgumentError('UpdateOnDifferentConnections', __CLASS__, __FUNCTION__, 'objects');
00086 }
00087 }
00088 }
00089 }
00090 # Currenlty, there is no speed improvement :)
00091 # There might be one in the future, though!
00092 foreach ($objects as $object) {
00093 $object->update();
00094 }
00095 return NULL;
00096 }
00097
00107 public static function deleteMultiple(array $objects) {
00108 if (empty($objects)) {
00109 return NULL;
00110 }
00111 $typeToIds = array();
00112 if (spunQ::inMode(spunQ::MODE_DEVELOPMENT)) {
00113 $connection = NULL;
00114 foreach ($objects as $object) {
00115 # XXX: Changes here should be performed below, too
00116 $typeName = $object->_getType()->getName();
00117 if (!isset($typeToIds[$typeName])) {
00118 $typeToIds[$typeName] = array();
00119 }
00120 $typeToIds[$typeName][] = $object->getId();
00121 if ($connection === NULL) {
00122 $connection = $object->getConnection();
00123 } else {
00124 if ($object->getConnection() !== $connection) {
00125 throw new spunQ_InvalidArgumentError('DeleteOnDifferentConnections', __CLASS__, __FUNCTION__, 'objects');
00126 }
00127 }
00128 }
00129 } else {
00130 foreach ($objects as $object) {
00131 # XXX: Changes here should be performed above, too
00132 $typeName = $object->_getType()->getName();
00133 if (!isset($typeToIds[$typeName])) {
00134 $typeToIds[$typeName] = array();
00135 }
00136 $typeToIds[$typeName][] = $object->getId();
00137 }
00138 }
00139 $delete = new spunQ_DeleteQuery();
00140 $delete->addCondition('id IN {0}');
00141 foreach ($typeToIds as $type => $ids) {
00142 $delete->setType($type);
00143 $delete->execute(array($ids), reset($objects)->getConnection());
00144 }
00145 # We could have done that above, but for the sake of integrity, we're
00146 # only setting the ids to NULL if the query performed successfully.
00147 foreach ($objects as $object) {
00148 $object->id = NULL;
00149 }
00150 return NULL;
00151 }
00152
00158 public static function sortByDisplayString($objects) {
00159 $objectNames = array();
00160 foreach($objects as $object) {
00161 if (!($object instanceof spunQ_StorableObject)) {
00162 sort($objects);
00163 return $objects;
00164 }
00165 $objectNames[] = $object->_getDisplayString();
00166 }
00167 asort($objectNames);
00168 $returnObjects = array();
00169 foreach($objectNames as $idx=>$name) {
00170 $returnObjects[] = $objects[$idx];
00171 }
00172 return $returnObjects;
00173 }
00174
00181 protected static function loadInjectedMember($connection, spunQ_InjectedMember $injectedMember) {
00182 $memberName = $injectedMember->getName();
00183 $objects = array();
00184 foreach ($connection->getCachedObjects($injectedMember->getSource()->getType()) as $object) {
00185 if (!isset($object->remoteValues[$memberName])) {
00186 $objects[$object->getId()] = $object;
00187 }
00188 }
00189 $query = new spunQ_SelectQuery();
00190 $query->setType($injectedMember->getSource()->getOwningType()->getName());
00191 $query->addCondition($injectedMember->getSource()->getName() . ' IN {0}');
00192 $objectsToInjectings = array();
00193 foreach ($query->execute(array(array_keys($objects))) as $injectingObject) {
00194 $objectId = $injectingObject->_getMember($injectedMember->getSource()->getName())->getId();
00195 if (!isset($objectsToInjectings[$objectId])) {
00196 $objectsToInjectings[$objectId] = array();
00197 }
00198 $objectsToInjectings[$objectId][] = $injectingObject;
00199 }
00200 if ($injectedMember->getSource()->getOptions()->keyExists('unique')) {
00201 foreach ($objects as $objectId => $object) {
00202 $result = NULL;
00203 if (isset($objectsToInjectings[$objectId])) {
00204 $result = reset($objectsToInjectings[$objectId]);
00205 }
00206 $object->remoteValues[$memberName] = $result;
00207 $object->$memberName = $result;
00208 }
00209 } else {
00210 foreach ($objects as $objectId => $object) {
00211 $result = array();
00212 if (isset($objectsToInjectings[$objectId])) {
00213 $result = $objectsToInjectings[$objectId];
00214 }
00215 $object->remoteValues[$memberName] = $result;
00216 $object->$memberName = $result;
00217 }
00218 }
00219 return NULL;
00220 }
00221
00228 protected $id;
00229
00235 protected $remoteValues = array();
00236
00240 protected $locallyModifiedMembers = array();
00241
00247 protected $connection = NULL;
00248
00256 public function setId($id) {
00257 $this->id = $id;
00258 return $this;
00259 }
00260
00265 public function getId() {
00266 return $this->id;
00267 }
00268
00273 public function store() {
00274 if (!$this->getId()) {
00275 return $this->insert();
00276 } else {
00277 return $this->update();
00278 }
00279 }
00280
00285 public function insert() {
00286 self::insertMultiple(array($this), $this->getConnection());
00287 return $this;
00288 }
00289
00294 public function delete() {
00295 self::deleteMultiple(array($this), $this->getConnection());
00296 return $this;
00297 }
00298
00303 public function update() {
00304 $performUpdate = false;
00305 $update = new spunQ_UpdateQuery();
00306 $update->setType($this->_type->getName());
00307 foreach ($this->_type->getOwnMembersRecursive() as $member) {
00308 if ($member->getName() === 'id') {
00309 continue;
00310 }
00311 $memberName = $member->getName();
00312 if (!isset($this->locallyModifiedMembers[$memberName])) {
00313 continue;
00314 }
00315 $performUpdate = true;
00316 $update->addValue($memberName, $this->$memberName);
00317 $this->remoteValues[$memberName] = $this->$memberName;
00318 }
00319 if (!$performUpdate) {
00320 return $this;
00321 }
00322 $this->locallyModifiedMembers = array();
00323 $update->setConditions(array('id={0}'));
00324 $update->execute(array($this->getId()), $this->getConnection());
00325 return $this;
00326 }
00327
00336 public function assureExists() {
00337 if ($this->getId() == 0) {
00338 throw new spunQ_InvalidObjectException('ObjectHasNoId', $this->_getType()->getFullName());
00339 }
00340 throw new spunQ_Exception('Not implemented');
00341 return $this;
00342 }
00343
00349 public function setConnection(spunQ_IDatabaseConnection $connection) {
00350 if ($this->connection !== NULL) {
00351 $this->connection->removeFromCache($this);
00352 }
00353 if ($this->getId() != NULL) {
00354 $connection->addToCache($this);
00355 }
00356 $this->connection = $connection;
00357 return $this;
00358 }
00359
00364 public function getConnection() {
00365 if ($this->connection === NULL) {
00366 $this->setConnection(spunQ_db::getDefaultConnection());
00367 }
00368 return $this->connection;
00369 }
00370
00374 public function handleSet($memberName, $arguments, $functionName) {
00375 $result = parent::handleSet($memberName, $arguments, $functionName);
00376 $this->locallyModifiedMembers[$memberName] = 1;
00377 return $result;
00378 }
00379
00394 public function _loadRemoteValues(array $values, $checkValues = true) {
00395 # optimzation: removing the overhead of calling a function.
00396 # the next lines could be replaced by $myType = $this->_getType();
00397 if ($this->_type instanceof spunQ_Type) {
00398 $myType = $this->_type;
00399 } else {
00400 $myType = $this->_getType();
00401 }
00402 if ($checkValues && spunQ::inMode(spunQ::MODE_SPUNQ_DEVELOPMENT)) {
00403 foreach ($values as $memberName => $value) {
00404 $myType->getMember($memberName)->check($value);
00405 }
00406 }
00407 $this->remoteValues = $values + $this->remoteValues;
00408 $myMembers = $myType->getOwnMembersRecursive();
00409 foreach ($values as $memberName => $value) {
00410 if ($memberName !== '_type' && !isset($this->locallyModifiedMembers[$memberName]) && isset($myMembers[$memberName])) {
00411 $this->$memberName = $value;
00412 }
00413 }
00414 return $this;
00415 }
00416
00417 public function _invalidateRemoteValues(array $memberNames) {
00418 foreach ($memberNames as $memberName) {
00419 unset($this->remoteValues[$memberName]);
00420 }
00421 }
00422
00427 public function _getDisplayString() {
00428 if ($this->_getType()->memberExists('name')) {
00429 return $this->getName();
00430 }
00431 if ($this->_getType()->memberExists('title')) {
00432 return $this->getTitle();
00433 }
00434 if ($this->_getType()->memberExists('alias')) {
00435 return $this->getAlias();
00436 }
00437 if ($this->_getType()->memberExists('text')) {
00438 return $this->getText();
00439 }
00440 info(
00441 'There is neither a "name" nor a "title" member available for this object. ' .
00442 'Override the function "_getDisplayString()" in the class "' .
00443 $this->_getType()->getName() . '" if you want more than the id. '
00444 );
00445 return $this->getId();
00446 }
00447
00453 public function _remoteValueLoaded($memberName) {
00454 return array_key_exists($memberName, $this->remoteValues);
00455 }
00456
00460 public function referencingObjects($otherType, $otherMember, $order = NULL, $limit = NULL) {
00461 $selectQuery = $this->_createQueryForReferencingObjects($otherType, $otherMember, $order, $limit);
00462 $queryParams = array('object' => $this);
00463 return $selectQuery->execute($queryParams);
00464 }
00465
00469 public function referencingObjectIds($otherType, $otherMember, $order = NULL, $limit = NULL) {
00470 $selectQuery = $this->_createQueryForReferencingObjects($otherType, $otherMember, $order, $limit);
00471 $selectQuery->setProperties(array('id'));
00472 $selectQuery->setReturnType(spunQ_SelectQuery::RETURN_SINGLE_PROPERTY);
00473 $queryParams = array('object' => $this);
00474 return $selectQuery->execute($queryParams);
00475 }
00476
00480 public function countReferencingObjects($otherType, $otherMember) {
00481 $selectQuery = $this->_createQueryForReferencingObjects($otherType, $otherMember);
00482 $queryParams = array('object' => $this);
00483 return $selectQuery->count($queryParams);
00484 }
00485
00491 protected function loadMember($memberName) {
00492 if (array_key_exists($memberName, $this->remoteValues)) {
00493 # Member already loaded.
00494 return $this;
00495 }
00496 if ($this->getId() == 0) {
00497 # Object has no id, member cannot be loaded.
00498 return $this;
00499 }
00500 $missingMembers = array_diff_key($this->_getType()->getOwnMembersRecursive(), $this->remoteValues);
00501 # The array addition is there to move the $memberName element to the
00502 # first position in the parameter to loadOnDemand.
00503 $this->getConnection()->loadOnDemand($this, array_keys(array($memberName => 1) + $missingMembers));
00504 return $this;
00505 }
00506
00510 protected function handleGet($memberName, $arguments, $functionName) {
00511 if (isset($this->locallyModifiedMembers[$memberName]) || array_key_exists($memberName, $this->remoteValues)) {
00512 # This value was already set, treat it as any other variable.
00513 return parent::handleGet($memberName, $arguments, $functionName);
00514 }
00515 $myType = $this->_getType();
00516 $injected = $myType->getInjectedMembersRecursive();
00517 if (isset($injected[$memberName])) {
00518 $injectedMember = $injected[$memberName];
00519 # injected member
00520 if ($this->getId() != 0) {
00521 self::loadInjectedMember($this->getConnection(), $injectedMember);
00522 $result = $this->remoteValues[$memberName];
00523 } else {
00524 if ($injectedMember->getSource()->getOptions()->keyExists('unique')) {
00525 $result = NULL;
00526 } else {
00527 $result = array();
00528 }
00529 }
00530 return $result;
00531 }
00532 $this->loadMember($memberName);
00533 $result = parent::handleGet($memberName, $arguments, $functionName);
00534 if ($myType->getMember($memberName)->getOptions()->keyExists('mappedBy')) {
00535 # Do not cache this mapped value
00536 unset($this->remoteValues[$memberName]);
00537 }
00538 return $result;
00539 }
00540
00544 protected function handleAdd($memberName, $arguments, $functionName) {
00545 if (property_exists($this, $memberName)) {
00546 $this->loadMember($memberName);
00547 $this->locallyModifiedMembers[$memberName] = 1;
00548 }
00549 $memberNamePlural = spunQ_Grammar::pluralize($memberName);
00550 if (property_exists($this, $memberNamePlural)) {
00551 $this->loadMember($memberNamePlural);
00552 $this->locallyModifiedMembers[$memberNamePlural] = 1;
00553 }
00554 return parent::handleAdd($memberName, $arguments, $functionName);
00555 }
00556
00560 protected function handleRemove($memberName, $arguments, $functionName) {
00561 if (property_exists($this, $memberName)) {
00562 $this->loadMember($memberName);
00563 $this->locallyModifiedMembers[$memberName] = 1;
00564 }
00565 $memberNamePlural = spunQ_Grammar::pluralize($memberName);
00566 if (property_exists($this, $memberNamePlural)) {
00567 $this->loadMember($memberNamePlural);
00568 $this->locallyModifiedMembers[$memberNamePlural] = 1;
00569 }
00570 return parent::handleRemove($memberName, $arguments, $functionName);
00571 }
00572
00576 protected function handleGetUntranslated($memberName, $arguments, $functionName) {
00577 if (property_exists($this, $memberName)) {
00578 $this->loadMember($memberName);
00579 }
00580 return parent::handleGetUntranslated($memberName, $arguments, $functionName);
00581 }
00582
00586 protected function handleGetFromArray($memberName, $arguments, $functionName) {
00587 if (property_exists($this, $memberName)) {
00588 $this->loadMember($memberName);
00589 }
00590 return parent::handleGetFromArray($memberName, $arguments, $functionName);
00591 }
00592
00596 private function _createQueryForReferencingObjects($otherType, $otherMember, $order = NULL, $limit = NULL, $justCount = false) {
00597 $selectQuery = new spunQ_SelectQuery();
00598 $selectQuery->setType($otherType);
00599 if ($limit !== NULL) {
00600 $selectQuery->setLimit($limit);
00601 }
00602 if ($order !== NULL) {
00603 if (is_array($order)) {
00604 $selectQuery->setOrder($order);
00605 } else {
00606 $selectQuery->addOrder($order);
00607 }
00608 }
00609 $member = spunQ_Type::getByName($otherType)->getMember($otherMember);
00610 if ($member->getType() instanceof spunQ_UserType) {
00611 $selectQuery->addCondition($otherMember . ' = {object}');
00612 } elseif ($member->getType() instanceof spunQ_ArrayType) {
00613 $selectQuery->addCondition($otherMember .'._value = {object}');
00614 } else {
00615 throw new spunQ_Exception('not implemented yet.');
00616 }
00617 return $selectQuery;
00618 }
00619
00620 }
00621