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