Node.php
1 <?php
2 /**
3  * wCMF - wemove Content Management Framework
4  * Copyright (C) 2005-2018 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 
24 
25 /**
26  * Node adds the concept of relations to PersistentObject. It is the basic component for
27  * building object trees (although a Node can have more than one parents).
28  * Relations are stored as values where the value name is the role name.
29  * The Node class implements the _Composite Pattern_.
30  * Use the methods addNode(), deleteNode() to build/modify trees.
31  *
32  * @author ingo herwig <ingo@wemove.com>
33  */
35 
40 
41  private $relationStates = [];
42 
43  private $addedNodes = [];
44  private $deletedNodes = [];
45  private $orderedNodes = null;
46 
47  private static $parentGetValueMethod = null;
48  private static $logger = null;
49 
50  /**
51  * Constructor
52  * @param $oid ObjectId instance (optional)
53  * @param $initialData Associative array with initial data to override default data (optional)
54  */
55  public function __construct(ObjectId $oid=null, array $initialData=null) {
56  parent::__construct($oid, $initialData);
57  // get parent::getValue method by reflection
58  if (self::$parentGetValueMethod == null) {
59  $reflector = new \ReflectionClass(__CLASS__);
60  $parent = $reflector->getParentClass();
61  self::$parentGetValueMethod = $parent->getMethod('getValue');
62  }
63  if (self::$logger == null) {
64  self::$logger = LogManager::getLogger(__CLASS__);
65  }
66  }
67 
68  /**
69  * @see PersistentObject::__clone()
70  */
71  public function __clone() {
72  $copy = parent::__clone();
73  $copy->relationStates = $this->relationStates;
74 
75  return $copy;
76  }
77 
78  /**
79  * @see PersistentObject::getValueNames()
80  */
81  public function getValueNames($excludeTransient=false) {
82  // exclude relations
83  $allAttributes = parent::getValueNames($excludeTransient);
84  $attributes = [];
85  $mapper = $this->getMapper();
86  foreach ($allAttributes as $attribute) {
87  if (!$mapper->hasRelation($attribute)) {
88  $attributes[] = $attribute;
89  }
90  }
91  return $attributes;
92  }
93 
94  /**
95  * @see PersistentObject::getValue()
96  */
97  public function getValue($name) {
98  // initialize a relation value, if not done before
99  $value = parent::getValue($name);
100  if (isset($this->relationStates[$name]) &&
101  $this->relationStates[$name] == self::RELATION_STATE_UNINITIALIZED) {
102 
103  $this->relationStates[$name] = self::RELATION_STATE_INITIALIZING;
104  $mapper = $this->getMapper();
105  $allRelatives = $mapper->loadRelation([$this], $name, BuildDepth::PROXIES_ONLY);
106  $oidStr = $this->getOID()->__toString();
107  $relatives = isset($allRelatives[$oidStr]) ? $allRelatives[$oidStr] : null;
108  $relDesc = $mapper->getRelation($name);
109  if ($relDesc->isMultiValued()) {
110  $mergeResult = self::mergeObjectLists($value, $relatives);
111  $value = $mergeResult['result'];
112  }
113  else {
114  $value = $relatives != null ? $relatives[0] : null;
115  }
116  $this->setValueInternal($name, $value);
117  $this->relationStates[$name] = self::RELATION_STATE_INITIALIZED;
118  }
119  return $value;
120  }
121 
122  /**
123  * @see PersistentObject::setValue()
124  */
125  public function setValue($name, $value, $forceSet=false, $trackChange=true) {
126  // if the attribute is a relation, a special handling is required
127  $mapper = $this->getMapper();
128  if ($mapper->hasRelation($name)) {
129  if (!is_array($value)) {
130  $value = [$value];
131  }
132  // clean the value
133  parent::setValue($name, null, true, false);
134  // delegate to addNode
135  $result = true;
136  for($i=0, $count=sizeof($value); $i<$count; $i++) {
137  $curValue = $value[$i];
138  if ($curValue != null) {
139  $result &= $this->addNode($curValue, $name, $forceSet, $trackChange);
140  }
141  }
142  $this->relationStates[$name] = self::RELATION_STATE_INITIALIZED;
143  return $result;
144  }
145  else {
146  // default behaviour
147  return parent::setValue($name, $value, $forceSet, $trackChange);
148  }
149  return true;
150  }
151 
152  /**
153  * @see PersistentObject::removeValue()
154  */
155  public function removeValue($name) {
156  parent::removeValue($name);
157  // set relation state to loaded in order to prevent lazy initialization
158  $mapper = $this->getMapper();
159  if ($mapper->hasRelation($name)) {
160  $this->relationStates[$name] = self::RELATION_STATE_LOADED;
161  }
162  }
163 
164  /**
165  * @see PersistentObject::getIndispensableObjects()
166  */
167  public function getIndispensableObjects() {
168  // return the parent objects
169  return $this->getParents();
170  }
171 
172  /**
173  * Get Nodes that match given conditions from a list.
174  * @param $nodeList An array of nodes to filter or a single Node.
175  * @param $oid The object id that the Nodes should match (optional, default: _null_)
176  * @param $type The type that the Nodes should match (either fully qualified or simple, if not ambiguous)
177  * (optional, default: _null_)
178  * @param $values An associative array holding key value pairs that the Node values should match
179  * (values are interpreted as regular expression, optional, default: _null_)
180  * @param $properties An associative array holding key value pairs that the Node properties should match
181  * (values are interpreted as regular expression, optional, default: _null_)
182  * @param $useRegExp Boolean whether to interpret the given values/properties as regular expressions or not (default: _true_)
183  * @return An Array holding references to the Nodes that matched.
184  */
185  public static function filter(array $nodeList, ObjectId $oid=null, $type=null, $values=null,
186  $properties=null, $useRegExp=true) {
187 
188  $returnArray = [];
189  for ($i=0, $count=sizeof($nodeList); $i<$count; $i++) {
190  $curNode = $nodeList[$i];
191  if ($curNode instanceof PersistentObject) {
192  $match = true;
193  // check oid
194  if ($oid != null && $curNode->getOID() != $oid) {
195  $match = false;
196  }
197  // check type
198  if ($type != null) {
199  $persistenceFacade = ObjectFactory::getInstance('persistenceFacade');
200  $fqType = $persistenceFacade->getFullyQualifiedType($type);
201  if ($fqType != null && $curNode->getType() != $fqType) {
202  $match = false;
203  }
204  }
205  // check values
206  if ($values != null && is_array($values)) {
207  foreach($values as $key => $value) {
208  $nodeValue = $curNode->getValue($key);
209  if ($useRegExp && !preg_match("/".$value."/m", $nodeValue) ||
210  !$useRegExp && $value != $nodeValue) {
211  $match = false;
212  break;
213  }
214  }
215  }
216  // check properties
217  if ($properties != null && is_array($properties)) {
218  foreach($properties as $key => $value) {
219  $nodeProperty = $curNode->getProperty($key);
220  if ($useRegExp && !preg_match("/".$value."/m", $nodeProperty) ||
221  !$useRegExp && $value != $nodeProperty) {
222  $match = false;
223  break;
224  }
225  }
226  }
227  if ($match) {
228  $returnArray[] = $curNode;
229  }
230  }
231  else {
232  self::$logger->warn(StringUtil::getDump($curNode)." found, where a PersistentObject was expected.\n".ErrorHandler::getStackTrace(),
233  __CLASS__);
234  }
235  }
236  return $returnArray;
237  }
238 
239  /**
240  * @see PersistentObject::mergeValues
241  */
242  public function mergeValues(PersistentObject $object) {
243  parent::mergeValues($object);
244  // implement special handling for relation values
245  $mapper = $this->getMapper();
246  foreach ($mapper->getRelations() as $curRelationDesc) {
247  $valueName = $curRelationDesc->getOtherRole();
248  // use parent getters to avoid loading relations
249  $existingValue = self::$parentGetValueMethod->invokeArgs($this, [$valueName]);
250  $newValue = self::$parentGetValueMethod->invokeArgs($object, [$valueName]);
251  if ($newValue != null) {
252  if ($curRelationDesc->isMultiValued()) {
253  $mergeResult = self::mergeObjectLists($existingValue, $newValue);
254  $newValue = $mergeResult['result'];
255  }
256  $this->setValueInternal($valueName, $newValue);
257  }
258  }
259  }
260 
261  /**
262  * Merge two object lists using the following rules:
263  * - proxies in list1 are replaced by the appropriate objects from list2
264  * - proxies/objects from list2 that don't exist in list1 are added to list1
265  * @param $list1 Array of PersistentObject(Proxy) instances
266  * @param $list2 Array of PersistentObject(Proxy) instances
267  * @return Associative array with keys 'result' and 'added' and arrays of
268  * all and only added objects respectively.
269  */
270  protected static function mergeObjectLists($list1, $list2) {
271  // ensure arrays
272  if (!is_array($list1)) {
273  $list1 = [];
274  }
275  if (!is_array($list2)) {
276  $list2 = [];
277  }
278  // create hashtables for better search performance
279  $list1Map = [];
280  $added = [];
281  foreach ($list1 as $curObject) {
282  $list1Map[$curObject->getOID()->__toString()] = $curObject;
283  }
284  // merge
285  foreach ($list2 as $curObject) {
286  $curOidStr = $curObject->getOID()->__toString();
287  if (!isset($list1Map[$curOidStr])) {
288  // add the object, if it doesn't exist yet
289  $list1Map[$curOidStr] = $curObject;
290  $added[] = $curObject;
291  }
292  elseif ($list1Map[$curOidStr] instanceof PersistentObjectProxy &&
293  $curObject instanceof PersistentObject) {
294  // overwrite a proxy by a real subject
295  $list1Map[$curOidStr] = $curObject;
296  }
297  }
298  return ['result' => array_values($list1Map), 'added' => $added];
299  }
300 
301  /**
302  * Get the names of all relations.
303  * @return An array of relation names.
304  */
305  public function getRelationNames() {
306  $result = [];
307  $relations = $this->getRelations();
308  foreach ($relations as $curRelation) {
309  $result[] = $curRelation->getOtherRole();
310  }
311  return $result;
312  }
313 
314  /**
315  * Add a Node to the given relation. Delegates to setValue internally.
316  * @param $other PersistentObject
317  * @param $role The role of the Node in the created relation. If null, the role will be
318  * the Node's simple type (without namespace) (default: _null_)
319  * @param $forceSet @see PersistentObject::setValue()
320  * @param $trackChange @see PersistentObject::setValue()
321  * @param $updateOtherSide Boolean whether to update also the other side of the relation (default: _true_)
322  * @return Boolean whether the operation succeeds or not
323  */
324  public function addNode(PersistentObject $other, $role=null, $forceSet=false, $trackChange=true, $updateOtherSide=true) {
325  $mapper = $this->getMapper();
326 
327  // set role if missing
328  if ($role == null) {
329  $otherType = $other->getType();
330  $relations = $mapper->getRelationsByType($otherType);
331  $role = (sizeof($relations) > 0) ? $relations[0]->getOtherRole() : $otherType;
332  }
333 
334  // get the relation description
335  $relDesc = $mapper->getRelation($role);
336 
337  $value = $other;
338  $oldValue = parent::getValue($role);
339  $addedNodes = []; // this array contains the other node or nothing
340  if (!$relDesc || $relDesc->isMultiValued()) {
341  // check multiplicity if multivalued
342  $maxMultiplicity = $relDesc->getOtherMaxMultiplicity();
343  if ($relDesc->isMultiValued() && !($maxMultiplicity == 'unbounded') &&
344  sizeof(oldValue) >= $maxMultiplicity) {
345  throw new IllegalArgumentException("Maximum number of related objects exceeded: ".$role." (".(sizeof(oldValue)+1)." > ".$maxMultiplicity.")");
346  }
347  // make sure that the value is an array if multivalued
348  $mergeResult = self::mergeObjectLists($oldValue, [$value]);
349  $value = $mergeResult['result'];
350  $addedNodes = $mergeResult['added'];
351  }
352  elseif ($oldValue == null && $value != null ||
353  $oldValue->getOID()->__toString() != $value->getOID()->__toString()) {
354  $addedNodes[] = $value;
355  }
356  $result1 = sizeof($addedNodes) > 0 && parent::setValue($role, $value, $forceSet, $trackChange);
357 
358  // remember the addition
359  if (sizeof($addedNodes) > 0) {
360  if (!isset($this->addedNodes[$role])) {
361  $this->addedNodes[$role] = [];
362  }
363  $this->addedNodes[$role][] = $other;
364  }
365 
366  // propagate add action to the other object
367  $result2 = true;
368  if ($updateOtherSide) {
369  $thisRole = $relDesc ? $relDesc->getThisRole() : null;
370  $result2 = $other->addNode($this, $thisRole, $forceSet, $trackChange, false);
371  }
372  return ($result1 & $result2);
373  }
374 
375  /**
376  * Get the object ids of the nodes that were added since the node was loaded.
377  * Persistence mappers use this method when persisting the node relations.
378  * @return Associative array with the roles as keys and an array of PersistentObject instances
379  * as values
380  */
381  public function getAddedNodes() {
382  return $this->addedNodes;
383  }
384 
385  /**
386  * Delete a Node from the given relation.
387  * @param $other The Node to delete.
388  * @param $role The role of the Node. If null, the role is the Node's type (without namespace) (default: _null_)
389  * @param $updateOtherSide Boolean whether to update also the other side of the relation (default: _true_)
390  */
391  public function deleteNode(PersistentObject $other, $role=null, $updateOtherSide=true) {
392 
393  $mapper = $this->getMapper();
394 
395  // set role if missing
396  if ($role == null) {
397  $otherType = $other->getType();
398  $relations = $mapper->getRelationsByType($otherType);
399  $role = (sizeof($relations) > 0) ? $relations[0]->getOtherRole() : $otherType;
400  }
401 
402  $nodes = $this->getValue($role);
403  if (empty($nodes)) {
404  // nothing to delete
405  return;
406  }
407 
408  // get the relation description
409  $relDesc = $mapper->getRelation($role);
410 
411  $oid = $other->getOID();
412  if (is_array($nodes)) {
413  // multi valued relation
414  for($i=0, $count=sizeof($nodes); $i<$count; $i++) {
415  if ($nodes[$i]->getOID() == $oid) {
416  // remove child
417  array_splice($nodes, $i, 1);
418  break;
419  }
420  }
421  }
422  else {
423  // single valued relation
424  if ($nodes->getOID() == $oid) {
425  // remove child
426  $nodes = null;
427  }
428  }
429  parent::setValue($role, $nodes);
430 
431  // remember the deletion
432  if (!isset($this->deletedNodes[$role])) {
433  $this->deletedNodes[$role] = [];
434  }
435  $this->deletedNodes[$role][] = $other->getOID();
436  $this->setState(PersistentOBject::STATE_DIRTY);
437 
438  // propagate add action to the other object
439  if ($updateOtherSide) {
440  $thisRole = $relDesc ? $relDesc->getThisRole() : null;
441  $other->deleteNode($this, $thisRole, false);
442  }
443  }
444 
445  /**
446  * Get the object ids of the nodes that were deleted since the node was loaded.
447  * Persistence mappers use this method when persisting the node relations.
448  * @return Associative array with the roles as keys and an array of ObjectId instances
449  * as values
450  */
451  public function getDeletedNodes() {
452  return $this->deletedNodes;
453  }
454 
455  /**
456  * Define the order of related Node instances. The mapper is responsible for
457  * persisting the order of the given Node instances in relation to this Node.
458  * @note Note instances, that are not explicitly sortable by a sortkey
459  * (@see PersistenceMapper::getDefaultOrder()) will be ignored. If a given
460  * Node instance is not related to this Node yet, an exception will be thrown.
461  * Any not persisted definition of a previous call will be overwritten
462  * @param $orderedList Array of ordered Node instances
463  * @param $movedList Array of repositioned Node instances (optional, improves performance)
464  * @param $role Role name of the Node instances (optional)
465  */
466  public function setNodeOrder(array $orderedList, array $movedList=null, $role=null) {
467  $this->orderedNodes = [
468  'ordered' => $orderedList,
469  'moved' => $movedList,
470  'role' => $role
471  ];
472  $this->setState(PersistentOBject::STATE_DIRTY);
473  }
474 
475  /**
476  * Get the order of related Node instances, if it was defined using
477  * the Node::setNodeOrder() method.
478  * @return Associative array of with keys 'ordered', 'moved', 'role' or null
479  */
480  public function getNodeOrder() {
481  return $this->orderedNodes;
482  }
483 
484  /**
485  * Load the children of a given role and add them. If all children should be
486  * loaded, set the role parameter to null.
487  * @param $role The role of children to load (maybe null, to load all children) (default: _null_)
488  * @param $buildDepth One of the BUILDDEPTH constants or a number describing the number of generations to build
489  * (default: _BuildDepth::SINGLE_)
490  */
491  public function loadChildren($role=null, $buildDepth=BuildDepth::SINGLE) {
492  if ($role != null) {
493  $this->loadRelations([$role], $buildDepth);
494  }
495  else {
496  $this->loadRelations(array_keys($this->getPossibleChildren()), $buildDepth);
497  }
498  }
499 
500  /**
501  * Get the number of children of the Node.
502  * @param $memOnly Boolean whether to only get the number of loaded children or all children (default: _true_).
503  * @return The number of children.
504  */
505  public function getNumChildren($memOnly=true) {
506  return $this->getNumRelatives('child', $memOnly);
507  }
508 
509  /**
510  * Get the first child that matches given conditions.
511  * @param $role The role that the child should match (optional, default: _null_).
512  * @param $type The type that the child should match (either fully qualified or simple, if not ambiguous)
513  * (optional, default: _null_).
514  * @param $values An associative array holding key value pairs that the child values should match (optional, default: _null_).
515  * @param $properties An associative array holding key value pairs that the child properties should match (optional, default: _null_).
516  * @param $useRegExp Boolean whether to interpret the given values/properties as regular expressions or not (default: _true_)
517  * @return Node instance or null.
518  */
519  public function getFirstChild($role=null, $type=null, $values=null, $properties=null, $useRegExp=true) {
520  $children = $this->getChildrenEx(null, $role, $type, $values, $properties, $useRegExp);
521  if (sizeof($children) > 0) {
522  return $children[0];
523  }
524  else {
525  return null;
526  }
527  }
528 
529  /**
530  * Get the Node's children.
531  * @param $memOnly Boolean whether to only get the loaded children or all children (default: _true_).
532  * @return Array PersistentObject instances.
533  */
534  public function getChildren($memOnly=true) {
535  return $this->getRelatives('child', $memOnly);
536  }
537 
538  /**
539  * Get the children that match given conditions.
540  * @note This method will only return objects that are already loaded, to get all objects in
541  * the given relation (including proxies), use the Node::getValue() method and filter the returned
542  * list afterwards.
543  * @param $oid The object id that the children should match (optional, default: _null_).
544  * @param $role The role that the children should match (optional, default: _null_).
545  * @param $type The type that the children should match (either fully qualified or simple, if not ambiguous)
546  * (optional, default: _null_).
547  * @param $values An associative array holding key value pairs that the children values should match (optional, default: _null_).
548  * @param $properties An associative array holding key value pairs that the children properties should match (optional, default: _null_).
549  * @param $useRegExp Boolean whether to interpret the given values/properties as regular expressions or not (default: _true_)
550  * @return Array containing children Nodes that matched (proxies not included).
551  */
552  public function getChildrenEx(ObjectId $oid=null, $role=null, $type=null, $values=null,
553  $properties=null, $useRegExp=true) {
554  if ($role != null) {
555  // nodes of a given role are requested
556  // make sure it is a child role
557  $childRoles = $this->getPossibleChildren();
558  if (!isset($childRoles[$role])) {
559  throw new IllegalArgumentException("No child role defined with name: ".$role);
560  }
561  // we are only looking for nodes that are in memory already
562  $nodes = parent::getValue($role);
563  if (!is_array($nodes)) {
564  $nodes = [$nodes];
565  }
566  // sort out proxies
567  $children = [];
568  foreach($nodes as $curNode) {
569  if ($curNode instanceof PersistentObject) {
570  $children[] = $curNode;
571  }
572  }
573  return self::filter($children, $oid, $type, $values, $properties, $useRegExp);
574  }
575  else {
576  return self::filter($this->getChildren(), $oid, $type, $values, $properties, $useRegExp);
577  }
578  }
579 
580  /**
581  * Get possible children of this node type (independent of existing children).
582  * @return An Array with role names as keys and RelationDescription instances as values.
583  */
584  public function getPossibleChildren() {
585  $result = [];
586  $relations = $this->getRelations('child');
587  foreach ($relations as $curRelation) {
588  $result[$curRelation->getOtherRole()] = $curRelation;
589  }
590  return $result;
591  }
592 
593  /**
594  * Load the parents of a given role and add them. If all parents should be
595  * loaded, set the role parameter to null.
596  * @param $role The role of parents to load (maybe null, to load all parents) (default: _null_)
597  * @param $buildDepth One of the BUILDDEPTH constants or a number describing the number of generations to build
598  * (default: _BuildDepth::SINGLE_)
599  */
600  public function loadParents($role=null, $buildDepth=BuildDepth::SINGLE) {
601  if ($role != null) {
602  $this->loadRelations([$role], $buildDepth);
603  }
604  else {
605  $this->loadRelations(array_keys($this->getPossibleParents()), $buildDepth);
606  }
607  }
608 
609  /**
610  * Get the number of parents of the Node.
611  * @param $memOnly Boolean whether to only get the number of loaded parents or all parents (default: _true_).
612  * @return The number of parents.
613  */
614  public function getNumParents($memOnly=true) {
615  return $this->getNumRelatives('parent', $memOnly);
616  }
617 
618  /**
619  * Get the Node's parent. This method exists for compatibility with previous
620  * versions. It returns the first parent.
621  * @return Node
622  */
623  public function getParent() {
624  $parents = $this->getParents();
625  if (sizeof($parents) > 0) {
626  return $parents[0];
627  }
628  else {
629  return null;
630  }
631  }
632 
633  /**
634  * Get the first parent that matches given conditions.
635  * @param $role The role that the parent should match (optional, default: _null_).
636  * @param $type The type that the parent should match (either fully qualified or simple, if not ambiguous)
637  * (optional, default: _null_).
638  * @param $values An associative array holding key value pairs that the parent values should match (optional, default: _null_).
639  * @param $properties An associative array holding key value pairs that the parent properties should match (optional, default: _null_).
640  * @param $useRegExp Boolean whether to interpret the given values/properties as regular expressions or not (default: _true_)
641  * @return Node instance or null.
642  */
643  public function getFirstParent($role=null, $type=null, $values=null, $properties=null, $useRegExp=true) {
644 
645  $parents = $this->getParentsEx(null, $role, $type, $values, $properties, $useRegExp);
646  if (sizeof($parents) > 0) {
647  return $parents[0];
648  }
649  else {
650  return null;
651  }
652  }
653 
654  /**
655  * Get the Nodes parents.
656  * @param $memOnly Boolean whether to only get the loaded parents or all parents (default: _true_).
657  * @return Array PersistentObject instances.
658  */
659  public function getParents($memOnly=true) {
660  return $this->getRelatives('parent', $memOnly);
661  }
662 
663  /**
664  * Get the parents that match given conditions.
665  * @note This method will only return objects that are already loaded, to get all objects in
666  * the given relation (including proxies), use the Node::getValue() method and filter the returned
667  * list afterwards.
668  * @param $oid The object id that the parent should match (optional, default: _null_).
669  * @param $role The role that the parents should match (optional, default: _null_).
670  * @param $type The type that the parents should match (either fully qualified or simple, if not ambiguous)
671  * (optional, default: _null_).
672  * @param $values An associative array holding key value pairs that the parent values should match (optional, default: _null_).
673  * @param $properties An associative array holding key value pairs that the parent properties should match (optional, default: _null_).
674  * @param $useRegExp Boolean whether to interpret the given values/properties as regular expressions or not (default: _true_)
675  * @return Array containing parent Nodes that matched (proxies not included).
676  */
677  public function getParentsEx(ObjectId $oid=null, $role=null, $type=null, $values=null,
678  $properties=null, $useRegExp=true) {
679  if ($role != null) {
680  // nodes of a given role are requested
681  // make sure it is a parent role
682  $parentRoles = $this->getPossibleParents();
683  if (!isset($parentRoles[$role])) {
684  throw new IllegalArgumentException("No parent role defined with name: ".$role);
685  }
686  // we are only looking for nodes that are in memory already
687  $nodes = parent::getValue($role);
688  if (!is_array($nodes)) {
689  $nodes = [$nodes];
690  }
691  // sort out proxies
692  $parents = [];
693  foreach($nodes as $curNode) {
694  if ($curNode instanceof PersistentObject) {
695  $parents[] = $curNode;
696  }
697  }
698  return self::filter($parents, $oid, $type, $values, $properties, $useRegExp);
699  }
700  else {
701  return self::filter($this->getParents(), $oid, $type, $values, $properties, $useRegExp);
702  }
703  }
704 
705  /**
706  * Get possible parents of this node type (independent of existing parents).
707  * @return An Array with role names as keys and RelationDescription instances as values.
708  */
709  public function getPossibleParents() {
710  $result = [];
711  $relations = $this->getRelations('parent');
712  foreach ($relations as $curRelation) {
713  $result[$curRelation->getOtherRole()] = $curRelation;
714  }
715  return $result;
716  }
717 
718  /**
719  * Get the relation description for a given node.
720  * @param $object PersistentObject instance to look for
721  * @return RelationDescription instance or null, if the Node is not related
722  */
723  public function getNodeRelation($object) {
724  $relations = $this->getRelations();
725  foreach ($relations as $curRelation) {
726  $curRelatives = parent::getValue($curRelation->getOtherRole());
727  if ($curRelatives instanceof Node && $curRelatives->getOID() == $object->getOID()) {
728  return $curRelation;
729  }
730  elseif (is_array($curRelatives)) {
731  foreach ($curRelatives as $curRelative) {
732  if ($curRelative->getOID() == $object->getOID()) {
733  return $curRelation;
734  }
735  }
736  }
737  }
738  return null;
739  }
740 
741  /**
742  * Load all objects in the given set of relations
743  * @param $roles An array of relation (=role) names
744  * @param $buildDepth One of the BUILDDEPTH constants or a number describing the number of generations to build
745  * (default: _BuildDepth::SINGLE_)
746  */
747  protected function loadRelations(array $roles, $buildDepth=BuildDepth::SINGLE) {
748  $oldState = $this->getState();
749  foreach ($roles as $curRole) {
750  if (isset($this->relationStates[$curRole]) &&
751  $this->relationStates[$curRole] != self::RELATION_STATE_LOADED) {
752  $relatives = [];
753 
754  // resolve proxies if the relation is already initialized
755  if ($this->relationStates[$curRole] == self::RELATION_STATE_INITIALIZED) {
756  $proxies = $this->getValue($curRole);
757  if (is_array($proxies)) {
758  foreach ($proxies as $curRelative) {
759  if ($curRelative instanceof PersistentObjectProxy) {
760  // resolve proxies
761  $curRelative->resolve($buildDepth);
762  $relatives[] = $curRelative->getRealSubject();
763  }
764  else {
765  $relatives[] = $curRelative;
766  }
767  }
768  }
769  }
770  // otherwise load the objects directly
771  else {
772  $mapper = $this->getMapper();
773  $allRelatives = $mapper->loadRelation([$this], $curRole, $buildDepth);
774  $oidStr = $this->getOID()->__toString();
775  $relatives = isset($allRelatives[$oidStr]) ? $allRelatives[$oidStr] : null;
776  $relDesc = $mapper->getRelation($curRole);
777  if (!$relDesc->isMultiValued()) {
778  $relatives = $relatives != null ? $relatives[0] : null;
779  }
780  }
781  $this->setValueInternal($curRole, $relatives);
782  $this->relationStates[$curRole] = self::RELATION_STATE_LOADED;
783  }
784  }
785  $this->setState($oldState);
786  }
787 
788  /**
789  * Get the relation descriptions of a given hierarchyType.
790  * @param $hierarchyType @see PersistenceMapper::getRelations (default: 'all')
791  * @return An array containing the RelationDescription instances.
792  */
793  protected function getRelations($hierarchyType='all') {
794  return $this->getMapper()->getRelations($hierarchyType);
795  }
796 
797  /**
798  * Get the relatives of a given hierarchyType.
799  * @param $hierarchyType @see PersistenceMapper::getRelations
800  * @param $memOnly Boolean whether to only get the relatives in memory or all relatives (including proxies) (default: _true_).
801  * @return An array containing the relatives.
802  */
803  public function getRelatives($hierarchyType, $memOnly=true) {
804  $relatives = [];
805  $relations = $this->getRelations($hierarchyType);
806  foreach ($relations as $curRelation) {
807  $curRelatives = null;
808  if ($memOnly) {
809  $curRelatives = parent::getValue($curRelation->getOtherRole());
810  }
811  else {
812  $curRelatives = $this->getValue($curRelation->getOtherRole());
813  }
814  if (!$curRelatives) {
815  continue;
816  }
817  if (!is_array($curRelatives)) {
818  $curRelatives = [$curRelatives];
819  }
820  foreach ($curRelatives as $curRelative) {
821  if ($curRelative instanceof PersistentObjectProxy && $memOnly) {
822  // ignore proxies
823  continue;
824  }
825  else {
826  $relatives[] = $curRelative;
827  }
828  }
829  }
830  return $relatives;
831  }
832 
833  /**
834  * Get the number of relatives of a given hierarchyType.
835  * @param $hierarchyType @see PersistenceMapper::getRelations
836  * @param $memOnly Boolean whether to only get the number of the relatives in memory or all relatives (default: _true_).
837  * @return The number of relatives.
838  */
839  public function getNumRelatives($hierarchyType, $memOnly=true) {
840  return sizeof($this->getRelatives($hierarchyType, $memOnly));
841  }
842 
843  /**
844  * Accept a Visitor. For use with the _Visitor Pattern_.
845  * @param $visitor Visitor instance.
846  */
847  public function acceptVisitor($visitor) {
848  $visitor->visit($this);
849  }
850 
851  /**
852  * Add an uninitialized relation. The relation will be
853  * initialized (proxies for related objects will be added)
854  * on first access.
855  * @param $name The relation name (= role)
856  */
857  public function addRelation($name) {
858  if (!$this->hasValue($name)) {
859  $this->relationStates[$name] = self::RELATION_STATE_UNINITIALIZED;
860  $this->setValueInternal($name, null);
861  }
862  }
863 
864  /**
865  * Output
866  */
867 
868  /**
869  * @see PersistentObject::getDisplayValue()
870  * Delegates to NodeUtil::getDisplayValue
871  */
872  public function getDisplayValue() {
873  return NodeUtil::getDisplayValue($this);
874  }
875 
876  /**
877  * Get a string representation of the Node.
878  * @return The string representation of the Node.
879  */
880  public function __toString() {
881  $pStr = parent::__toString();
882  $str = $this->getDisplayValue();
883  if ($pStr != $str) {
884  return $this->getDisplayValue().' ['.$pStr.']';
885  }
886  return $str;
887  }
888 }
889 ?>
getValue($name)
Definition: Node.php:97
getType()
Get the type of the object.
getNumChildren($memOnly=true)
Get the number of children of the Node.
Definition: Node.php:505
getFirstChild($role=null, $type=null, $values=null, $properties=null, $useRegExp=true)
Get the first child that matches given conditions.
Definition: Node.php:519
Node related interfaces and classes.
Definition: namespaces.php:26
getParentsEx(ObjectId $oid=null, $role=null, $type=null, $values=null, $properties=null, $useRegExp=true)
Get the parents that match given conditions.
Definition: Node.php:677
const RELATION_STATE_UNINITIALIZED
Definition: Node.php:36
getOID()
Get the object id of the PersistentObject.
const RELATION_STATE_INITIALIZING
Definition: Node.php:37
getRelations($hierarchyType='all')
Get the relation descriptions of a given hierarchyType.
Definition: Node.php:793
getNumRelatives($hierarchyType, $memOnly=true)
Get the number of relatives of a given hierarchyType.
Definition: Node.php:839
addNode(PersistentObject $other, $role=null, $forceSet=false, $trackChange=true, $updateOtherSide=true)
Add a Node to the given relation.
Definition: Node.php:324
const RELATION_STATE_INITIALIZED
Definition: Node.php:38
getAddedNodes()
Get the object ids of the nodes that were added since the node was loaded.
Definition: Node.php:381
setValueInternal($name, $value)
Internal (fast) version to set a value without any validation, state change, listener notification et...
IllegalArgumentException signals an exception in method arguments.
static filter(array $nodeList, ObjectId $oid=null, $type=null, $values=null, $properties=null, $useRegExp=true)
Get Nodes that match given conditions from a list.
Definition: Node.php:185
ObjectId is the unique identifier of an object.
Definition: ObjectId.php:27
setValue($name, $value, $forceSet=false, $trackChange=true)
Definition: Node.php:125
static getLogger($name)
Get the logger with the given name.
Definition: LogManager.php:37
loadChildren($role=null, $buildDepth=BuildDepth::SINGLE)
Load the children of a given role and add them.
Definition: Node.php:491
const RELATION_STATE_LOADED
Definition: Node.php:39
getRelatives($hierarchyType, $memOnly=true)
Get the relatives of a given hierarchyType.
Definition: Node.php:803
getChildrenEx(ObjectId $oid=null, $role=null, $type=null, $values=null, $properties=null, $useRegExp=true)
Get the children that match given conditions.
Definition: Node.php:552
getRelationNames()
Get the names of all relations.
Definition: Node.php:305
static mergeObjectLists($list1, $list2)
Merge two object lists using the following rules:
Definition: Node.php:270
PersistentObjectProxy is proxy for an PersistentObject instance.
getParents($memOnly=true)
Get the Nodes parents.
Definition: Node.php:659
getPossibleChildren()
Get possible children of this node type (independent of existing children).
Definition: Node.php:584
setNodeOrder(array $orderedList, array $movedList=null, $role=null)
Define the order of related Node instances.
Definition: Node.php:466
mergeValues(PersistentObject $object)
Definition: Node.php:242
deleteNode(PersistentObject $other, $role=null, $updateOtherSide=true)
Delete a Node from the given relation.
Definition: Node.php:391
getNodeRelation($object)
Get the relation description for a given node.
Definition: Node.php:723
getFirstParent($role=null, $type=null, $values=null, $properties=null, $useRegExp=true)
Get the first parent that matches given conditions.
Definition: Node.php:643
getParent()
Get the Node's parent.
Definition: Node.php:623
static getInstance($name, $dynamicConfiguration=[])
addRelation($name)
Add an uninitialized relation.
Definition: Node.php:857
acceptVisitor($visitor)
Accept a Visitor.
Definition: Node.php:847
removeValue($name)
Definition: Node.php:155
getChildren($memOnly=true)
Get the Node's children.
Definition: Node.php:534
getNodeOrder()
Get the order of related Node instances, if it was defined using the Node::setNodeOrder() method...
Definition: Node.php:480
getIndispensableObjects()
Definition: Node.php:167
static getDump($variable, $strlen=100, $width=25, $depth=10, $i=0, &$objects=[])
Get the dump of a variable as string.
Definition: StringUtil.php:29
__construct(ObjectId $oid=null, array $initialData=null)
Constructor.
Definition: Node.php:55
static getDisplayValue(Node $node, $language=null)
Get the display value for a Node defined by the 'displayValues' property.
Definition: NodeUtil.php:152
DefaultPersistentObject is the base class of all persistent objects.
getNumParents($memOnly=true)
Get the number of parents of the Node.
Definition: Node.php:614
getDisplayValue()
Output.
Definition: Node.php:872
getValueNames($excludeTransient=false)
Definition: Node.php:81
getPossibleParents()
Get possible parents of this node type (independent of existing parents).
Definition: Node.php:709
__toString()
Get a string representation of the Node.
Definition: Node.php:880
getDeletedNodes()
Get the object ids of the nodes that were deleted since the node was loaded.
Definition: Node.php:451
loadParents($role=null, $buildDepth=BuildDepth::SINGLE)
Load the parents of a given role and add them.
Definition: Node.php:600
loadRelations(array $roles, $buildDepth=BuildDepth::SINGLE)
Load all objects in the given set of relations.
Definition: Node.php:747
Node adds the concept of relations to PersistentObject.
Definition: Node.php:34
static getStackTrace()
Get the stack trace.
PersistentObject defines the interface of all persistent objects.