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