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