Passed
Push — master ( d4b9fc...75256f )
by Fabio
04:50
created

TList::_getZappableSleepProps()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

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
ccs 0
cts 0
cp 0
crap 12
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 206
		parent::__construct();
68
		if ($data !== null) {
69 206
			$this->copyFrom($data);
70 29
		}
71
		$this->setReadOnly($readOnly);
72 206
	}
73 206
74
	/**
75
	 * @return bool whether this list is read-only or not. Defaults to false.
76
	 */
77
	public function getReadOnly(): bool
78 120
	{
79
		return $this->_r;
80 120
	}
81
82
	/**
83
	 * @param bool $value whether this list is read-only or not
84
	 */
85
	protected function setReadOnly($value)
86 206
	{
87
		$this->_r = TPropertyValue::ensureBoolean($value);
88 206
	}
89 206
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 25
	{
97
		return new \ArrayIterator($this->_d);
98 25
	}
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 2
	{
107
		return $this->getCount();
108 2
	}
109
110
	/**
111
	 * @return int the number of items in the list
112
	 */
113
	public function getCount(): int
114 151
	{
115
		return $this->_c;
116 151
	}
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 102
	{
127
		if ($index >= 0 && $index < $this->_c) {
128 102
			return $this->_d[$index];
129 102
		} else {
130
			throw new TInvalidDataValueException('list_index_invalid', $index);
131 2
		}
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 79
	{
142
		$this->insertAt($this->_c, $item);
143 79
		return $this->_c - 1;
144 79
	}
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 100
	{
157
		if (!$this->_r) {
158 100
			if ($index === $this->_c) {
159 100
				$this->_d[$this->_c++] = $item;
160 100
			} elseif ($index >= 0 && $index < $this->_c) {
161 2
				array_splice($this->_d, $index, 0, [$item]);
162 2
				$this->_c++;
163 2
			} else {
164
				throw new TInvalidDataValueException('list_index_invalid', $index);
165 100
			}
166
		} else {
167
			throw new TInvalidOperationException('list_readonly', get_class($this));
168 2
		}
169
	}
170 100
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 8
	{
182
		if (!$this->_r) {
183 8
			if (($index = $this->indexOf($item)) >= 0) {
184 7
				$this->removeAt($index);
185 7
				return $index;
186 7
			} else {
187
				throw new TInvalidDataValueException('list_item_inexistent');
188 1
			}
189
		} else {
190
			throw new TInvalidOperationException('list_readonly', get_class($this));
191 1
		}
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 18
	{
203
		if (!$this->_r) {
204 18
			if ($index >= 0 && $index < $this->_c) {
205 16
				$this->_c--;
206 16
				if ($index === $this->_c) {
207 16
					return array_pop($this->_d);
208 11
				} else {
209
					$item = $this->_d[$index];
210 6
					array_splice($this->_d, $index, 1);
211 6
					return $item;
212 6
				}
213
			} else {
214
				throw new TInvalidDataValueException('list_index_invalid', $index);
215 2
			}
216
		} else {
217
			throw new TInvalidOperationException('list_readonly', get_class($this));
218 2
		}
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 17
	{
227
		for ($i = $this->_c - 1; $i >= 0; --$i) {
228 17
			$this->removeAt($i);
229 7
		}
230
	}
231 16
232
	/**
233
	 * @param mixed $item the item
234
	 * @return bool whether the list contains the item
235
	 */
236
	public function contains($item): bool
237 8
	{
238
		return $this->indexOf($item) !== -1;
239 8
	}
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 16
	{
247
		if (($index = array_search($item, $this->_d, true)) === false) {
248 16
			return -1;
249 13
		} 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 12
		}
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 2
	{
265
		if (!$this->_r) {
266 2
			if (($index = $this->indexOf($baseitem)) == -1) {
267 1
				throw new TInvalidDataValueException('list_item_inexistent');
268 1
			}
269
270
			$this->insertAt($index, $item);
271
272
			return $index;
273
		} else {
274
			throw new TInvalidOperationException('list_readonly', get_class($this));
275 1
		}
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 2
	{
289
		if (!$this->_r) {
290 2
			if (($index = $this->indexOf($baseitem)) == -1) {
291 1
				throw new TInvalidDataValueException('list_item_inexistent');
292 1
			}
293
294
			$this->insertAt($index + 1, $item);
295
296
			return $index + 1;
297
		} else {
298
			throw new TInvalidOperationException('list_readonly', get_class($this));
299 1
		}
300
	}
301
302
	/**
303
	 * @return array the list of items in array
304
	 */
305
	public function toArray(): array
306 8
	{
307
		return $this->_d;
308 8
	}
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 31
	{
318
		if (is_array($data) || ($data instanceof \Traversable)) {
319 31
			if ($this->_c > 0) {
320 31
				$this->clear();
321 2
			}
322
			foreach ($data as $item) {
323 31
				$this->add($item);
324 31
			}
325
		} elseif ($data !== null) {
326 1
			throw new TInvalidDataTypeException('list_data_not_iterable');
327 1
		}
328
	}
329 31
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 1
	{
338
		if (is_array($data) || ($data instanceof \Traversable)) {
339 1
			foreach ($data as $item) {
340 1
				$this->add($item);
341 1
			}
342
		} elseif ($data !== null) {
343 1
			throw new TInvalidDataTypeException('list_data_not_iterable');
344 1
		}
345
	}
346 1
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 1
	{
355
		return ($offset >= 0 && $offset < $this->_c);
356 1
	}
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 8
	{
367
		return $this->itemAt($offset);
368 8
	}
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 18
	{
378
		if ($offset === null || $offset === $this->_c) {
379 18
			$this->insertAt($this->_c, $item);
380 17
		} else {
381
			$this->removeAt($offset);
382 1
			$this->insertAt($offset, $item);
383 1
		}
384
	}
385 18
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 1
	{
393
		$this->removeAt($offset);
394 1
	}
395 1
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