DefaultPersistentObject.php
1 <?php
2 /**
3  * wCMF - wemove Content Management Framework
4  * Copyright (C) 2005-2020 wemove digital solutions GmbH
5  *
6  * Licensed under the terms of the MIT License.
7  *
8  * See the LICENSE file distributed with this work for
9  * additional information.
10  */
12 
25 
26 /**
27  * DefaultPersistentObject is the base class of all persistent objects.
28  * It mainly implements an unique identifier for each instance (ObjectId),
29  * tracking of the persistent state, methods for setting and getting values
30  * as well as callback methods for lifecycle events.
31  *
32  * @author ingo herwig <ingo@wemove.com>
33  */
34 class DefaultPersistentObject implements PersistentObject, \Serializable {
35 
36  private $oid = null; // object identifier
37  private $type = ''; // the object type
38  private $data = []; // associative array holding the data
39  private $properties = []; // associative array holding the object properties
40  private $valueProperties = []; // associative array holding the value properties
41  private $state = self::STATE_CLEAN; // the state of the PersistentObject
42  private $changedAttributes = []; // used to track changes, see setValue method
43  private $originalData = []; // data provided to the initialize method
44  private $mapper = null; // mapper instance
45 
46  private static $nullMapper = null;
47 
48  // TODO: add static cache for frequently requested entity type data
49 
50  /**
51  * Constructor.
52  * The object will be bound to the appropriate PersistenceMapper automatically,
53  * if the PersistenceFacade knows the type. The object id is needed to extract
54  * the type. If the id parameter of the object id is a dummy id, the object
55  * is supposed to be a newly created object (@see ObjectId::containsDummyIds()).
56  * @param $oid ObjectId instance (optional)
57  * @param $initialData Associative array with initial attribute data to override default data (optional)
58  */
59  public function __construct(ObjectId $oid=null, array $initialData=null) {
60  // set oid and state (avoid calling listeners)
61  if ($oid == null || !ObjectId::isValid($oid)) {
62  $oid = ObjectId::NULL_OID();
63  }
64  $this->type = $oid->getType();
65  $this->oid = $oid;
66  if ($oid->containsDummyIds()) {
67  $this->state = self::STATE_NEW;
68  }
69  else {
70  $this->state = self::STATE_CLEAN;
71  }
72 
73  // get default data
74  $data = [];
75  $attributeDescriptions = $this->getMapper()->getAttributes();
76  foreach($attributeDescriptions as $curAttributeDesc) {
77  $data[$curAttributeDesc->getName()] = $curAttributeDesc->getDefaultValue();
78  }
79 
80  // merge initial data
81  if ($initialData != null) {
82  $data = array_merge($data, $initialData);
83  }
84 
85  // set data
86  foreach ($data as $name => $value) {
87  $this->setValueInternal($name, $value);
88  }
89  $this->originalData = $data;
90 
91  // set primary keys
92  $this->setOIDInternal($oid, false);
93  }
94 
95  /**
96  * Initialize the PersistenceMapper instance
97  */
98  private function initializeMapper() {
99  // set the mapper
100  $persistenceFacade = ObjectFactory::getInstance('persistenceFacade');
101  if ($persistenceFacade->isKnownType($this->type)) {
102  $this->mapper = $persistenceFacade->getMapper($this->type);
103  }
104  else {
105  // initialize null mapper if not done already
106  if (self::$nullMapper == null) {
107  self::$nullMapper = new NullMapper();
108  }
109  $this->mapper = self::$nullMapper;
110  }
111  }
112 
113  /**
114  * @see PersistentObject::getType()
115  */
116  public function getType() {
117  return $this->type;
118  }
119 
120  /**
121  * @see PersistentObject::getMapper()
122  */
123  public function getMapper() {
124  if ($this->mapper == null) {
125  $this->initializeMapper();
126  }
127  return $this->mapper;
128  }
129 
130  /**
131  * @see PersistentObject::getOID()
132  */
133  public function getOID() {
134  return $this->oid;
135  }
136 
137  /**
138  * @see PersistentObject::setOID()
139  */
140  public function setOID(ObjectId $oid) {
141  $this->setOIDInternal($oid, true);
142  }
143 
144  /**
145  * Set the object id of the PersistentObject.
146  * @param $oid The PersistentObject's oid.
147  * @param $triggerListeners Boolean, whether value CahngeListeners should be
148  * notified or not
149  */
150  protected function setOIDInternal(ObjectId $oid, $triggerListeners) {
151  $this->type = $oid->getType();
152  $this->oid = $oid;
153  // update the primary key attributes
154  $ids = $oid->getId();
155  $pkNames = $this->getMapper()->getPkNames();
156  for ($i=0, $count=sizeof($pkNames); $i<$count; $i++) {
157  if ($triggerListeners) {
158  $this->setValue($pkNames[$i], $ids[$i], true);
159  }
160  else {
161  $this->setValueInternal($pkNames[$i], $ids[$i]);
162  }
163  }
164  }
165 
166  /**
167  * @see PersistentObject::getState()
168  */
169  public function getState() {
170  return $this->state;
171  }
172 
173  /**
174  * @see PersistentObject::setState()
175  */
176  public function setState($state) {
177  $oldState = $this->state;
178  switch ($oldState) {
179  // new object must stay new when it's modified
180  case self::STATE_NEW:
181  switch ($state) {
182  case self::STATE_DIRTY:
183  $this->state = self::STATE_NEW;
184  break;
185 
186  default:
187  $this->state = $state;
188  }
189  break;
190 
191  // deleted object must stay deleted in every case
192  case self::STATE_DELETED:
193  $this->state = self::STATE_DELETED;
194  break;
195 
196  default:
197  $this->state = $state;
198  }
199  if ($oldState != $this->state) {
200  ObjectFactory::getInstance('eventManager')->dispatch(StateChangeEvent::NAME,
201  new StateChangeEvent($this, $oldState, $this->state));
202  }
203  }
204 
205  /**
206  * @see PersistentObject::delete()
207  */
208  public function delete() {
209  // delete the object (will be done in the transaction)
210  $this->setState(self::STATE_DELETED);
211  }
212 
213  /**
214  * @see PersistentObject::__clone()
215  */
216  public function __clone() {
217  $class = get_class($this);
218  $copy = new $class;
219  $copy->oid = $this->oid;
220  $copy->type = $this->type;
221  $copy->data = $this->data;
222  $copy->properties = $this->properties;
223  $copy->valueProperties = $this->valueProperties;
224  $copy->state = $this->state;
225  $copy->changedAttributes = $this->changedAttributes;
226  $copy->originalData = $this->originalData;
227 
228  return $copy;
229  }
230 
231  /**
232  * @see PersistentObject::copyValues()
233  */
234  public function copyValues(PersistentObject $object, $copyPkValues=true) {
235  $pkNames = $this->getMapper()->getPkNames();
236  $iter = new NodeValueIterator($this, false);
237  foreach($iter as $valueName => $value) {
238  if (strlen($value) > 0 && ($copyPkValues || !in_array($valueName, $pkNames))) {
239  $object->setValue($valueName, $value, true);
240  }
241  }
242  }
243 
244  /**
245  * @see PersistentObject::mergeValues()
246  */
247  public function mergeValues(PersistentObject $object) {
248  $iter = new NodeValueIterator($object, false);
249  foreach($iter as $valueName => $value) {
250  if (!$this->hasValue($valueName) && strlen($value) > 0) {
251  $this->setValueInternal($valueName, $value);
252  }
253  }
254  }
255 
256  /**
257  * @see PersistentObject::clearValues()
258  */
259  public function clearValues() {
260  $pkNames = $this->getMapper()->getPkNames();
261  $iter = new NodeValueIterator($this, false);
262  for($iter->rewind(); $iter->valid(); $iter->next()) {
263  $valueName = $iter->key();
264  if (!in_array($valueName, $pkNames)) {
265  $curNode = $iter->currentNode();
266  $curNode->setValue($valueName, null, true);
267  }
268  }
269  }
270 
271  /**
272  * @see PersistentObject::reset()
273  */
274  public function reset() {
275  $this->data = $this->originalData;
276  $this->changedAttributes = [];
277  }
278 
279  /**
280  * Recalculate the object id
281  */
282  private function updateOID() {
283  $pkValues = [];
284  // collect the values of the primary keys and compose the oid from them
285  $pkNames = $this->getMapper()->getPkNames();
286  foreach ($pkNames as $pkName) {
287  $pkValues[] = self::getValue($pkName);
288  }
289  $this->oid = new ObjectId($this->getType(), $pkValues);
290  }
291 
292  /**
293  * @see PersistentObject::afterCreate()
294  * @note The default implementation does nothing
295  */
296  public function afterCreate() {}
297 
298  /**
299  * @see PersistentObject::beforeInsert()
300  * @note The default implementation does nothing
301  */
302  public function beforeInsert() {}
303 
304  /**
305  * @see PersistentObject::afterInsert()
306  * @note The default implementation does nothing
307  */
308  public function afterInsert() {}
309 
310  /**
311  * @see PersistentObject::afterLoad()
312  * @note The default implementation does nothing
313  */
314  public function afterLoad() {}
315 
316  /**
317  * @see PersistentObject::beforeUpdate()
318  * @note The default implementation does nothing
319  */
320  public function beforeUpdate() {}
321 
322  /**
323  * @see PersistentObject::afterUpdate()
324  * @note The default implementation does nothing
325  */
326  public function afterUpdate() {}
327 
328  /**
329  * @see PersistentObject::beforeDelete()
330  * @note The default implementation does nothing
331  */
332  public function beforeDelete() {}
333 
334  /**
335  * @see PersistentObject::afterDelete()
336  * @note The default implementation does nothing
337  */
338  public function afterDelete() {}
339 
340  /**
341  * @see PersistentObject::getValue()
342  */
343  public function getValue($name) {
344  if (isset($this->data[$name])) {
345  return $this->data[$name];
346  }
347  return null;
348  }
349 
350  /**
351  * @see PersistentObject::setValue()
352  */
353  public function setValue($name, $value, $forceSet=false, $trackChange=true) {
354  if (!$forceSet) {
355  $this->validateValue($name, $value);
356  }
357  $oldValue = self::getValue($name);
358  if ($forceSet || $oldValue !== $value) {
359  $this->setValueInternal($name, $value);
360  if (in_array($name, $this->getMapper()->getPKNames())) {
361  $this->updateOID();
362  }
363  if ($trackChange) {
364  self::setState(self::STATE_DIRTY);
365  $this->changedAttributes[$name] = true;
366  ObjectFactory::getInstance('eventManager')->dispatch(ValueChangeEvent::NAME,
367  new ValueChangeEvent($this, $name, $oldValue, $value));
368  }
369  return true;
370  }
371  return false;
372  }
373 
374  /**
375  * Internal (fast) version to set a value without any validation, state change,
376  * listener notification etc.
377  * @param $name The name of the value
378  * @param $value The value
379  */
380  protected function setValueInternal($name, $value) {
381  $this->data[$name] = $value;
382  }
383 
384  /**
385  * @see PersistentObject::hasValue()
386  */
387  public function hasValue($name) {
388  return array_key_exists($name, $this->data);
389  }
390 
391  /**
392  * @see PersistentObject::removeValue()
393  */
394  public function removeValue($name) {
395  if ($this->hasValue($name)) {
396  unset($this->data[$name]);
397  }
398  }
399 
400  /**
401  * @see PersistentObject::validateValues()
402  */
403  public function validateValues() {
404  $errorMessage = '';
405  $iter = new NodeValueIterator($this, false);
406  for($iter->rewind(); $iter->valid(); $iter->next()) {
407  $curNode = $iter->currentNode();
408  try {
409  $curNode->validateValue($iter->key(), $iter->current());
410  }
411  catch (ValidationException $ex) {
412  $errorMessage .= $ex->getMessage()."\n";
413  }
414  }
415  if (strlen($errorMessage) > 0) {
416  throw new ValidationException(null, null, $errorMessage);
417  }
418  }
419 
420  /**
421  * @see PersistentObject::validateValue()
422  */
423  public function validateValue($name, $value) {
424  $this->validateValueAgainstValidateType($name, $value);
425  }
426 
427  /**
428  * Check a value's value against the validation type set on it. This method uses the
429  * validateType property of the attribute definition.
430  * Throws a ValidationException if the value is not valid.
431  * @param $name The name of the item to set.
432  * @param $value The value of the item.
433  */
434  protected function validateValueAgainstValidateType($name, $value) {
435  // don't validate referenced values
436  $mapper = $this->getMapper();
437  if ($mapper->hasAttribute($name) && $mapper->getAttribute($name) instanceof ReferenceDescription) {
438  return;
439  }
440  $validateType = $this->getValueProperty($name, 'validate_type');
441  if (($validateType == '' || Validator::validate($value, $validateType,
442  ['entity' => $this, 'value' => $name]))) {
443  return;
444  }
445  // construct the error message
446  $messageInstance = ObjectFactory::getInstance('message');
447  $validateDescription = $this->getValueProperty($name, 'validate_description');
448  if (strlen($validateDescription) > 0) {
449  // use configured message if existing
450  $errorMessage = $messageInstance->getText($validateDescription);
451  }
452  else {
453  // default text
454  $errorMessage = $messageInstance->getText("The value of '%0%' (%1%) is invalid.", [$messageInstance->getText($name), $value]);
455  }
456  throw new ValidationException($name, $value, $errorMessage);
457  }
458 
459  /**
460  * @see PersistentObject::getChangedValues()
461  */
462  public function getChangedValues() {
463  return array_keys($this->changedAttributes);
464  }
465 
466  /**
467  * @see PersistentObject::getOriginalValue()
468  */
469  public function getOriginalValue($name) {
470  if (isset($this->originalData[$name])) {
471  return $this->originalData[$name];
472  }
473  return null;
474  }
475 
476  /**
477  * @see PersistentObject::getIndispensableObjects()
478  */
479  public function getIndispensableObjects() {
480  return [];
481  }
482 
483  /**
484  * @see PersistentObject::getProperty()
485  */
486  public function getProperty($name) {
487  if (isset($this->properties[$name])) {
488  return $this->properties[$name];
489  }
490  else {
491  // get the default property value from the mapper
492  $properties = $this->getMapper()->getProperties();
493  if (isset($properties[$name])) {
494  return $properties[$name];
495  }
496  }
497  return null;
498  }
499 
500  /**
501  * @see PersistentObject::setProperty()
502  */
503  public function setProperty($name, $value) {
504  $oldValue = $this->getProperty($name);
505  $this->properties[$name] = $value;
506  ObjectFactory::getInstance('eventManager')->dispatch(PropertyChangeEvent::NAME,
507  new PropertyChangeEvent($this, $name, $oldValue, $value));
508  }
509 
510  /**
511  * @see PersistentObject::getPropertyNames()
512  */
513  public function getPropertyNames() {
514  $result = array_keys($this->getMapper()->getProperties());
515  return array_merge($result, array_keys($this->properties));
516  }
517 
518  /**
519  * @see PersistentObject::getValueProperty()
520  */
521  public function getValueProperty($name, $property) {
522  if (!isset($this->valueProperties[$name]) || !isset($this->valueProperties[$name][$property])) {
523  // get the default property value from the mapper
524  $mapper = $this->getMapper();
525  if ($mapper->hasAttribute($name)) {
526  $attr = $mapper->getAttribute($name);
527  if ($attr) {
528  $getter = 'get'.ucfirst(StringUtil::underScoreToCamelCase($property, true));
529  return $attr->$getter();
530  }
531  }
532  }
533  else {
534  // the default property value is overidden
535  return $this->valueProperties[$name][$property];
536  }
537  return false;
538  }
539 
540  /**
541  * @see PersistentObject::setValueProperty()
542  */
543  public function setValueProperty($name, $property, $value) {
544  if (!isset($this->valueProperties[$name])) {
545  $this->valueProperties[$name] = [];
546  }
547  $this->valueProperties[$name][$property] = $value;
548  }
549 
550  /**
551  * @see PersistentObject::getValuePropertyNames()
552  */
553  public function getValuePropertyNames($name) {
554  $result = [];
555  $mapper = $this->getMapper();
556  if ($mapper->hasAttribute($name)) {
557  $attr = $mapper->getAttribute($name);
558  if ($attr) {
559  $result = $attr->getPropertyNames();
560  }
561  }
562  return array_merge($result, array_keys($this->valueProperties[$name]));
563  }
564 
565  /**
566  * @see PersistentObject::getValueNames()
567  */
568  public function getValueNames($excludeTransient=false) {
569  if ($excludeTransient) {
570  // get only value names from mapper
571  $names = [];
572  $attributes = $this->getMapper()->getAttributes();
573  foreach ($attributes as $attribute) {
574  $names[] = $attribute->getName();
575  }
576  }
577  else {
578  $names = array_keys($this->data);
579  }
580  return $names;
581  }
582 
583  /**
584  * @see PersistentObject::getDisplayValue()
585  * @note Subclasses will override this for special application requirements
586  */
587  public function getDisplayValue() {
588  return $this->getOID()->__toString();
589  }
590 
591  /**
592  * @see PersistentObject::dump()
593  */
594  public function dump() {
595  $str = $this->getOID()->__toString()."\n";
596  foreach ($this->getValueNames() as $valueName) {
597  $value = self::getValue($valueName);
598  $valueStr = null;
599  if (is_array($value)) {
600  $valueStr = self::dumpArray($value)."\n";
601  }
602  else {
603  if (is_object($value)) {
604  $valueStr = $value->__toString()."\n";
605  }
606  else {
607  $valueStr = StringUtil::getDump($value);
608  }
609  }
610  $str .= " ".$valueName.": ".$valueStr;
611  }
612  return $str;
613  }
614 
615  /**
616  * Get a string representation of an array of values.
617  * @param $array The array to dump
618  * @return String
619  */
620  private static function dumpArray(array $array) {
621  $str = "[";
622  foreach ($array as $value) {
623  if (is_object($value)) {
624  $str .= $value->__toString().", ";
625  }
626  else {
627  $str .= $value.", ";
628  }
629  }
630  $str = preg_replace("/, $/", "", $str)."]";
631  return $str;
632  }
633 
634  /**
635  * Get a string representation of the PersistentObject.
636  * @return String
637  */
638  public function __toString() {
639  return self::getDisplayValue();
640  }
641 
642  public function serialize() {
643  $this->mapper = null;
644  return serialize(get_object_vars($this));
645  }
646 
647  public function unserialize($serialized) {
648  $values = unserialize($serialized);
649  foreach ($values as $key => $value) {
650  $this->$key = $value;
651  }
652  }
653 }
654 ?>
static NULL_OID()
Get the NULL instance.
Definition: ObjectId.php:78
ValidationException signals an exception in validation.
NullMapper acts as there is no mapper.
Definition: NullMapper.php:27
static getDump($variable, $strlen=100, $width=25, $depth=10, $i=0, &$objects=[])
Get the dump of a variable as string.
Definition: StringUtil.php:29
DefaultPersistentObject is the base class of all persistent objects.
PropertyChangeEvent signals a change of a property of a PersistentObject instance.
__construct(ObjectId $oid=null, array $initialData=null)
Constructor.
NodeValueIterator is used to iterate over all persistent values of a Node (not including relations).
copyValues(PersistentObject $object, $copyPkValues=true)
Validator is is the single entry point for validation.
Definition: Validator.php:23
__toString()
Get a string representation of the PersistentObject.
static underScoreToCamelCase($string, $firstLowerCase=false)
Convert a string in underscore notation to camel case notation.
Definition: StringUtil.php:365
StateChangeEvent signals a change of the state of a PersistentObject instance.
setValue($name, $value, $forceSet=false, $trackChange=true)
StringUtil provides support for string manipulation.
Definition: StringUtil.php:18
setValue($name, $value, $forceSet=false, $trackChange=true)
Set the value of an attribute if it exists.
ObjectId is the unique identifier of an object.
Definition: ObjectId.php:28
ValueChangeEvent signals a change of a value of a PersistentObject instance.
Instances of ReferenceDescription describe reference attributes of PersistentObjects.
getType()
Get the type (including namespace)
Definition: ObjectId.php:97
setValueInternal($name, $value)
Internal (fast) version to set a value without any validation, state change, listener notification et...
static getInstance($name, $dynamicConfiguration=[])
static validate($value, $validateDesc, $context=null)
Validate the given value against the given validateType description.
Definition: Validator.php:35
PersistentObject defines the interface of all persistent objects.
static isValid($oid)
Check if a serialized ObjectId has a valid syntax, the type is known and if the number of primary key...
Definition: ObjectId.php:123
setOIDInternal(ObjectId $oid, $triggerListeners)
Set the object id of the PersistentObject.
validateValueAgainstValidateType($name, $value)
Check a value's value against the validation type set on it.
ObjectFactory implements the service locator pattern by wrapping a Factory instance and providing sta...