PersistentIterator.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  */
11 namespace wcmf\lib\model;
12 
16 
17 /**
18  * PersistentIterator is used to iterate over a tree/list built of oids
19  * using a Depth-First-Algorithm. To persist its state use the PersistentIterator::save() method,
20  * to restore its state use the static PersistentIterator::load() method, which returns the loaded instance.
21  * States are identified by an unique id, which is provided after saving.
22  *
23  * @author ingo herwig <ingo@wemove.com>
24  */
25 class PersistentIterator implements \Iterator {
26 
27  private $_persistenceFacade = null;
28  private $_session = null;
29 
30  protected $_end; // indicates if the iteration is ended
31  protected $_oidList; // the list of oids to process
32  protected $_processedOidList; // the list of all seen object ids
33  protected $_currentOid; // the oid the iterator points to
34  protected $_startOid; // the oid the iterator started with
35  protected $_currentDepth; // the depth in the tree of the oid the iterator points to
36  protected $_aggregationKinds; // array of aggregation kind values to follow (empty: all)
37 
38  /**
39  * Constructor.
40  * @param $persistenceFacade
41  * @param $session
42  * @param $oid The oid to start from.
43  * @param $aggregationKinds Array of aggregation kind values of relations to follow
44  * possible values: 'none', 'shared', 'composite'. Empty array means all (default: empty)
45  */
46  public function __construct(PersistenceFacade $persistenceFacade,
47  Session $session,
48  ObjectId $oid,
49  $aggregationKinds=array()) {
50  $this->_persistenceFacade = $persistenceFacade;
51  $this->_session = $session;
52 
53  $this->_end = false;
54  $this->_oidList = array();
55  $this->_processedOidList = array();
56  $this->_currentOid = $oid;
57  $this->_startOid = $oid;
58  $this->_currentDepth = 0;
59  $this->_aggregationKinds = $aggregationKinds;
60  }
61 
62  /**
63  * Save the iterator state to the session
64  * @return A unique id to provide for load, see PersistentIterator::load()
65  */
66  public function save() {
67  $uid = md5(uniqid(""));
68  $state = array('end' => $this->_end, 'oidList' => $this->_oidList, 'processedOidList' => $this->_processedOidList,
69  'currentOID' => $this->_currentOid, 'currentDepth' => $this->_currentDepth, 'aggregationKinds' => $this->_aggregationKinds);
70  $this->_session->set('PersistentIterator.'.$uid, $state);
71  return $uid;
72  }
73 
74  /**
75  * Load an iterator state from the session
76  * @param $persistenceFacade
77  * @param $session
78  * @param $uid The unique id returned from the save method, see PersistentIterator::save()
79  * @return PersistentIterator instance holding the saved state or null if unique id is not found
80  */
81  public static function load($persistenceFacade, $session, $uid) {
82  // get state from session
83  $state = $session->get('PersistentIterator.'.$uid);
84  if ($state == null) {
85  return null;
86  }
87  // create instance
88  $instance = new PersistentIterator($persistenceFacade, $session,
89  $state['currentOID']);
90  $instance->_end = $state['end'];
91  $instance->_oidList = $state['oidList'];
92  $instance->_processedOidList = $state['processedOidList'];
93  $instance->_currentDepth = $state['currentDepth'];
94  $instance->_aggregationKinds = $state['aggregationKinds'];
95  return $instance;
96  }
97 
98  /**
99  * Return the current element
100  * @return ObjectId, the current object id
101  */
102  public function current() {
103  return $this->_currentOid;
104  }
105 
106  /**
107  * Return the key of the current element
108  * @return Number, the current depth
109  */
110  public function key() {
111  return $this->_currentDepth;
112  }
113 
114  /**
115  * Move forward to next element
116  */
117  public function next() {
118  // the current oid was processed
119  $this->_processedOidList[] = $this->_currentOid->__toString();
120 
121  $node = $this->_persistenceFacade->load($this->_currentOid);
122 
123  // collect navigable children for the given aggregation kinds
124  $childOIDs = array();
125  $mapper = $node->getMapper();
126  $relations = $mapper->getRelations('child');
127  $followAll = sizeof($this->_aggregationKinds) == 0;
128  foreach ($relations as $relation) {
129  $aggregationKind = $relation->getOtherAggregationKind();
130  if ($relation->getOtherNavigability() && ($followAll || in_array($aggregationKind, $this->_aggregationKinds))) {
131  $childValue = $node->getValue($relation->getOtherRole());
132  if ($childValue != null) {
133  $children = $relation->isMultiValued() ? $childValue : array($childValue);
134  foreach ($children as $child) {
135  $childOIDs[] = $child->getOID();
136  }
137  }
138  }
139  }
140  $this->addToQueue($childOIDs, ++$this->_currentDepth);
141 
142  // set current node
143  if (sizeof($this->_oidList) != 0) {
144  list($oid, $depth) = array_pop($this->_oidList);
145  $oidStr = $oid->__toString();
146  // not the last node -> search for unprocessed nodes
147  while (sizeof($this->_oidList) > 0 && in_array($oidStr, $this->_processedOidList)) {
148  list($oid, $depth) = array_pop($this->_oidList);
149  $oidStr = $oid->__toString();
150  }
151  // last node found, but it was processed already
152  if (sizeof($this->_oidList) == 0 && in_array($oidStr, $this->_processedOidList)) {
153  $this->_end = true;
154  }
155  $this->_currentOid = $oid;
156  $this->_currentDepth = $depth;
157  }
158  else {
159  $this->_end = true;
160  }
161  return $this;
162  }
163 
164  /**
165  * Rewind the Iterator to the first element
166  */
167  public function rewind() {
168  $this->_end = false;
169  $this->_oidList= array();
170  $this->_processedOidList = array();
171  $this->_currentOid = $this->_startOid;
172  $this->_currentDepth = 0;
173  }
174 
175  /**
176  * Checks if current position is valid
177  */
178  public function valid() {
179  return !$this->_end;
180  }
181 
182  /**
183  * Add oids to the processing queue.
184  * @param $oidList An array of oids.
185  * @param $depth The depth of the oids in the tree.
186  */
187  protected function addToQueue($oidList, $depth) {
188  for ($i=sizeOf($oidList)-1; $i>=0; $i--) {
189  $this->_oidList[] = array($oidList[$i], $depth);
190  }
191  }
192 }
193 ?>
Node related interfaces and classes.
Definition: namespaces.php:26
static load($persistenceFacade, $session, $uid)
Load an iterator state from the session.
current()
Return the current element.
ObjectId is the unique identifier of an object.
Definition: ObjectId.php:27
valid()
Checks if current position is valid.
addToQueue($oidList, $depth)
Add oids to the processing queue.
Session is the interface for session implementations and defines access to session variables...
Definition: Session.php:21
rewind()
Rewind the Iterator to the first element.
__toString()
Get a string representation of the object id.
Definition: ObjectId.php:204
__construct(PersistenceFacade $persistenceFacade, Session $session, ObjectId $oid, $aggregationKinds=array())
Constructor.
next()
Move forward to next element.
PersistenceFacade defines the interface for PersistenceFacade implementations.
PersistentIterator is used to iterate over a tree/list built of oids using a Depth-First-Algorithm.
key()
Return the key of the current element.
save()
Save the iterator state to the session.
get($key)
Returns the value of an session variable.