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