1<?php declare(strict_types=1);
2
3namespace DOMWrap\Collections;
4
5/**
6 * Node List
7 *
8 * @package DOMWrap\Collections
9 * @license http://opensource.org/licenses/BSD-3-Clause BSD 3 Clause
10 */
11class NodeCollection implements \Countable, \ArrayAccess, \RecursiveIterator
12{
13    /** @var array */
14    protected $nodes = [];
15
16    /**
17     * @param iterable $nodes
18     */
19    public function __construct(iterable $nodes = null) {
20        if (!is_iterable($nodes)) {
21            $nodes = [];
22        }
23
24        foreach ($nodes as $node) {
25            $this->nodes[] = $node;
26        }
27    }
28
29    /**
30     * @see \Countable::count()
31     *
32     * @return int
33     */
34    public function count(): int {
35        return count($this->nodes);
36    }
37
38    /**
39     * @see \ArrayAccess::offsetExists()
40     *
41     * @param mixed $offset
42     *
43     * @return bool
44     */
45    public function offsetExists($offset): bool {
46        return isset($this->nodes[$offset]);
47    }
48
49    /**
50     * @see \ArrayAccess::offsetGet()
51     *
52     * @param mixed $offset
53     *
54     * @return mixed
55     */
56    #[\ReturnTypeWillChange]
57    public function offsetGet($offset) {
58        return isset($this->nodes[$offset]) ? $this->nodes[$offset] : null;
59    }
60
61    /**
62     * @see \ArrayAccess::offsetSet()
63     *
64     * @param mixed $offset
65     * @param mixed $value
66     */
67    public function offsetSet($offset, $value): void {
68        if (is_null($offset)) {
69            $this->nodes[] = $value;
70        } else {
71            $this->nodes[$offset] = $value;
72        }
73    }
74
75    /**
76     * @see \ArrayAccess::offsetUnset()
77     *
78     * @param mixed $offset
79     */
80    public function offsetUnset($offset): void {
81        unset($this->nodes[$offset]);
82    }
83
84    /**
85     * @see \RecursiveIterator::RecursiveIteratorIterator()
86     *
87     * @return \RecursiveIteratorIterator
88     */
89    public function getRecursiveIterator(): \RecursiveIteratorIterator {
90        return new \RecursiveIteratorIterator($this, \RecursiveIteratorIterator::SELF_FIRST);
91    }
92
93    /**
94     * @see \RecursiveIterator::getChildren()
95     *
96     * @return \RecursiveIterator
97     */
98    public function getChildren(): \RecursiveIterator {
99        $nodes = [];
100
101        if ($this->valid()) {
102            $nodes = $this->current()->childNodes;
103        }
104
105        return new static($nodes);
106    }
107
108    /**
109     * @see \RecursiveIterator::hasChildren()
110     *
111     * @return bool
112     */
113    public function hasChildren(): bool {
114        if ($this->valid()) {
115            return $this->current()->hasChildNodes();
116        }
117
118        return false;
119    }
120
121    /**
122     * @see \RecursiveIterator::current()
123     * @see \Iterator::current()
124     *
125     * @return mixed
126     */
127    #[\ReturnTypeWillChange]
128    public function current() {
129        return current($this->nodes);
130    }
131
132    /**
133     * @see \RecursiveIterator::key()
134     * @see \Iterator::key()
135     *
136     * @return mixed
137     */
138    #[\ReturnTypeWillChange]
139    public function key() {
140        return key($this->nodes);
141    }
142
143    /**
144     * @see \RecursiveIterator::next()
145     * @see \Iterator::next()
146     *
147     * @return mixed
148     */
149    #[\ReturnTypeWillChange]
150    public function next() {
151        return next($this->nodes);
152    }
153
154    /**
155     * @see \RecursiveIterator::rewind()
156     * @see \Iterator::rewind()
157     *
158     * @return mixed
159     */
160    #[\ReturnTypeWillChange]
161    public function rewind() {
162        return reset($this->nodes);
163    }
164
165    /**
166     * @see \RecursiveIterator::valid()
167     * @see \Iterator::valid()
168     *
169     * @return bool
170     */
171    public function valid(): bool {
172        return key($this->nodes) !== null;
173    }
174}