00001 <?php
00002
00009 class spunQ_DataObject {
00010
00054 public static function valueByProperty($value, $path) {
00055 if (!is_array($path)) {
00056 $path = explode('.', $path);
00057 }
00058 $first = array_shift($path);
00059 $value = self::propertyOfValue($value, $first);
00060 if (isset($path[0])) {
00061 return self::valueByProperty($value, $path);
00062 } else {
00063 return $value;
00064 }
00065 }
00066
00074 public static function propertyOfValue($value, $property) {
00075 if (is_array($value)) {
00076 if ($property === '_keys') {
00077 return range(0, count($value));
00078 }
00079 if ($property === '_values') {
00080 return array_values($value);
00081 }
00082 if ($property === '_key' && count($value) > 0) {
00083 reset($value);
00084 return key($value);
00085 }
00086 if ($property === '_value' && count($value) > 0) {
00087 return reset($value);
00088 }
00089 if ($property === '_count') {
00090 return count($value);
00091 }
00092 } elseif (is_string($value)) {
00093 if ($property === '_length') {
00094 return mb_strlen($value);
00095 }
00096 } elseif ($value instanceof spunQ_Map) {
00097 if ($property === '_keys') {
00098 return $value->getKeys();
00099 }
00100 if ($property === '_values') {
00101 return $value->getValues();
00102 }
00103 if ($property === '_key') {
00104 return $value->getRandomKey();
00105 }
00106 if ($property === '_value') {
00107 return $value->get($value->getRandomKey());
00108 }
00109 if ($property === '_count') {
00110 return $value->getSize();
00111 }
00112 } elseif ($value instanceof spunQ_Type) {
00113 try {
00114 return $value->_getMember($property);
00115 } catch (spunQ_UndefinedMemberError $e) {
00116 }
00117 } elseif ($value instanceof spunQ_DataObject) {
00118 if ($value->_getType()->memberExists($property)) {
00119 return $value->_getMember($property);
00120 }
00121 }
00122 throw new spunQ_InvalidPropertyException(spunQ_Type::getDescription($value), $property);
00123 }
00124
00129 protected $_type;
00130
00134 public function __construct() {
00135 # we used to check each member here to assign empty arrays and maps to
00136 # members that were declared as such (and weren't optional either) ...
00137 # ... until we found out that this was a HUGE performance hit.
00138 return NULL;
00139 }
00140
00148 public function __call($functionName, $arguments) {
00149 # Assert that this object has a type.
00150 # optimzation: removing the overhead of calling a function.
00151 # the next lines could be replaced by $this->_getType();
00152 if (!($this->_type instanceof spunQ_Type)) {
00153 $this->_getType();
00154 }
00155 if ($functionName === 'get_type') {
00156 return $this->_getType();
00157 }
00158 # Warning: the regexes here are quite similar. Do not forget to update
00159 # the others if you change one!
00160 if (preg_match('/^get([A-Z][A-Za-z0-9]*)_untranslated$/', $functionName, $matches)) {
00161 return $this->handleGetUntranslated(lcfirst($matches[1]), $arguments, $functionName);
00162 } elseif (preg_match('/^get([A-Z][A-Za-z0-9]*)$/', $functionName, $matches)) {
00163 return $this->handleGet(lcfirst($matches[1]), $arguments, $functionName);
00164 } elseif (preg_match('/^set([A-Z][A-Za-z0-9]*)$/', $functionName, $matches)) {
00165 return $this->handleSet(lcfirst($matches[1]), $arguments, $functionName);
00166 } elseif (preg_match('/^add([A-Z][A-Za-z0-9]*)$/', $functionName, $matches)) {
00167 return $this->handleAdd(lcfirst($matches[1]), $arguments, $functionName);
00168 } elseif (preg_match('/^remove([A-Z][A-Za-z0-9]*)$/', $functionName, $matches)) {
00169 return $this->handleRemove(lcfirst($matches[1]), $arguments, $functionName);
00170 }
00171 throw new spunQ_UndefinedMethodError(get_class($this), $functionName);
00172 }
00173
00178 public function __sleep() {
00179 if (is_object($this->_type)) {
00180 $this->_type = $this->_type->getName();
00181 }
00182 $vars = get_object_vars($this);
00183 return array_keys($vars);
00184 }
00185
00198 protected function handleGetUntranslated($memberName, $arguments, $functionName) {
00199 if (!$this->_getType()->getMember($memberName)->getOptions()->keyExists('localized')) {
00200 throw new spunQ_UndefinedMethodError(get_class($this), $functionName);
00201 }
00202 return $this->$memberName;
00203 }
00204
00217 protected function handleGet($memberName, $arguments, $functionName) {
00218 # optimzation: removing the overhead of calling a function.
00219 # the next lines could be replaced by $myType = $this->_getType();
00220 if ($this->_type instanceof spunQ_Type) {
00221 $myType = $this->_type;
00222 } else {
00223 $myType = $this->_getType();
00224 }
00225 if (SPUNQ_ENABLE_LOCALIZATIONS && $myType->getMember($memberName)->getOptions()->keyExists('localized')) {
00226 $this->initLocalizedMember($memberName);
00227 return $this->$memberName->get(lang()->getLocale());
00228 }
00229 if (count($arguments) === 0) {
00230 return $this->$memberName;
00231 } else {
00232 if (!($myType->getMember($memberName)->getType() instanceof spunQ_ArrayType)) {
00233 throw new spunQ_TypeMismatchException('array', spunQ_Type::getDescription($this->$memberName));
00234 }
00235 return $this->handleGetFromArray($memberName, $arguments, $functionName);
00236 }
00237 }
00238
00245 protected function initLocalizedMember($memberName) {
00246 if ($this->$memberName === NULL) {
00247 $this->$memberName = new spunQ_Map();
00248 }
00249 return NULL;
00250 }
00251
00264 protected function handleGetFromArray($memberName, $arguments, $functionName) {
00265 if (isset($arguments[1])) {
00266 throw new spunQ_BadArgumentCountError(get_class($this), $functionName, count($arguments));
00267 }
00268 $argument = $arguments[0];
00269 if (!is_array($argument)) {
00270 throw new spunQ_InvalidArgumentError('WrongType', $this->_getType(false) === NULL ? get_class($this) : $this->_getType()->getName(), $functionName, 'options', 'array', spunQ_Type::getDescription($argument));
00271 }
00272 if (!isset($argument['count'])) {
00273 $argument['count'] = NULL;
00274 }
00275 if (!isset($argument['offset'])) {
00276 $argument['offset'] = 0;
00277 }
00278 return array_slice($this->$memberName, $argument['offset'], $argument['count']);
00279 }
00280
00292 protected function handleSet($memberName, $arguments, $functionName) {
00293 if ($memberName === '_type') {
00294 throw new spunQ_InvalidArgumentError('CannotSetType', get_class($this));
00295 }
00296 $argumentCount = count($arguments);
00297 if ($argumentCount !== 1) {
00298 throw new spunQ_BadArgumentCountError(get_class($this), $functionName, $argumentCount);
00299 }
00300 $member = $this->_getType()->getMember($memberName);
00301 if ($member->getOptions()->keyExists('mappedBy')) {
00302 throw new spunQ_InvalidArgumentError('CannotSetMappedValue', $member->getCanonicalName());
00303 }
00304 $member->check($arguments[0]);
00305 if ($member->getOptions()->keyExists('localized')) {
00306 $this->initLocalizedMember($memberName);
00307 $this->$memberName->add(lang()->getLocale(), $arguments[0]);
00308 } else {
00309 $this->$memberName = $arguments[0];
00310 }
00311 return $this;
00312 }
00313
00327 protected function handleAdd($memberName, $arguments, $functionName) {
00328 $argc = count($arguments);
00329 if ($argc === 0) {
00330 throw new spunQ_BadArgumentCountError(get_class($this), $functionName, $argc);
00331 }
00332 $myType = $this->_getType();
00333 $member = $myType->getMember($memberName, false);
00334 if ($member !== NULL) {
00335 if (spunQ::inMode(spunQ::MODE_DEPLOYMENT)) {
00336 # No checks in deployment
00337 $this->$memberName = array_merge($this->$memberName, $arguments[0]);
00338 } else {
00339 if ($member->getType() instanceof spunQ_ArrayType) {
00340 if ($argc !== 1) {
00341 throw new spunQ_BadArgumentCountError(get_class($this), $functionName, $argc);
00342 }
00343 $member->check($arguments[0]);
00344 $this->$memberName = array_merge($this->$memberName, $arguments[0]);
00345 } else {
00346 throw new spunQ_InvalidArgumentError('CanOnlyAddMultipleToArrays', get_class($this), $functionName, 0);
00347 }
00348 }
00349 } else {
00350 $pluralizedMemberName = spunQ_Grammar::pluralize($memberName);
00351 $member = $myType->getMember($pluralizedMemberName, false);
00352 if ($member === NULL) {
00353 throw new spunQ_UndefinedMemberError($myType->getName(), $memberName);
00354 }
00355 $memberType = $member->getType();
00356 if ($memberType instanceof spunQ_ArrayType) {
00357 if ($argc !== 1) {
00358 throw new spunQ_BadArgumentCountError(get_class($this), $functionName, $argc);
00359 }
00360 $member->check($arguments);
00361 if ($this->$pluralizedMemberName === NULL) {
00362 $this->$pluralizedMemberName = array();
00363 } elseif (!is_array($this->$pluralizedMemberName)) {
00364 throw new spunQ_InvalidObjectException('MemberIsNotOfTypeArray', $myType->getName(), $pluralizedMemberName, spunQ_Type::getDescription($this->$pluralizedMemberName));
00365 }
00366 $this->$pluralizedMemberName = array_merge($this->$pluralizedMemberName, $arguments);
00367 } elseif ($memberType instanceof spunQ_MapType) {
00368 if ($argc !== 2) {
00369 throw new spunQ_BadArgumentCountError(get_class($this), $functionName, $argc);
00370 }
00371 $member->checkKey($arguments[0]);
00372 $member->checkValue($arguments[1]);
00373 if ($this->$pluralizedMemberName === NULL) {
00374 $this->$pluralizedMemberName = new spunQ_Map();
00375 } elseif (!$this->$pluralizedMemberName instanceof spunQ_Map) {
00376 throw new spunQ_InvalidObjectException('MemberIsNotOfTypeMap', $myType->getName(), $pluralizedMemberName, spunQ_Type::getDescription($this->$pluralizedMemberName));
00377 }
00378 $this->$pluralizedMemberName->add($arguments[0], $arguments[1]);
00379 } else {
00380 throw new spunQ_InvalidArgumentError('CanOnlyAddToCollections', get_class($this), $functionName, 0);
00381 }
00382 }
00383 return $this;
00384 }
00385
00399 protected function handleRemove($memberName, $arguments, $functionName) {
00400 if (!isset($arguments[0])) {
00401 throw new spunQ_BadArgumentCountError(get_class($this), $functionName, count($arguments));
00402 }
00403 if ($this->_getType()->memberExists($memberName)) {
00404 if (spunQ::inMode(spunQ::MODE_DEPLOYMENT)) {
00405 # No checks in deployment
00406 foreach (array_keys($this->$memberName, $arguments[0], true) as $key) {
00407 unset($this->{$memberName}[$key]);
00408 }
00409 } else {
00410 if ($this->_getType()->getMember($memberName)->getType() instanceof spunQ_ArrayType) {
00411 if (count($arguments) !== 1) {
00412 throw new spunQ_BadArgumentCountError(get_class($this), $functionName, count($arguments));
00413 }
00414 $this->_getType()->getMember($memberName)->check($arguments[0]);
00415 foreach (array_keys($this->$memberName, $arguments[0], true) as $key) {
00416 unset($this->{$memberName}[$key]);
00417 }
00418 } else {
00419 throw new spunQ_InvalidArgumentError('CanOnlyRemoveMultipleFromArrays', get_class($this), $functionName, 0);
00420 }
00421 }
00422 } elseif ($this->_getType()->memberExists(spunQ_Grammar::pluralize($memberName))) {
00423 $memberName = spunQ_Grammar::pluralize($memberName);
00424 $member = $this->_getType()->getMember($memberName);
00425 if ($member->getType() instanceof spunQ_ArrayType) {
00426 if (count($arguments) !== 1) {
00427 throw new spunQ_BadArgumentCountError(get_class($this), $functionName, count($arguments));
00428 }
00429 $member->check($arguments);
00430 foreach (array_keys($this->$memberName, $arguments[0], true) as $key) {
00431 unset($this->{$memberName}[$key]);
00432 }
00433 } elseif ($member->getType() instanceof spunQ_MapType) {
00434 if (count($arguments) !== 1) {
00435 throw new spunQ_BadArgumentCountError(get_class($this), $functionName, count($arguments));
00436 }
00437 $member->checkKey($arguments[0]);
00438 $this->$memberName->remove($arguments[0]);
00439 } else {
00440 throw new spunQ_InvalidArgumentError('CanOnlyRemoveFromCollections', get_class($this), $functionName, 0);
00441 }
00442 } else {
00443 throw new spunQ_UndefinedMemberError($this->_getType()->getName(), $memberName);
00444 }
00445 return $this;
00446 }
00447
00458 public function _verifyMembers($omittedMembers = array(), $throwException = true) {
00459 foreach ($this->_getType()->getOwnMembersRecursive() as $member) {
00460 if (in_array($member->getName(), $omittedMembers)) {
00461 continue;
00462 }
00463 if (!$member->check($this->_getMember($member->getName()), $throwException)) {
00464 return false;
00465 }
00466 }
00467 return true;
00468 }
00469
00478 public function _getMember($name) {
00479 $func = "get" . ucfirst($name);
00480 if (method_exists($this, $func)) {
00481 return $this->$func();
00482 }
00483 return $this->handleGet($name, array(), '_getMember');
00484 }
00485
00495 public function _setMember($name, $value) {
00496 $func = "set" . ucfirst($name);
00497 if (method_exists($this, $func)) {
00498 $this->$func($value);
00499 }
00500 return $this->handleSet($name, array($value), '_setMember');
00501 }
00502
00507 public function _getType($throwException = true) {
00508 if ($this->_type instanceof spunQ_Type) {
00509 return $this->_type;
00510 }
00511 if (is_string($this->_type)) {
00512 $this->_type = spunQ_UserType::getByName($this->_type, $throwException);
00513 } elseif ($this->_type === NULL) {
00514 $this->_type = spunQ_UserType::getByClass(get_class($this), $throwException);
00515 }
00516 return $this->_type;
00517 }
00518
00519 }
00520