Overview

Namespaces

  • Pharborist
    • Constants
    • ControlStructures
    • Exceptions
    • Functions
    • Generators
    • Namespaces
    • Objects
    • Operators
    • Types
    • Variables

Classes

  • Pharborist\Variables\CompoundVariableNode
  • Pharborist\Variables\GlobalStatementNode
  • Pharborist\Variables\ReferenceVariableNode
  • Pharborist\Variables\StaticVariableNode
  • Pharborist\Variables\StaticVariableStatementNode
  • Pharborist\Variables\VariableNode
  • Pharborist\Variables\VariableVariableNode

Interfaces

  • Pharborist\Variables\VariableExpressionNode
  • Overview
  • Namespace
  • Class
  1: <?php
  2: namespace Pharborist;
  3: 
  4: /**
  5:  * A set of matched nodes.
  6:  *
  7:  * jQuery like wrapper around Node[] to support Traversing and Manipulation.
  8:  */
  9: class NodeCollection implements \IteratorAggregate, \Countable, \ArrayAccess {
 10:   /**
 11:    * @var Node[]
 12:    */
 13:   protected $nodes;
 14: 
 15:   /**
 16:    * Sort nodes and remove duplicates
 17:    * @param Node[] $nodes
 18:    * @return Node[]
 19:    */
 20:   protected static function sortUnique($nodes) {
 21:     $sort = [];
 22:     $detached = [];
 23:     foreach ($nodes as $node) {
 24:       $key = $node->sortKey();
 25:       if ($key[0] === '~') {
 26:         $detached[] = $node;
 27:       }
 28:       else {
 29:         $sort[$key] = $node;
 30:       }
 31:     }
 32:     ksort($sort, SORT_NATURAL);
 33:     return array_merge(array_values($sort), $detached);
 34:   }
 35: 
 36:   public function __construct($nodes = [], $sort = TRUE) {
 37:     $this->nodes = $sort ? static::sortUnique($nodes) : $nodes;
 38:   }
 39: 
 40:   /**
 41:    * Implements \IteratorAggregate::getIterator().
 42:    */
 43:   public function getIterator() {
 44:     return new \ArrayIterator($this->nodes);
 45:   }
 46: 
 47:   /**
 48:    * Implements \Countable::count().
 49:    */
 50:   public function count() {
 51:     return count($this->nodes);
 52:   }
 53: 
 54:   /**
 55:    * Implements \ArrayAccess::offsetExists().
 56:    *
 57:    * @param integer $offset
 58:    *
 59:    * @return boolean
 60:    */
 61:   public function offsetExists($offset) {
 62:     return isset($this->nodes[$offset]);
 63:   }
 64: 
 65:   /**
 66:    * Implements \ArrayAccess::offsetGet().
 67:    *
 68:    * @param integer $offset
 69:    *
 70:    * @return Node
 71:    */
 72:   public function offsetGet($offset) {
 73:     return $this->nodes[$offset];
 74:   }
 75: 
 76:   /**
 77:    * Implements \ArrayAccess::offsetSet().
 78:    *
 79:    * @param integer $offset
 80:    * @param Node $value
 81:    *
 82:    * @throws \BadMethodCallException
 83:    */
 84:   public function offsetSet($offset, $value) {
 85:     throw new \BadMethodCallException('NodeCollection offsetSet not supported');
 86:   }
 87: 
 88:   /**
 89:    * Implements \ArrayAccess::offsetUnset().
 90:    *
 91:    * @param integer $offset
 92:    *
 93:    * @throws \BadMethodCallException
 94:    */
 95:   public function offsetUnset($offset) {
 96:     throw new \BadMethodCallException('NodeCollection offsetUnset not supported');
 97:   }
 98: 
 99: 
100:   /**
101:    * Returns if the collection is empty.
102:    *
103:    * @return boolean
104:    */
105:   public function isEmpty() {
106:     return $this->count() == 0;
107:   }
108: 
109:   /**
110:    * Returns if the collection is not empty.
111:    *
112:    * @return boolean
113:    */
114:   public function isNotEmpty() {
115:     return $this->count() > 0;
116:   }
117: 
118:   /**
119:    * Get collection in reverse order
120:    * @return Node[]
121:    */
122:   public function reverse() {
123:     return array_reverse($this->nodes);
124:   }
125: 
126:   /**
127:    * Reduce the set of matched nodes to a subset specified by a range.
128:    * @param int $start_index
129:    * @param int $end_index
130:    * @return NodeCollection
131:    */
132:   public function slice($start_index, $end_index = NULL) {
133:     if ($start_index < 0) {
134:       $start_index = $this->count() + $start_index;
135:     }
136:     if ($end_index < 0) {
137:       $end_index = $this->count() + $end_index;
138:     }
139:     $last_index = $this->count() - 1;
140:     if ($start_index > $last_index) {
141:       $start_index = $last_index;
142:     }
143:     if ($end_index !== NULL) {
144:       if ($end_index > $last_index) {
145:         $end_index = $last_index;
146:       }
147:       if ($start_index > $end_index) {
148:         $start_index = $end_index;
149:       }
150:       $length = $end_index - $start_index;
151:     }
152:     else {
153:       $length = $this->count() - $start_index;
154:     }
155:     return new NodeCollection(array_slice($this->nodes, $start_index, $length));
156:   }
157: 
158:   /**
159:    * Creates a new collection by applying a callback to each node in the matched
160:    * set, like jQuery's .map().
161:    *
162:    * @param callable $callback
163:    *  The callback to apply, receiving the current node in the set.
164:    *
165:    * @return NodeCollection
166:    */
167:   public function map(callable $callback) {
168:     return new NodeCollection(array_map($callback, $this->nodes));
169:   }
170: 
171:   /**
172:    * Iteratively reduce the collection to a single value using a callback.
173:    *
174:    * @param callable $callback
175:    *   Callback function that receives the return value of the previous
176:    *   iteration and the current node in the set being processed.
177:    * @param mixed $initial
178:    *   The initial value for first iteration, or final result in case
179:    *   of empty collection.
180:    *
181:    * @return mixed
182:    *   Returns the resulting value.
183:    */
184:   public function reduce(callable $callback, $initial = NULL) {
185:     return array_reduce($this->nodes, $callback, $initial);
186:   }
187: 
188:   /**
189:    * Returns the raw array of nodes, like jQuery's get() called with no
190:    * arguments.
191:    *
192:    * @return Node[]
193:    */
194:   public function toArray() {
195:     return $this->nodes;
196:   }
197: 
198:   /**
199:    * Get the element at index.
200:    *
201:    * @param int $index
202:    *   Index of element to get.
203:    *
204:    * @return Node
205:    */
206:   public function get($index) {
207:     return $this->nodes[$index];
208:   }
209: 
210:   /**
211:    * Index of first element that is matched by callback.
212:    *
213:    * @param callable $callback
214:    *   Callback to test for node match.
215:    *
216:    * @return int
217:    *   Index of first element that is matched by callback.
218:    */
219:   public function indexOf(callable $callback) {
220:     foreach ($this->nodes as $i => $node) {
221:       if ($callback($node)) {
222:         return $i;
223:       }
224:     }
225:     return -1;
226:   }
227: 
228:   /**
229:    * Test is any element in collection matches.
230:    *
231:    * @param callable $callback
232:    *   Callback to test for node match.
233:    *
234:    * @return boolean
235:    *   TRUE if any element in set of nodes matches.
236:    */
237:   public function is(callable $callback) {
238:     foreach ($this->nodes as $node) {
239:       if ($callback($node)) {
240:         return TRUE;
241:       }
242:     }
243:     return FALSE;
244:   }
245: 
246:   /**
247:    * Get the parent of each node in the current set of matched nodes,
248:    * optionally filtered by a callback.
249:    * @param callable $callback An optional callback to filter by.
250:    * @return NodeCollection
251:    */
252:   public function parent(callable $callback = NULL) {
253:     $matches = [];
254:     foreach ($this->nodes as $node) {
255:       if ($match = $node->parent($callback)) {
256:         $matches[] = $match;
257:       }
258:     }
259:     return new NodeCollection($matches);
260:   }
261: 
262:   /**
263:    * Get the ancestors of each node in the current set of matched nodes,
264:    * optionally filtered by a callback.
265:    * @param callable $callback An optional callback to filter by.
266:    * @return NodeCollection
267:    */
268:   public function parents(callable $callback = NULL) {
269:     $matches = [];
270:     foreach ($this->nodes as $node) {
271:       $matches = array_merge($matches, $node->parents($callback)->nodes);
272:     }
273:     return new NodeCollection($matches);
274:   }
275: 
276:   /**
277:    * Get ancestors of each node in the current set of matched nodes,
278:    * up to the node matched by callback.
279:    * @param callable $callback Callback to test for match.
280:    * @param bool $inclusive TRUE to include the node matched by callback.
281:    * @return NodeCollection
282:    */
283:   public function parentsUntil(callable $callback, $inclusive = FALSE) {
284:     $matches = [];
285:     foreach ($this->nodes as $node) {
286:       $matches = array_merge($matches, $node->parentsUntil($callback, $inclusive)->nodes);
287:     }
288:     return new NodeCollection($matches);
289:   }
290: 
291:   /**
292:    * For each node in the collection, get the first node matched by the
293:    * callback by testing this node and traversing up through its ancestors in
294:    * the tree.
295:    * @param callable $callback Callback to test for match.
296:    * @return Node
297:    */
298:   public function closest(callable $callback) {
299:     $matches = [];
300:     foreach ($this->nodes as $node) {
301:       if ($match = $node->closest($callback)) {
302:         $matches[] = $match;
303:       }
304:     }
305:     return new NodeCollection($matches);
306:   }
307: 
308:   /**
309:    * Get the immediate children of each node in the set of matched nodes.
310:    * @param callable $callback An optional callback to filter by.
311:    * @return NodeCollection
312:    */
313:   public function children(callable $callback = NULL) {
314:     $matches = [];
315:     foreach ($this->nodes as $node) {
316:       if ($node instanceof ParentNode) {
317:         $matches = array_merge($matches, $node->children($callback)->nodes);
318:       }
319:     }
320:     return new NodeCollection($matches);
321:   }
322: 
323:   /**
324:    * Remove all child nodes of the set of matched elements.
325:    *
326:    * @return $this
327:    */
328:   public function clear() {
329:     foreach ($this->nodes as $node) {
330:       if ($node instanceof ParentNode) {
331:         $node->clear();
332:       }
333:     }
334:     return $this;
335:   }
336: 
337:   /**
338:    * Get the immediately preceding sibling of each node in the set of matched
339:    * nodes. If a callback is provided, it retrieves the next sibling only if
340:    * the callback returns TRUE.
341:    * @param callable $callback An optional callback to filter by.
342:    * @return NodeCollection
343:    */
344:   public function previous(callable $callback = NULL) {
345:     $matches = [];
346:     foreach ($this->nodes as $node) {
347:       if ($match = $node->previous($callback)) {
348:         $matches[] = $match;
349:       }
350:     }
351:     return new NodeCollection($matches);
352:   }
353: 
354:   /**
355:    * Get all preceding siblings of each node in the set of matched nodes,
356:    * optionally filtered by a callback.
357:    * @param callable $callback An optional callback to filter by.
358:    * @return NodeCollection
359:    */
360:   public function previousAll(callable $callback = NULL) {
361:     $matches = [];
362:     foreach ($this->nodes as $node) {
363:       $matches = array_merge($matches, $node->previousAll($callback)->nodes);
364:     }
365:     return new NodeCollection($matches);
366:   }
367: 
368:   /**
369:    * Get all preceding siblings of each node up to the node matched by the
370:    * callback.
371:    * @param callable $callback Callback to test for match.
372:    * @param bool $inclusive TRUE to include the node matched by callback.
373:    * @return NodeCollection
374:    */
375:   public function previousUntil(callable $callback, $inclusive = FALSE) {
376:     $matches = [];
377:     foreach ($this->nodes as $node) {
378:       $matches = array_merge($matches, $node->previousUntil($callback, $inclusive)->nodes);
379:     }
380:     return new NodeCollection($matches);
381:   }
382: 
383:   /**
384:    * Get the immediately following sibling of each node in the set of matched
385:    * nodes. If a callback is provided, it retrieves the next sibling only if
386:    * the callback returns TRUE.
387:    * @param callable $callback An optional callback to filter by.
388:    * @return NodeCollection
389:    */
390:   public function next(callable $callback = NULL) {
391:     $matches = [];
392:     foreach ($this->nodes as $node) {
393:       if ($match = $node->next($callback)) {
394:         $matches[] = $match;
395:       }
396:     }
397:     return new NodeCollection($matches);
398:   }
399: 
400:   /**
401:    * Get all following siblings of each node in the set of matched nodes,
402:    * optionally filtered by a callback.
403:    * @param callable $callback An optional callback to filter by.
404:    * @return NodeCollection
405:    */
406:   public function nextAll(callable $callback = NULL) {
407:     $matches = [];
408:     foreach ($this->nodes as $node) {
409:       $matches = array_merge($matches, $node->nextAll($callback)->nodes);
410:     }
411:     return new NodeCollection($matches);
412:   }
413: 
414:   /**
415:    * Get all following siblings of each node up to the node matched by the
416:    * callback.
417:    * @param callable $callback Callback to test for match.
418:    * @param bool $inclusive TRUE to include the node matched by callback.
419:    * @return NodeCollection
420:    */
421:   public function nextUntil(callable $callback, $inclusive = FALSE) {
422:     $matches = [];
423:     foreach ($this->nodes as $node) {
424:       $matches = array_merge($matches, $node->nextUntil($callback, $inclusive)->nodes);
425:     }
426:     return new NodeCollection($matches);
427:   }
428: 
429:   /**
430:    * Get the siblings of each node in the set of matched nodes,
431:    * optionally filtered by a callback.
432:    * @param callable $callback An optional callback to filter by.
433:    * @return NodeCollection
434:    */
435:   public function siblings(callable $callback = NULL) {
436:     $matches = [];
437:     foreach ($this->nodes as $node) {
438:       $matches = array_merge($matches, $node->siblings($callback)->nodes);
439:     }
440:     return new NodeCollection($matches);
441:   }
442: 
443:   /**
444:    * Get the descendants of each node in the current set of matched nodes,
445:    * filtered by callback.
446:    * @param callable $callback Callback to filter by.
447:    * @return NodeCollection
448:    */
449:   public function find(callable $callback) {
450:     $matches = [];
451:     foreach ($this->nodes as $node) {
452:       if ($node instanceof ParentNode) {
453:         $matches = array_merge($matches, $node->find($callback)->nodes);
454:       }
455:     }
456:     return new NodeCollection($matches);
457:   }
458: 
459:   /**
460:    * Reduce the set of matched nodes to those that pass the callback filter.
461:    * @param callable $callback Callback to test for match.
462:    * @return NodeCollection
463:    */
464:   public function filter(callable $callback) {
465:     $matches = [];
466:     foreach ($this->nodes as $index => $node) {
467:       if ($callback($node, $index)) {
468:         $matches[] = $node;
469:       }
470:     }
471:     return new NodeCollection($matches, FALSE);
472:   }
473: 
474:   /**
475:    * Remove nodes from the set of matched nodes.
476:    * @param callable $callback Callback to test for match.
477:    * @return NodeCollection
478:    */
479:   public function not(callable $callback) {
480:     $matches = [];
481:     foreach ($this->nodes as $index => $node) {
482:       if (!$callback($node, $index)) {
483:         $matches[] = $node;
484:       }
485:     }
486:     return new NodeCollection($matches, FALSE);
487:   }
488: 
489:   /**
490:    * Reduce the set of matched nodes to those that have a descendant that
491:    * match.
492:    * @param callable $callback Callback to test for match.
493:    * @return NodeCollection
494:    */
495:   public function has(callable $callback) {
496:     $matches = [];
497:     foreach ($this->nodes as $node) {
498:       if ($node instanceof ParentNode && $node->has($callback)) {
499:         $matches[] = $node;
500:       }
501:     }
502:     return new NodeCollection($matches, FALSE);
503:   }
504: 
505:   /**
506:    * Reduce the set of matched nodes to the first in the set.
507:    */
508:   public function first() {
509:     $matches = [];
510:     if (!empty($this->nodes)) {
511:       $matches[] = $this->nodes[0];
512:     }
513:     return new NodeCollection($matches, FALSE);
514:   }
515: 
516:   /**
517:    * Reduce the set of matched nodes to the last in the set.
518:    */
519:   public function last() {
520:     $matches = [];
521:     if (!empty($this->nodes)) {
522:       $matches[] = $this->nodes[count($this->nodes) - 1];
523:     }
524:     return new NodeCollection($matches, FALSE);
525:   }
526: 
527:   /**
528:    * Insert every node in the set of matched nodes before the targets.
529:    * @param Node|Node[]|NodeCollection $targets Nodes to insert before.
530:    * @return $this
531:    */
532:   public function insertBefore($targets) {
533:     foreach ($this->nodes as $node) {
534:       $node->insertBefore($targets);
535:     }
536:     return $this;
537:   }
538: 
539:   /**
540:    * Insert nodes before each node in the set of matched nodes.
541:    * @param Node|Node[]|NodeCollection $nodes Nodes to insert.
542:    * @return $this
543:    */
544:   public function before($nodes) {
545:     foreach ($this->nodes as $i => $node) {
546:       $node->before($i === 0 ? $nodes : clone $nodes);
547:     }
548:     return $this;
549:   }
550: 
551:   /**
552:    * Insert every node in the set of matched nodes after the targets.
553:    * @param Node|Node[]|NodeCollection $targets Nodes to insert after.
554:    * @return $this
555:    */
556:   public function insertAfter($targets) {
557:     foreach ($this->nodes as $node) {
558:       $node->insertAfter($targets);
559:     }
560:     return $this;
561:   }
562: 
563:   /**
564:    * Insert nodes after each node in the set of matched nodes.
565:    * @param Node|Node[]|NodeCollection $nodes Nodes to insert.
566:    * @return $this
567:    */
568:   public function after($nodes) {
569:     foreach ($this->nodes as $i => $node) {
570:       $node->after($i === 0 ? $nodes : clone $nodes);
571:     }
572:     return $this;
573:   }
574: 
575:   /**
576:    * Remove the set of matched nodes from the tree.
577:    * @return $this
578:    */
579:   public function remove() {
580:     foreach ($this->nodes as $node) {
581:       $node->remove();
582:     }
583:     return $this;
584:   }
585: 
586:   /**
587:    * Replace each node in the set of matched nodes with the provided new nodes
588:    * and return the set of nodes that was removed.
589:    * @param Node|Node[]|NodeCollection $nodes Replacement nodes.
590:    * @return $this
591:    */
592:   public function replaceWith($nodes) {
593:     $first = TRUE;
594:     foreach ($this->nodes as $node) {
595:       if (!$first) {
596:         if (is_array($nodes)) {
597:           $nodes = new NodeCollection($nodes, FALSE);
598:         }
599:         $nodes = clone $nodes;
600:       }
601:       $node->replaceWith($nodes);
602:       $first = FALSE;
603:     }
604:     return $this;
605:   }
606: 
607:   /**
608:    * Replace each target node with the set of matched nodes.
609:    * @param Node|Node[]|NodeCollection $targets Targets to replace.
610:    * @return $this
611:    */
612:   public function replaceAll($targets) {
613:     if ($targets instanceof Node) {
614:       $targets->replaceWith($this->nodes);
615:     }
616:     elseif ($targets instanceof NodeCollection || is_array($targets)) {
617:       $first = TRUE;
618:       /** @var Node $target */
619:       foreach ($targets as $target) {
620:         $target->replaceWith($first ? $this->nodes : clone $this);
621:         $first = FALSE;
622:       }
623:     }
624:     return $this;
625:   }
626: 
627:   /**
628:    * Add nodes to this collection.
629:    * @param Node|Node[]|NodeCollection $nodes Nodes to add to collection.
630:    * @return $this
631:    * @throws \InvalidArgumentException
632:    */
633:   public function add($nodes) {
634:     if ($nodes instanceof Node) {
635:       $this->nodes[] = $nodes;
636:     }
637:     elseif ($nodes instanceof NodeCollection) {
638:       $this->nodes = array_merge($this->nodes, $nodes->nodes);
639:     }
640:     elseif (is_array($nodes)) {
641:       $this->nodes = array_merge($this->nodes, $nodes);
642:     }
643:     else {
644:       throw new \InvalidArgumentException();
645:     }
646:     $this->nodes = static::sortUnique($this->nodes);
647:     return $this;
648:   }
649: 
650:   /**
651:    * Merges the current collection with another one, and returns the other one.
652:    *
653:    * @param static $collection
654:    *  The destination collection.
655:    *
656:    * @return static
657:    */
658:   public function addTo(NodeCollection $collection) {
659:     return $collection->add($this);
660:   }
661: 
662:   /**
663:    * Apply callback on each element in the node collection.
664:    *
665:    * @param callable $callback Callback to apply on each element.
666:    * @return $this
667:    */
668:   public function each(callable $callback) {
669:     array_walk($this->nodes, $callback);
670:     return $this;
671:   }
672: 
673:   public function __clone() {
674:     $copy = [];
675:     foreach ($this->nodes as $node) {
676:       $copy[] = clone $node;
677:     }
678:     $this->nodes = $copy;
679:   }
680: }
681: 
Pharborist API documentation generated by ApiGen