DefaultPersistentObject.php
1 <?php
2 /**
3  * wCMF - wemove Content Management Framework
4  * Copyright (C) 2005-2017 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 
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  $errorMessage = ObjectFactory::getInstance('message')->getText("The value of '%0%' (%1%) is invalid.", [$name, $value]);
437 
438  // use configured message if existing
439  $validateDescription = $this->getValueProperty($name, 'validate_description');
440  if (strlen($validateDescription) > 0) {
441  $errorMessage .= " ".$validateDescription;
442  }
443  throw new ValidationException($name, $value, $errorMessage);
444  }
445 
446  /**
447  * @see PersistentObject::getChangedValues()
448  */
449  public function getChangedValues() {
450  return array_keys($this->changedAttributes);
451  }
452 
453  /**
454  * @see PersistentObject::getOriginalValues()
455  */
456  public function getOriginalValues() {
457  return $this->originalData;
458  }
459 
460  /**
461  * @see PersistentObject::getIndispensableObjects()
462  */
463  public function getIndispensableObjects() {
464  return [];
465  }
466 
467  /**
468  * @see PersistentObject::getProperty()
469  */
470  public function getProperty($name) {
471  if (isset($this->properties[$name])) {
472  return $this->properties[$name];
473  }
474  else {
475  // get the default property value from the mapper
476  $properties = $this->getMapper()->getProperties();
477  if (isset($properties[$name])) {
478  return $properties[$name];
479  }
480  }
481  return null;
482  }
483 
484  /**
485  * @see PersistentObject::setProperty()
486  */
487  public function setProperty($name, $value) {
488  $oldValue = $this->getProperty($name);
489  $this->properties[$name] = $value;
490  ObjectFactory::getInstance('eventManager')->dispatch(PropertyChangeEvent::NAME,
491  new PropertyChangeEvent($this, $name, $oldValue, $value));
492  }
493 
494  /**
495  * @see PersistentObject::getPropertyNames()
496  */
497  public function getPropertyNames() {
498  $result = array_keys($this->getMapper()->getProperties());
499  return array_merge($result, array_keys($this->properties));
500  }
501 
502  /**
503  * @see PersistentObject::getValueProperty()
504  */
505  public function getValueProperty($name, $property) {
506  if (!isset($this->valueProperties[$name]) || !isset($this->valueProperties[$name][$property])) {
507  // get the default property value from the mapper
508  $mapper = $this->getMapper();
509  if ($mapper->hasAttribute($name)) {
510  $attr = $mapper->getAttribute($name);
511  if ($attr) {
512  $getter = 'get'.ucfirst(StringUtil::underScoreToCamelCase($property, true));
513  return $attr->$getter();
514  }
515  }
516  }
517  else {
518  // the default property value is overidden
519  return $this->valueProperties[$name][$property];
520  }
521  return false;
522  }
523 
524  /**
525  * @see PersistentObject::setValueProperty()
526  */
527  public function setValueProperty($name, $property, $value) {
528  if (!isset($this->valueProperties[$name])) {
529  $this->valueProperties[$name] = [];
530  }
531  $this->valueProperties[$name][$property] = $value;
532  }
533 
534  /**
535  * @see PersistentObject::getValuePropertyNames()
536  */
537  public function getValuePropertyNames($name) {
538  $result = [];
539  $mapper = $this->getMapper();
540  if ($mapper->hasAttribute($name)) {
541  $attr = $mapper->getAttribute($name);
542  if ($attr) {
543  $result = $attr->getPropertyNames();
544  }
545  }
546  return array_merge($result, array_keys($this->valueProperties[$name]));
547  }
548 
549  /**
550  * @see PersistentObject::getValueNames()
551  */
552  public function getValueNames($excludeTransient=false) {
553  if ($excludeTransient) {
554  // get only value names from mapper
555  $names = [];
556  $attributes = $this->getMapper()->getAttributes();
557  foreach ($attributes as $attribute) {
558  $names[] = $attribute->getName();
559  }
560  }
561  else {
562  $names = array_keys($this->data);
563  }
564  return $names;
565  }
566 
567  /**
568  * @see PersistentObject::getDisplayValue()
569  * @note Subclasses will override this for special application requirements
570  */
571  public function getDisplayValue() {
572  return $this->getOID()->__toString();
573  }
574 
575  /**
576  * @see PersistentObject::dump()
577  */
578  public function dump() {
579  $str = $this->getOID()->__toString()."\n";
580  foreach ($this->getValueNames() as $valueName) {
581  $value = self::getValue($valueName);
582  $valueStr = null;
583  if (is_array($value)) {
584  $valueStr = self::dumpArray($value)."\n";
585  }
586  else {
587  if (is_object($value)) {
588  $valueStr = $value->__toString()."\n";
589  }
590  else {
591  $valueStr = StringUtil::getDump($value);
592  }
593  }
594  $str .= " ".$valueName.": ".$valueStr;
595  }
596  return $str;
597  }
598 
599  /**
600  * Get a string representation of an array of values.
601  * @param $array The array to dump
602  * @return String
603  */
604  private static function dumpArray(array $array) {
605  $str = "[";
606  foreach ($array as $value) {
607  if (is_object($value)) {
608  $str .= $value->__toString().", ";
609  }
610  else {
611  $str .= $value.", ";
612  }
613  }
614  $str = preg_replace("/, $/", "", $str)."]";
615  return $str;
616  }
617 
618  /**
619  * Get a string representation of the PersistentObject.
620  * @return String
621  */
622  public function __toString() {
623  return self::getDisplayValue();
624  }
625 
626  public function serialize() {
627  $this->mapper = null;
628  return serialize(get_object_vars($this));
629  }
630 
631  public function unserialize($serialized) {
632  $values = unserialize($serialized);
633  foreach ($values as $key => $value) {
634  $this->$key = $value;
635  }
636  }
637 }
638 ?>
setValueInternal($name, $value)
Internal (fast) version to set a value without any validation, state change, listener notification et...
__construct(ObjectId $oid=null, array $initialData=null)
Constructor.
copyValues(PersistentObject $object, $copyPkValues=true)
static NULL_OID()
Get the NULL instance.
Definition: ObjectId.php:77
setValue($name, $value, $forceSet=false, $trackChange=true)
NodeValueIterator is used to iterate over all persistent values of a Node (not including relations)...
setOIDInternal(ObjectId $oid, $triggerListeners)
Set the object id of the PersistentObject.
ObjectId is the unique identifier of an object.
Definition: ObjectId.php:27
StateChangeEvent signals a change of the state of a PersistentObject instance.
ValueChangeEvent signals a change of a value of a PersistentObject instance.
validateValueAgainstValidateType($name, $value)
Check a value's value against the validation type set on it.
PropertyChangeEvent signals a change of a property of a PersistentObject instance.
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
static underScoreToCamelCase($string, $firstLowerCase=false)
Convert a string in underscore notation to camel case notation.
Definition: StringUtil.php:259
static getInstance($name, $dynamicConfiguration=[])
setValue($name, $value, $forceSet=false, $trackChange=true)
Set the value of an attribute if it exists.
Instances of ReferenceDescription describe reference attributes of PersistentObjects.
static validate($value, $validateDesc, $context=null)
Validate the given value against the given validateType description.
Definition: Validator.php:35
DefaultPersistentObject is the base class of all persistent objects.
getType()
Get the type (including namespace)
Definition: ObjectId.php:96
ValidationException signals an exception in validation.
static getDump($var)
Get the dump of a variable as string.
Definition: StringUtil.php:25
PersistentObject defines the interface of all persistent objects.
__toString()
Get a string representation of the PersistentObject.