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 
22 use wcmf\lib\util\StringUtil;
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 
226  return $copy;
227  }
228 
229  /**
230  * @see PersistentObject::copyValues()
231  */
232  public function copyValues(PersistentObject $object, $copyPkValues=true) {
233  $pkNames = $this->getMapper()->getPkNames();
234  $iter = new NodeValueIterator($this, false);
235  foreach($iter as $valueName => $value) {
236  if (strlen($value) > 0 && ($copyPkValues || !in_array($valueName, $pkNames))) {
237  $object->setValue($valueName, $value, true);
238  }
239  }
240  }
241 
242  /**
243  * @see PersistentObject::mergeValues()
244  */
245  public function mergeValues(PersistentObject $object) {
246  $iter = new NodeValueIterator($object, false);
247  foreach($iter as $valueName => $value) {
248  if (!$this->hasValue($valueName) && strlen($value) > 0) {
249  $this->setValueInternal($valueName, $value);
250  }
251  }
252  }
253 
254  /**
255  * @see PersistentObject::clearValues()
256  */
257  public function clearValues() {
258  $pkNames = $this->getMapper()->getPkNames();
259  $iter = new NodeValueIterator($this, false);
260  for($iter->rewind(); $iter->valid(); $iter->next()) {
261  $valueName = $iter->key();
262  if (!in_array($valueName, $pkNames)) {
263  $curNode = $iter->currentNode();
264  $curNode->setValue($valueName, null, true);
265  }
266  }
267  }
268 
269  /**
270  * Recalculate the object id
271  */
272  private function updateOID() {
273  $pkValues = [];
274  // collect the values of the primary keys and compose the oid from them
275  $pkNames = $this->getMapper()->getPkNames();
276  foreach ($pkNames as $pkName) {
277  $pkValues[] = self::getValue($pkName);
278  }
279  $this->oid = new ObjectId($this->getType(), $pkValues);
280  }
281 
282  /**
283  * @see PersistentObject::afterCreate()
284  * @note The default implementation does nothing
285  */
286  public function afterCreate() {}
287 
288  /**
289  * @see PersistentObject::beforeInsert()
290  * @note The default implementation does nothing
291  */
292  public function beforeInsert() {}
293 
294  /**
295  * @see PersistentObject::afterInsert()
296  * @note The default implementation does nothing
297  */
298  public function afterInsert() {}
299 
300  /**
301  * @see PersistentObject::afterLoad()
302  * @note The default implementation does nothing
303  */
304  public function afterLoad() {}
305 
306  /**
307  * @see PersistentObject::beforeUpdate()
308  * @note The default implementation does nothing
309  */
310  public function beforeUpdate() {}
311 
312  /**
313  * @see PersistentObject::afterUpdate()
314  * @note The default implementation does nothing
315  */
316  public function afterUpdate() {}
317 
318  /**
319  * @see PersistentObject::beforeDelete()
320  * @note The default implementation does nothing
321  */
322  public function beforeDelete() {}
323 
324  /**
325  * @see PersistentObject::afterDelete()
326  * @note The default implementation does nothing
327  */
328  public function afterDelete() {}
329 
330  /**
331  * @see PersistentObject::getValue()
332  */
333  public function getValue($name) {
334  if (isset($this->data[$name])) {
335  return $this->data[$name];
336  }
337  return null;
338  }
339 
340  /**
341  * @see PersistentObject::setValue()
342  */
343  public function setValue($name, $value, $forceSet=false, $trackChange=true) {
344  if (!$forceSet) {
345  $this->validateValue($name, $value);
346  }
347  $oldValue = self::getValue($name);
348  if ($forceSet || $oldValue !== $value) {
349  $this->setValueInternal($name, $value);
350  if (in_array($name, $this->getMapper()->getPKNames())) {
351  $this->updateOID();
352  }
353  if ($trackChange) {
354  self::setState(self::STATE_DIRTY);
355  $this->changedAttributes[$name] = true;
356  ObjectFactory::getInstance('eventManager')->dispatch(ValueChangeEvent::NAME,
357  new ValueChangeEvent($this, $name, $oldValue, $value));
358  }
359  return true;
360  }
361  return false;
362  }
363 
364  /**
365  * Internal (fast) version to set a value without any validation, state change,
366  * listener notification etc.
367  * @param $name The name of the value
368  * @param $value The value
369  */
370  protected function setValueInternal($name, $value) {
371  $this->data[$name] = $value;
372  }
373 
374  /**
375  * @see PersistentObject::hasValue()
376  */
377  public function hasValue($name) {
378  return array_key_exists($name, $this->data);
379  }
380 
381  /**
382  * @see PersistentObject::removeValue()
383  */
384  public function removeValue($name) {
385  if ($this->hasValue($name)) {
386  unset($this->data[$name]);
387  }
388  }
389 
390  /**
391  * @see PersistentObject::validateValues()
392  */
393  public function validateValues() {
394  $errorMessage = '';
395  $iter = new NodeValueIterator($this, false);
396  for($iter->rewind(); $iter->valid(); $iter->next()) {
397  $curNode = $iter->currentNode();
398  try {
399  $curNode->validateValue($iter->key(), $iter->current());
400  }
401  catch (ValidationException $ex) {
402  $errorMessage .= $ex->getMessage()."\n";
403  }
404  }
405  if (strlen($errorMessage) > 0) {
406  throw new ValidationException(null, null, $errorMessage);
407  }
408  }
409 
410  /**
411  * @see PersistentObject::validateValue()
412  */
413  public function validateValue($name, $value) {
414  $this->validateValueAgainstValidateType($name, $value);
415  }
416 
417  /**
418  * Check a value's value against the validation type set on it. This method uses the
419  * validateType property of the attribute definition.
420  * Throws a ValidationException if the value is not valid.
421  * @param $name The name of the item to set.
422  * @param $value The value of the item.
423  */
424  protected function validateValueAgainstValidateType($name, $value) {
425  // don't validate referenced values
426  $mapper = $this->getMapper();
427  if ($mapper->hasAttribute($name) && $mapper->getAttribute($name) instanceof ReferenceDescription) {
428  return;
429  }
430  $validateType = $this->getValueProperty($name, 'validate_type');
431  if (($validateType == '' || Validator::validate($value, $validateType,
432  ['entity' => $this, 'value' => $name]))) {
433  return;
434  }
435  // construct the error message
436  $messageInstance = ObjectFactory::getInstance('message');
437  $validateDescription = $this->getValueProperty($name, 'validate_description');
438  if (strlen($validateDescription) > 0) {
439  // use configured message if existing
440  $errorMessage = $messageInstance->getText($validateDescription);
441  }
442  else {
443  // default text
444  $errorMessage = $messageInstance->getText("The value of '%0%' (%1%) is invalid.", [$messageInstance->getText($name), $value]);
445  }
446  throw new ValidationException($name, $value, $errorMessage);
447  }
448 
449  /**
450  * @see PersistentObject::getChangedValues()
451  */
452  public function getChangedValues() {
453  return array_keys($this->changedAttributes);
454  }
455 
456  /**
457  * @see PersistentObject::getOriginalValue()
458  */
459  public function getOriginalValue($name) {
460  if (isset($this->originalData[$name])) {
461  return $this->originalData[$name];
462  }
463  return null;
464  }
465 
466  /**
467  * @see PersistentObject::getIndispensableObjects()
468  */
469  public function getIndispensableObjects() {
470  return [];
471  }
472 
473  /**
474  * @see PersistentObject::getProperty()
475  */
476  public function getProperty($name) {
477  if (isset($this->properties[$name])) {
478  return $this->properties[$name];
479  }
480  else {
481  // get the default property value from the mapper
482  $properties = $this->getMapper()->getProperties();
483  if (isset($properties[$name])) {
484  return $properties[$name];
485  }
486  }
487  return null;
488  }
489 
490  /**
491  * @see PersistentObject::setProperty()
492  */
493  public function setProperty($name, $value) {
494  $oldValue = $this->getProperty($name);
495  $this->properties[$name] = $value;
496  ObjectFactory::getInstance('eventManager')->dispatch(PropertyChangeEvent::NAME,
497  new PropertyChangeEvent($this, $name, $oldValue, $value));
498  }
499 
500  /**
501  * @see PersistentObject::getPropertyNames()
502  */
503  public function getPropertyNames() {
504  $result = array_keys($this->getMapper()->getProperties());
505  return array_merge($result, array_keys($this->properties));
506  }
507 
508  /**
509  * @see PersistentObject::getValueProperty()
510  */
511  public function getValueProperty($name, $property) {
512  if (!isset($this->valueProperties[$name]) || !isset($this->valueProperties[$name][$property])) {
513  // get the default property value from the mapper
514  $mapper = $this->getMapper();
515  if ($mapper->hasAttribute($name)) {
516  $attr = $mapper->getAttribute($name);
517  if ($attr) {
518  $getter = 'get'.ucfirst(StringUtil::underScoreToCamelCase($property, true));
519  return $attr->$getter();
520  }
521  }
522  }
523  else {
524  // the default property value is overidden
525  return $this->valueProperties[$name][$property];
526  }
527  return false;
528  }
529 
530  /**
531  * @see PersistentObject::setValueProperty()
532  */
533  public function setValueProperty($name, $property, $value) {
534  if (!isset($this->valueProperties[$name])) {
535  $this->valueProperties[$name] = [];
536  }
537  $this->valueProperties[$name][$property] = $value;
538  }
539 
540  /**
541  * @see PersistentObject::getValuePropertyNames()
542  */
543  public function getValuePropertyNames($name) {
544  $result = [];
545  $mapper = $this->getMapper();
546  if ($mapper->hasAttribute($name)) {
547  $attr = $mapper->getAttribute($name);
548  if ($attr) {
549  $result = $attr->getPropertyNames();
550  }
551  }
552  return array_merge($result, array_keys($this->valueProperties[$name]));
553  }
554 
555  /**
556  * @see PersistentObject::getValueNames()
557  */
558  public function getValueNames($excludeTransient=false) {
559  if ($excludeTransient) {
560  // get only value names from mapper
561  $names = [];
562  $attributes = $this->getMapper()->getAttributes();
563  foreach ($attributes as $attribute) {
564  $names[] = $attribute->getName();
565  }
566  }
567  else {
568  $names = array_keys($this->data);
569  }
570  return $names;
571  }
572 
573  /**
574  * @see PersistentObject::getDisplayValue()
575  * @note Subclasses will override this for special application requirements
576  */
577  public function getDisplayValue() {
578  return $this->getOID()->__toString();
579  }
580 
581  /**
582  * @see PersistentObject::dump()
583  */
584  public function dump() {
585  $str = $this->getOID()->__toString()."\n";
586  foreach ($this->getValueNames() as $valueName) {
587  $value = self::getValue($valueName);
588  $valueStr = null;
589  if (is_array($value)) {
590  $valueStr = self::dumpArray($value)."\n";
591  }
592  else {
593  if (is_object($value)) {
594  $valueStr = $value->__toString()."\n";
595  }
596  else {
597  $valueStr = StringUtil::getDump($value);
598  }
599  }
600  $str .= " ".$valueName.": ".$valueStr;
601  }
602  return $str;
603  }
604 
605  /**
606  * Get a string representation of an array of values.
607  * @param $array The array to dump
608  * @return String
609  */
610  private static function dumpArray(array $array) {
611  $str = "[";
612  foreach ($array as $value) {
613  if (is_object($value)) {
614  $str .= $value->__toString().", ";
615  }
616  else {
617  $str .= $value.", ";
618  }
619  }
620  $str = preg_replace("/, $/", "", $str)."]";
621  return $str;
622  }
623 
624  /**
625  * Get a string representation of the PersistentObject.
626  * @return String
627  */
628  public function __toString() {
629  return self::getDisplayValue();
630  }
631 
632  public function serialize() {
633  $this->mapper = null;
634  return serialize(get_object_vars($this));
635  }
636 
637  public function unserialize($serialized) {
638  $values = unserialize($serialized);
639  foreach ($values as $key => $value) {
640  $this->$key = $value;
641  }
642  }
643 }
644 ?>
static NULL_OID()
Get the NULL instance.
Definition: ObjectId.php:77
ValidationException signals an exception in validation.
NullMapper acts as there is no mapper.
Definition: NullMapper.php:27
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.
StateChangeEvent signals a change of the state of a PersistentObject instance.
setValue($name, $value, $forceSet=false, $trackChange=true)
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:27
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:96
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:122
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...