2323 */
2424abstract class AbstractModels implements \Iterator, \ArrayAccess, \Countable, \JsonSerializable
2525{
26- /** @var \DCarbone\PHPConsulAPI\AbstractModel[] */
27- protected array $ _list = [];
28-
2926 /** @var string */
3027 protected string $ containedClass ;
3128
29+ /** @var \DCarbone\PHPConsulAPI\AbstractModel[] */
30+ private array $ _list = [];
31+
32+ /** @var int */
33+ private int $ _size = 0 ;
34+
3235 /**
3336 * AbstractModels constructor.
3437 * @param array|null $children
3538 */
3639 public function __construct (?array $ children = [])
3740 {
41+ if (!isset ($ this ->containedClass )) {
42+ throw new \DomainException (
43+ \sprintf (
44+ 'Class "%s" must define $containedClass ' ,
45+ \get_called_class ()
46+ )
47+ );
48+ }
3849 if (null === $ children ) {
3950 return ;
4051 }
41- foreach (\array_filter ($ children ) as $ child ) {
42- if (\is_array ($ child )) {
43- $ this ->_list [] = $ this ->newChild ($ child );
44- } elseif ($ child instanceof $ this ->containedClass ) {
45- $ this ->_list [] = $ child ;
46- } else {
47- throw new \InvalidArgumentException (
48- \sprintf (
49- \get_class ($ this ) . ' accepts only ' . $ this ->containedClass . ' as a child, saw %s ' ,
50- \is_object ($ child ) ? \get_class ($ child ) : \gettype ($ child )
51- )
52- );
53- }
52+ foreach ($ children as $ child ) {
53+ $ this ->append ($ child );
5454 }
5555 }
5656
5757 /**
5858 * @param \DCarbone\PHPConsulAPI\AbstractModel|null $value
5959 */
60- public function append (AbstractModel $ value = null ): void
60+ public function append (? AbstractModel $ value ): void
6161 {
62- if (null === $ value || $ value instanceof $ this ->containedClass ) {
63- $ this ->_list [] = $ value ;
64- } else {
65- throw new \InvalidArgumentException (
66- \sprintf (
67- '%s accepts only objects of type %s or null as values ' ,
68- \get_class ($ this ),
69- $ this ->containedClass ,
70- )
71- );
72- }
62+ // validate provided value is either null or instance of allowed child class
63+ $ value = $ this ->_validateValue ($ value );
64+
65+ // set offset to current value of _size, and iterate size by 1
66+ $ offset = $ this ->_size ++;
67+
68+ // if value is passed, clone then set.
69+ $ this ->_list [$ offset ] = $ value ;
7370 }
7471
7572 /**
76- * @return \DCarbone\PHPConsulAPI\AbstractModel|mixed
73+ * @return \DCarbone\PHPConsulAPI\AbstractModel|false
7774 */
7875 public function current ()
7976 {
@@ -96,7 +93,7 @@ public function key()
9693 /**
9794 * @return bool
9895 */
99- public function valid ()
96+ public function valid (): bool
10097 {
10198 return null !== \key ($ this ->_list );
10299 }
@@ -115,17 +112,18 @@ public function offsetExists($offset)
115112 return \is_int ($ offset ) && isset ($ this ->_list [$ offset ]);
116113 }
117114
115+ /**
116+ * @param mixed $offset
117+ * @return \DCarbone\PHPConsulAPI\AbstractModel|null
118+ */
118119 public function offsetGet ($ offset )
119120 {
120- if (\is_int ($ offset ) && isset ($ this ->_list [$ offset ])) {
121+ $ this ->_validateOffset ($ offset );
122+ if (isset ($ this ->_list [$ offset ])) {
121123 return $ this ->_list [$ offset ];
122124 }
123- throw new \OutOfRangeException (
124- \sprintf (
125- 'Offset %s does not exist in this list ' ,
126- \is_int ($ offset ) ? (string ) $ offset : \gettype ($ offset )
127- )
128- );
125+
126+ return $ this ->_list [$ offset ];
129127 }
130128
131129 /**
@@ -134,42 +132,108 @@ public function offsetGet($offset)
134132 */
135133 public function offsetSet ($ offset , $ value ): void
136134 {
137- if (!\is_int ($ offset )) {
138- throw new \InvalidArgumentException ('Offset must be int ' );
139- }
140- if (null !== $ value && !($ value instanceof $ this ->containedClass )) {
141- throw new \InvalidArgumentException ('Value must be instance of ' . $ this ->containedClass );
135+ // if incoming offset is null, assume [] (append) operation.
136+ if (null === $ offset ) {
137+ $ this ->append ($ value );
138+ return ;
142139 }
143- $ this ->_list [$ offset ] = $ value ;
140+
141+ // validate provided offset value
142+ $ this ->_validateOffset ($ offset );
143+
144+ // validate value input and set
145+ $ this ->_list [$ offset ] = $ this ->_validateValue ($ value );
144146 }
145147
146148 /**
147149 * @param mixed $offset
148150 */
149151 public function offsetUnset ($ offset ): void
150152 {
151- unset($ this ->_list [$ offset ]);
153+ // validate provided offset value
154+ $ this ->_validateOffset ($ offset );
155+
156+ // null out value in list
157+ $ this ->_list [$ offset ] = null ;
152158 }
153159
154160 /**
155161 * @return int
156162 */
157163 public function count (): int
158164 {
159- return \count ( $ this ->_list ) ;
165+ return $ this ->_size ;
160166 }
161167
162168 /**
163169 * @return \DCarbone\PHPConsulAPI\AbstractModel[]
164170 */
165171 public function jsonSerialize (): array
166172 {
167- return \array_filter ((array ) $ this ->_list );
173+ $ out = [];
174+ foreach ($ this ->_list as $ i => $ item ) {
175+ if (null === $ item ) {
176+ $ out [$ i ] = null ;
177+ } else {
178+ $ out [$ i ] = clone $ item ;
179+ }
180+ }
181+ return $ out ;
168182 }
169183
170184 /**
171185 * @param array $data
172- * @return \static
186+ * @return \DCarbone\PHPConsulAPI\AbstractModel
173187 */
174188 abstract protected function newChild (array $ data ): AbstractModel ;
189+
190+ /**
191+ * @param mixed $offset
192+ */
193+ private function _validateOffset ($ offset ): void
194+ {
195+ if (!\is_int ($ offset )) {
196+ throw new \InvalidArgumentException (
197+ \sprintf (
198+ 'Cannot use offset of type "%s" with "%s" ' ,
199+ \gettype ($ offset ),
200+ \get_class ($ this )
201+ )
202+ );
203+ }
204+ if (0 > $ offset || $ offset >= $ this ->_size ) {
205+ throw new \OutOfRangeException (\sprintf ('Offset %d does not exist in this list ' , $ offset ));
206+ }
207+ }
208+
209+ /**
210+ * @param mixed $value
211+ * @return \DCarbone\PHPConsulAPI\AbstractModel|null
212+ */
213+ private function _validateValue ($ value ): ?AbstractModel
214+ {
215+ // fast path for null values
216+ if (null === $ value ) {
217+ return null ;
218+ }
219+
220+ // if instance of contained class, clone and move on
221+ if ($ value instanceof $ this ->containedClass ) {
222+ return clone $ value ;
223+ }
224+
225+ // if array, construct new child
226+ if (\is_array ($ value )) {
227+ return $ this ->newChild ($ value );
228+ }
229+
230+ // if we make it down here, fail.
231+ throw new \InvalidArgumentException (
232+ \sprintf (
233+ '%s accepts only objects of type %s, null, or associative array definition as values ' ,
234+ \get_class ($ this ),
235+ $ this ->containedClass ,
236+ )
237+ );
238+ }
175239}
0 commit comments