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