DefaultTransaction.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 
13 use Exception;
21 
22 /**
23  * Default implementation of Transaction.
24  *
25  * @author ingo herwig <ingo@wemove.com>
26  */
27 class DefaultTransaction implements Transaction {
28 
29  private static $_isInfoEnabled = false;
30  private static $_isDebugEnabled = false;
31  private static $_logger = null;
32 
33  private $_persistenceFacade = null;
34  private $_eventManager = null;
35 
36  private $_id = '';
37  private $_isActive = false;
38  private $_observedObjects = array();
39 
40  protected $_newObjects = array();
41  protected $_dirtyObjects = array();
42  protected $_deletedObjects = array();
43  protected $_detachedObjects = array();
44 
45  /**
46  * Contains all loaded objects no matter which state they have
47  */
48  protected $_loadedObjects = array();
49 
50  /**
51  * Constructor
52  * @param $persistenceFacade
53  * @param $eventManager
54  */
55  public function __construct(PersistenceFacade $persistenceFacade,
56  EventManager $eventManager) {
57  if (self::$_logger == null) {
58  self::$_logger = LogManager::getLogger(__CLASS__);
59  }
60  $this->_persistenceFacade = $persistenceFacade;
61  $this->_eventManager = $eventManager;
62 
63  $this->_id = __CLASS__.'_'.ObjectId::getDummyId();
64  $this->_eventManager->addListener(StateChangeEvent::NAME,
65  array($this, 'stateChanged'));
66  self::$_isInfoEnabled = self::$_logger->isInfoEnabled();
67  self::$_isDebugEnabled = self::$_logger->isDebugEnabled();
68  }
69 
70  /**
71  * Destructor.
72  */
73  public function __destruct() {
74  $this->_eventManager->removeListener(StateChangeEvent::NAME,
75  array($this, 'stateChanged'));
76  }
77 
78  /**
79  * @see Transaction::registerLoaded()
80  */
81  public function registerLoaded(PersistentObject $object) {
82  $oid = $object->getOID();
83  $key = $oid->__toString();
84  if (self::$_isDebugEnabled) {
85  self::$_logger->debug("New Data:\n".$object->dump());
86  self::$_logger->debug("Registry before:\n".$this->dump());
87  }
88  // register the object if it is newly loaded or
89  // merge the attributes, if it is already loaded
90  $registeredObject = null;
91  if (isset($this->_loadedObjects[$key])) {
92  $registeredObject = $this->_loadedObjects[$key];
93  // merge existing attributes with new attributes
94  if (self::$_isDebugEnabled) {
95  self::$_logger->debug("Merging data of ".$key);
96  }
97  $registeredObject->mergeValues($object);
98  }
99  else {
100  if (self::$_isDebugEnabled) {
101  self::$_logger->debug("Register loaded object: ".$key);
102  }
103  $this->_loadedObjects[$key] = $object;
104  // start to listen to changes if the transaction is active
105  if ($this->_isActive) {
106  if (self::$_isDebugEnabled) {
107  self::$_logger->debug("Start listening to: ".$key);
108  }
109  $this->_observedObjects[$key] = $object;
110  }
111  $registeredObject = $object;
112  }
113  if (self::$_isDebugEnabled) {
114  self::$_logger->debug("Registry after:\n".$this->dump());
115  }
116  return $registeredObject;
117  }
118 
119  /**
120  * @see Transaction::registerNew()
121  */
122  public function registerNew(PersistentObject $object) {
123  if (!$this->_isActive) {
124  return;
125  }
126  $key = $object->getOID()->__toString();
127  if (self::$_isDebugEnabled) {
128  self::$_logger->debug("Register new object: ".$key);
129  }
130  $this->_newObjects[$key] = $object;
131  $this->_observedObjects[$key] = $object;
132  }
133 
134  /**
135  * @see Transaction::registerDirty()
136  */
137  public function registerDirty(PersistentObject $object) {
138  if (!$this->_isActive) {
139  return;
140  }
141  $key = $object->getOID()->__toString();
142  // if it was a new or deleted object, we return immediatly
143  if (isset($this->_newObjects[$key]) || isset($this->_deletedObjects[$key])) {
144  return;
145  }
146  if (self::$_isDebugEnabled) {
147  self::$_logger->debug("Register dirty object: ".$key);
148  }
149  $this->_dirtyObjects[$key] = $object;
150  }
151 
152  /**
153  * @see Transaction::registerDeleted()
154  */
155  public function registerDeleted(PersistentObject $object) {
156  if (!$this->_isActive) {
157  return;
158  }
159  $key = $object->getOID()->__toString();
160  // if it was a new object, we remove it from the registry and
161  // return immediatly
162  if (isset($this->_newObjects[$key])) {
163  unset($this->_newObjects[$key]);
164  return;
165  }
166  // if it was a dirty object, we remove it from the registry
167  if (isset($this->_dirtyObjects[$key])) {
168  unset($this->_dirtyObjects[$key]);
169  }
170  if (self::$_isDebugEnabled) {
171  self::$_logger->debug("Register deleted object: ".$key);
172  }
173  $this->_deletedObjects[$key] = $object;
174  }
175 
176  /**
177  * @see Transaction::begin()
178  */
179  public function begin() {
180  if (self::$_isInfoEnabled) {
181  self::$_logger->info("Starting transaction");
182  }
183  $this->_isActive = true;
184  }
185 
186  /**
187  * @see Transaction::commit()
188  */
189  public function commit() {
190  if (self::$_isInfoEnabled) {
191  self::$_logger->info("Commit transaction");
192  }
193  $changedOids = array();
194  if ($this->_isActive) {
195  $knowTypes = $this->_persistenceFacade->getKnownTypes();
196  try {
197  // start transaction for each mapper
198  foreach ($knowTypes as $type) {
199  $mapper = $this->_persistenceFacade->getMapper($type);
200  $mapper->beginTransaction();
201  }
202  // process the recorded object changes, since new
203  // object changes may occure during the commit, we
204  // loop until all queues are empty
205  $commitDone = false;
206  while (!$commitDone) {
207  $changedOids = array_merge($changedOids, $this->processInserts());
208  $this->processUpdates();
209  $this->processDeletes();
210  // check if all queues are empty
211  $commitDone = (sizeof($this->_newObjects) == 0 &&
212  sizeof($this->_dirtyObjects) == 0 &&
213  sizeof($this->_deletedObjects) == 0);
214  }
215  // commit transaction for each mapper
216  if (self::$_isInfoEnabled) {
217  self::$_logger->info("Committing transaction");
218  }
219  foreach ($knowTypes as $type) {
220  $mapper = $this->_persistenceFacade->getMapper($type);
221  $mapper->commitTransaction();
222  }
223  }
224  catch (Exception $ex) {
225  // rollback transaction for each mapper
226  self::$_logger->error("Rolling back transaction. Exception: ".$ex->__toString());
227  foreach ($knowTypes as $type) {
228  $mapper = $this->_persistenceFacade->getMapper($type);
229  $mapper->rollbackTransaction();
230  }
231  $this->rollback();
232  throw $ex;
233  }
234  }
235  // forget changes
236  $this->rollback();
237  return $changedOids;
238  }
239 
240  /**
241  * @see Transaction::rollback()
242  */
243  public function rollback() {
244  if (self::$_isInfoEnabled) {
245  self::$_logger->info("Rollback transaction");
246  }
247  // forget changes
248  $this->clear();
249  $this->_isActive = false;
250  }
251 
252  /**
253  * @see Transaction::isActive()
254  */
255  public function isActive() {
256  return $this->_isActive;
257  }
258 
259  /**
260  * @see Transaction::getLoaded()
261  */
262  public function getLoaded(ObjectId $oid) {
263  $registeredObject = null;
264  $key = $oid->__toString();
265  if (isset($this->_loadedObjects[$key])) {
266  $registeredObject = $this->_loadedObjects[$key];
267  }
268  return $registeredObject;
269  }
270 
271  /**
272  * @see Transaction::detach()
273  */
274  public function detach(ObjectId $oid) {
275  $key = $oid->__toString();
276  if (isset($this->_newObjects[$key])) {
277  unset($this->_newObjects[$key]);
278  }
279  if (isset($this->_dirtyObjects[$key])) {
280  unset($this->_dirtyObjects[$key]);
281  }
282  if (isset($this->_deletedObjects[$key])) {
283  unset($this->_deletedObjects[$key]);
284  }
285  if (isset($this->_loadedObjects[$key])) {
286  unset($this->_loadedObjects[$key]);
287  }
288  unset($this->_observedObjects[$key]);
289  $this->_detachedObjects[$key] = $oid;
290  }
291 
292  /**
293  * Dump the registry content into a string
294  * @return String
295  */
296  protected function dump() {
297  $str = '';
298  foreach (array_values($this->_loadedObjects) as $curObject) {
299  $str .= $curObject->dump();
300  }
301  return $str;
302  }
303 
304  /**
305  * Clear all internal
306  */
307  protected function clear() {
308  foreach ($this->_newObjects as $object) {
309  unset($this->_observedObjects[$object->getOID()->__toString()]);
310  }
311  $this->_newObjects = array();
312 
313  foreach ($this->_dirtyObjects as $object) {
314  unset($this->_observedObjects[$object->getOID()->__toString()]);
315  }
316  $this->_dirtyObjects = array();
317 
318  foreach ($this->_deletedObjects as $object) {
319  unset($this->_observedObjects[$object->getOID()->__toString()]);
320  }
321  $this->_deletedObjects = array();
322 
323  foreach ($this->_loadedObjects as $object) {
324  unset($this->_observedObjects[$object->getOID()->__toString()]);
325  }
326  $this->_loadedObjects = array();
327 
328  $this->_detachedObjects = array();
329  }
330 
331  /**
332  * Process the new objects queue
333  * @return Map of oid changes (key: oid string before commit, value: oid string after commit)
334  */
335  protected function processInserts() {
336  $changedOids = array();
337  $pendingInserts = array();
338  $insertOids = array_keys($this->_newObjects);
339  while (sizeof($insertOids) > 0) {
340  $key = array_shift($insertOids);
341  if (self::$_isDebugEnabled) {
342  self::$_logger->debug("Process insert on object: ".$key);
343  }
344  $object = $this->_newObjects[$key];
345  // postpone insert, if the object has required objects that are
346  // not persisted yet
347  $canInsert = true;
348  $requiredObjects = $object->getIndispensableObjects();
349  foreach ($requiredObjects as $requiredObject) {
350  if ($requiredObject->getState() == PersistentObject::STATE_NEW) {
351  if (self::$_isDebugEnabled) {
352  self::$_logger->debug("Postpone insert of object: ".$key.". Required objects are not saved yet.");
353  }
354  $pendingInserts[] = $object;
355  $canInsert = false;
356  break;
357  }
358  }
359  if ($canInsert) {
360  $oldOid = $object->getOID();
361  $object->getMapper()->save($object);
362  $changedOids[$oldOid->__toString()] = $object->getOID()->__toString();
363  }
364  unset($this->_newObjects[$key]);
365  $insertOids = array_keys($this->_newObjects);
366  }
367  // re-add pending inserts
368  foreach ($pendingInserts as $object) {
369  $key = $object->getOID()->__toString();
370  $this->_newObjects[$key] = $object;
371  }
372  return $changedOids;
373  }
374 
375  /**
376  * Process the dirty objects queue
377  */
378  protected function processUpdates() {
379  $updateOids = array_keys($this->_dirtyObjects);
380  while (sizeof($updateOids) > 0) {
381  $key = array_shift($updateOids);
382  if (self::$_isDebugEnabled) {
383  self::$_logger->debug("Process update on object: ".$key);
384  }
385  $object = $this->_dirtyObjects[$key];
386  $object->getMapper()->save($object);
387  unset($this->_dirtyObjects[$key]);
388  $updateOids = array_keys($this->_dirtyObjects);
389  }
390  }
391 
392  /**
393  * Process the deleted objects queue
394  */
395  protected function processDeletes() {
396  $deleteOids = array_keys($this->_deletedObjects);
397  while (sizeof($deleteOids) > 0) {
398  $key = array_shift($deleteOids);
399  if (self::$_isDebugEnabled) {
400  self::$_logger->debug("Process delete on object: ".$key);
401  }
402  $object = $this->_deletedObjects[$key];
403  $object->getMapper()->delete($object);
404  unset($this->_deletedObjects[$key]);
405  $deleteOids = array_keys($this->_deletedObjects);
406  }
407  }
408 
409  /**
410  * Listen to StateChangeEvents
411  * @param $event StateChangeEvent instance
412  */
413  public function stateChanged(StateChangeEvent $event) {
414  $object = $event->getObject();
415 
416  // don't listen to detached object changes
417  $key = $object->getOID()->__toString();
418  if (isset($this->_detachedObjects[$key])) {
419  return;
420  }
421  //if (isset($this->_observedObjects[$object->getOID()->__toString()])) {
422  $oldState = $event->getOldValue();
423  $newState = $event->getNewValue();
424  if (self::$_isDebugEnabled) {
425  self::$_logger->debug("State changed: ".$object->getOID()." old:".$oldState." new:".$newState);
426  }
427  switch ($newState) {
429  $this->registerNew($object);
430  break;
431 
433  $this->registerDirty($object);
434  break;
435 
437  $this->registerDeleted($object);
438  break;
439  }
440  //}
441  }
442 
443  /**
444  * @see Transaction::getObjects()
445  */
446  public function getObjects() {
447  return $this->_observedObjects;
448  }
449 }
450 ?>
getOID()
Get the object id of the PersistentObject.
static getDummyId()
Get a dummy id ("wcmf" + unique 32 character string).
Definition: ObjectId.php:219
processUpdates()
Process the dirty objects queue.
EventManager is responsible for dispatching events to registered listeners.
getObject()
Get the object whose state has changed.
ObjectId is the unique identifier of an object.
Definition: ObjectId.php:27
StateChangeEvent signals a change of the state of a PersistentObject instance.
static getLogger($name)
Get the logger with the given name.
Definition: LogManager.php:35
__construct(PersistenceFacade $persistenceFacade, EventManager $eventManager)
Constructor.
Default implementation of Transaction.
Transaction implements the Unit of Work pattern as it defines the interface for maintaining a list of...
Definition: Transaction.php:21
stateChanged(StateChangeEvent $event)
Listen to StateChangeEvents.
__toString()
Get a string representation of the object id.
Definition: ObjectId.php:204
dump()
Get a string representation of the values of the PersistentObject.
dump()
Dump the registry content into a string.
processInserts()
Process the new objects queue.
PersistenceFacade defines the interface for PersistenceFacade implementations.
$_loadedObjects
Contains all loaded objects no matter which state they have.
processDeletes()
Process the deleted objects queue.
PersistentObject defines the interface of all persistent objects.