Passed
Push — php8 ( bd75dc...d8afb4 )
by Fabio
08:15
created

TList::_getZappableSleepProps()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 6
c 0
b 0
f 0
nc 4
nop 1
dl 0
loc 9
rs 10
1
<?php
2
/**
3
 * TList class
4
 *
5
 * @author Qiang Xue <[email protected]>
6
 * @link https://github.com/pradosoft/prado
7
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
8
 */
9
10
namespace Prado\Collections;
11
12
use Prado\Exceptions\TInvalidOperationException;
13
use Prado\Exceptions\TInvalidDataTypeException;
14
use Prado\Exceptions\TInvalidDataValueException;
15
use Prado\TPropertyValue;
16
17
/**
18
 * TList class
19
 *
20
 * TList implements an integer-indexed collection class.
21
 *
22
 * You can access, append, insert, remove an item by using
23
 * {@link itemAt}, {@link add}, {@link insertAt}, {@link remove}, and {@link removeAt}.
24
 * To get the number of the items in the list, use {@link getCount}.
25
 * TList can also be used like a regular array as follows,
26
 * <code>
27
 * $list[]=$item;  // append at the end
28
 * $list[$index]=$item; // $index must be between 0 and $list->Count
29
 * unset($list[$index]); // remove the item at $index
30
 * if(isset($list[$index])) // if the list has an item at $index
31
 * foreach($list as $index=>$item) // traverse each item in the list
32
 * $n=count($list); // returns the number of items in the list
33
 * </code>
34
 *
35
 * To extend TList by doing additional operations with each addition or removal
36
 * operation, override {@link insertAt()}, and {@link removeAt()}.
37
 *
38
 * @author Qiang Xue <[email protected]>
39
 * @since 3.0
40
 */
41
class TList extends \Prado\TComponent implements \IteratorAggregate, \ArrayAccess, \Countable
42
{
43
	/**
44
	 * internal data storage
45
	 * @var array
46
	 */
47
	protected array $_d = [];
48
	/**
49
	 * number of items
50
	 * @var int
51
	 */
52
	protected int $_c = 0;
53
	/**
54
	 * @var bool whether this list is read-only
55
	 */
56
	private bool $_r = false;
57
58
	/**
59
	 * Constructor.
60
	 * Initializes the list with an array or an iterable object.
61
	 * @param null|array|\Iterator $data the initial data. Default is null, meaning no initialization.
62
	 * @param bool $readOnly whether the list is read-only
63
	 * @throws TInvalidDataTypeException If data is not null and neither an array nor an iterator.
64
	 */
65
	public function __construct($data = null, $readOnly = false)
66
	{
67
		parent::__construct();
68
		if ($data !== null) {
69
			$this->copyFrom($data);
70
		}
71
		$this->setReadOnly($readOnly);
72
	}
73
74
	/**
75
	 * @return bool whether this list is read-only or not. Defaults to false.
76
	 */
77
	public function getReadOnly(): bool
78
	{
79
		return $this->_r;
80
	}
81
82
	/**
83
	 * @param bool $value whether this list is read-only or not
84
	 */
85
	protected function setReadOnly($value)
86
	{
87
		$this->_r = TPropertyValue::ensureBoolean($value);
88
	}
89
90
	/**
91
	 * Returns an iterator for traversing the items in the list.
92
	 * This method is required by the interface \IteratorAggregate.
93
	 * @return \Iterator an iterator for traversing the items in the list.
94
	 */
95
	public function getIterator(): \Iterator
96
	{
97
		return new \ArrayIterator($this->_d);
98
	}
99
100
	/**
101
	 * Returns the number of items in the list.
102
	 * This method is required by \Countable interface.
103
	 * @return int number of items in the list.
104
	 */
105
	public function count(): int
106
	{
107
		return $this->getCount();
108
	}
109
110
	/**
111
	 * @return int the number of items in the list
112
	 */
113
	public function getCount(): int
114
	{
115
		return $this->_c;
116
	}
117
118
	/**
119
	 * Returns the item at the specified offset.
120
	 * This method is exactly the same as {@link offsetGet}.
121
	 * @param int $index the index of the item
122
	 * @throws TInvalidDataValueException if the index is out of the range
123
	 * @return mixed the item at the index
124
	 */
125
	public function itemAt($index)
126
	{
127
		if ($index >= 0 && $index < $this->_c) {
128
			return $this->_d[$index];
129
		} else {
130
			throw new TInvalidDataValueException('list_index_invalid', $index);
131
		}
132
	}
133
134
	/**
135
	 * Appends an item at the end of the list.
136
	 * @param mixed $item new item
137
	 * @throws TInvalidOperationException if the list is read-only
138
	 * @return int the zero-based index at which the item is added
139
	 */
140
	public function add($item)
141
	{
142
		$this->insertAt($this->_c, $item);
143
		return $this->_c - 1;
144
	}
145
146
	/**
147
	 * Inserts an item at the specified position.
148
	 * Original item at the position and the next items
149
	 * will be moved one step towards the end.
150
	 * @param int $index the specified position.
151
	 * @param mixed $item new item
152
	 * @throws TInvalidDataValueException If the index specified exceeds the bound
153
	 * @throws TInvalidOperationException if the list is read-only
154
	 */
155
	public function insertAt($index, $item)
156
	{
157
		if (!$this->_r) {
158
			if ($index === $this->_c) {
159
				$this->_d[$this->_c++] = $item;
160
			} elseif ($index >= 0 && $index < $this->_c) {
161
				array_splice($this->_d, $index, 0, [$item]);
162
				$this->_c++;
163
			} else {
164
				throw new TInvalidDataValueException('list_index_invalid', $index);
165
			}
166
		} else {
167
			throw new TInvalidOperationException('list_readonly', get_class($this));
168
		}
169
	}
170
171
	/**
172
	 * Removes an item from the list.
173
	 * The list will first search for the item.
174
	 * The first item found will be removed from the list.
175
	 * @param mixed $item the item to be removed.
176
	 * @throws TInvalidDataValueException If the item does not exist
177
	 * @throws TInvalidOperationException if the list is read-only
178
	 * @return int the index at which the item is being removed
179
	 */
180
	public function remove($item)
181
	{
182
		if (!$this->_r) {
183
			if (($index = $this->indexOf($item)) >= 0) {
184
				$this->removeAt($index);
185
				return $index;
186
			} else {
187
				throw new TInvalidDataValueException('list_item_inexistent');
188
			}
189
		} else {
190
			throw new TInvalidOperationException('list_readonly', get_class($this));
191
		}
192
	}
193
194
	/**
195
	 * Removes an item at the specified position.
196
	 * @param int $index the index of the item to be removed.
197
	 * @throws TInvalidDataValueException If the index specified exceeds the bound
198
	 * @throws TInvalidOperationException if the list is read-only
199
	 * @return mixed the removed item.
200
	 */
201
	public function removeAt($index)
202
	{
203
		if (!$this->_r) {
204
			if ($index >= 0 && $index < $this->_c) {
205
				$this->_c--;
206
				if ($index === $this->_c) {
207
					return array_pop($this->_d);
208
				} else {
209
					$item = $this->_d[$index];
210
					array_splice($this->_d, $index, 1);
211
					return $item;
212
				}
213
			} else {
214
				throw new TInvalidDataValueException('list_index_invalid', $index);
215
			}
216
		} else {
217
			throw new TInvalidOperationException('list_readonly', get_class($this));
218
		}
219
	}
220
221
	/**
222
	 * Removes all items in the list.
223
	 * @throws TInvalidOperationException if the list is read-only
224
	 */
225
	public function clear(): void
226
	{
227
		for ($i = $this->_c - 1; $i >= 0; --$i) {
228
			$this->removeAt($i);
229
		}
230
	}
231
232
	/**
233
	 * @param mixed $item the item
234
	 * @return bool whether the list contains the item
235
	 */
236
	public function contains($item): bool
237
	{
238
		return $this->indexOf($item) !== -1;
239
	}
240
241
	/**
242
	 * @param mixed $item the item
243
	 * @return int the index of the item in the list (0 based), -1 if not found.
244
	 */
245
	public function indexOf($item)
246
	{
247
		if (($index = array_search($item, $this->_d, true)) === false) {
248
			return -1;
249
		} else {
250
			return $index;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $index also could return the type string which is incompatible with the documented return type integer.
Loading history...
251
		}
252
	}
253
254
	/**
255
	 * Finds the base item.  If found, the item is inserted before it.
256
	 * @param mixed $baseitem the base item which will be pushed back by the second parameter
257
	 * @param mixed $item the item
258
	 * @throws TInvalidDataValueException if the base item is not within this list
259
	 * @throws TInvalidOperationException if the list is read-only
260
	 * @return int the index where the item is inserted
261
	 * @since 3.2a
262
	 */
263
	public function insertBefore($baseitem, $item)
264
	{
265
		if (!$this->_r) {
266
			if (($index = $this->indexOf($baseitem)) == -1) {
267
				throw new TInvalidDataValueException('list_item_inexistent');
268
			}
269
270
			$this->insertAt($index, $item);
271
272
			return $index;
273
		} else {
274
			throw new TInvalidOperationException('list_readonly', get_class($this));
275
		}
276
	}
277
278
	/**
279
	 * Finds the base item.  If found, the item is inserted after it.
280
	 * @param mixed $baseitem the base item which comes before the second parameter when added to the list
281
	 * @param mixed $item the item
282
	 * @throws TInvalidDataValueException if the base item is not within this list
283
	 * @throws TInvalidOperationException if the list is read-only
284
	 * @return int the index where the item is inserted
285
	 * @since 3.2a
286
	 */
287
	public function insertAfter($baseitem, $item)
288
	{
289
		if (!$this->_r) {
290
			if (($index = $this->indexOf($baseitem)) == -1) {
291
				throw new TInvalidDataValueException('list_item_inexistent');
292
			}
293
294
			$this->insertAt($index + 1, $item);
295
296
			return $index + 1;
297
		} else {
298
			throw new TInvalidOperationException('list_readonly', get_class($this));
299
		}
300
	}
301
302
	/**
303
	 * @return array the list of items in array
304
	 */
305
	public function toArray(): array
306
	{
307
		return $this->_d;
308
	}
309
310
	/**
311
	 * Copies iterable data into the list.
312
	 * Note, existing data in the list will be cleared first.
313
	 * @param mixed $data the data to be copied from, must be an array or object implementing Traversable
314
	 * @throws TInvalidDataTypeException If data is neither an array nor a Traversable.
315
	 */
316
	public function copyFrom($data): void
317
	{
318
		if (is_array($data) || ($data instanceof \Traversable)) {
319
			if ($this->_c > 0) {
320
				$this->clear();
321
			}
322
			foreach ($data as $item) {
323
				$this->add($item);
324
			}
325
		} elseif ($data !== null) {
326
			throw new TInvalidDataTypeException('list_data_not_iterable');
327
		}
328
	}
329
330
	/**
331
	 * Merges iterable data into the map.
332
	 * New data will be appended to the end of the existing data.
333
	 * @param mixed $data the data to be merged with, must be an array or object implementing Traversable
334
	 * @throws TInvalidDataTypeException If data is neither an array nor an iterator.
335
	 */
336
	public function mergeWith($data): void
337
	{
338
		if (is_array($data) || ($data instanceof \Traversable)) {
339
			foreach ($data as $item) {
340
				$this->add($item);
341
			}
342
		} elseif ($data !== null) {
343
			throw new TInvalidDataTypeException('list_data_not_iterable');
344
		}
345
	}
346
347
	/**
348
	 * Returns whether there is an item at the specified offset.
349
	 * This method is required by the interface \ArrayAccess.
350
	 * @param int $offset the offset to check on
351
	 * @return bool
352
	 */
353
	public function offsetExists($offset): bool
354
	{
355
		return ($offset >= 0 && $offset < $this->_c);
356
	}
357
358
	/**
359
	 * Returns the item at the specified offset.
360
	 * This method is required by the interface \ArrayAccess.
361
	 * @param int $offset the offset to retrieve item.
362
	 * @throws TInvalidDataValueException if the offset is invalid
363
	 * @return mixed the item at the offset
364
	 */
365
	public function offsetGet($offset): mixed
366
	{
367
		return $this->itemAt($offset);
368
	}
369
370
	/**
371
	 * Sets the item at the specified offset.
372
	 * This method is required by the interface \ArrayAccess.
373
	 * @param int $offset the offset to set item
374
	 * @param mixed $item the item value
375
	 */
376
	public function offsetSet($offset, $item): void
377
	{
378
		if ($offset === null || $offset === $this->_c) {
379
			$this->insertAt($this->_c, $item);
380
		} else {
381
			$this->removeAt($offset);
382
			$this->insertAt($offset, $item);
383
		}
384
	}
385
386
	/**
387
	 * Unsets the item at the specified offset.
388
	 * This method is required by the interface \ArrayAccess.
389
	 * @param int $offset the offset to unset item
390
	 */
391
	public function offsetUnset($offset): void
392
	{
393
		$this->removeAt($offset);
394
	}
395
396
	/**
397
	 * Returns an array with the names of all variables of this object that should NOT be serialized
398
	 * because their value is the default one or useless to be cached for the next page loads.
399
	 * When there are no items in the list, _d and _c are not stored
400
	 * @param array $exprops by reference
401
	 * @since 4.2.3
402
	 */
403
	protected function _getZappableSleepProps(&$exprops)
404
	{
405
		parent::_getZappableSleepProps($exprops);
406
		if ($this->_c === 0) {
407
			$exprops[] = "\0*\0_d";
408
			$exprops[] = "\0*\0_c";
409
		}
410
		if ($this->_r === false) {
411
			$exprops[] = "\0" . __CLASS__ . "\0_r";
412
		}
413
	}
414
}
415