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