Passed
Push — enums ( 8bfe56...7bf697 )
by Fabio
06:38 queued 01:28
created

TWeakCallableCollection::copyFrom()   C

Complexity

Conditions 14
Paths 18

Size

Total Lines 31
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 14
eloc 21
nc 18
nop 1
dl 0
loc 31
rs 6.2666
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * TWeakCallableCollection class
4
 *
5
 * @author Brad Anderson <[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\TInvalidDataValueException;
13
use Prado\Exceptions\TInvalidDataTypeException;
14
use Prado\Exceptions\TInvalidOperationException;
15
use Prado\TPropertyValue;
16
17
use Closure;
18
use Traversable;
19
use WeakReference;
20
21
/**
22
 * TWeakCallableCollection class
23
 *
24
 * TWeakCallableCollection implements a priority ordered list collection of callables.
25
 * This extends {@link TPriorityList}.  This holds the callables for object event handlers
26
 * and global event handlers by converting all callable objects into a WeakReference.
27
 * TWeakCallableCollection prevents circular references in global events that would
28
 * otherwise block object destruction, and thus removal of the callable in __destruct.
29
 * All data out has the callable objects converted back to the regular object reference
30
 * in a callable.
31
 *
32
 * This uses PHP 8 WeakMap to track any system changes to the weak references of
33
 * objects the list is using -when {@link getDiscardInvalid DiscardInvalid} is true.
34
 * By default, when the map is read only then the items are not scrubbed, but the
35
 * scrubbing behavior can be enabled for read only lists. In this instance, no new
36
 * items can be added but only a list of valid  callables is kept.
37
 *
38
 * By default, lists that are mutable (aka. not read only) will discard invalid callables
39
 * automatically, but the scrubbing behavior can be disabled for mutable lists if needed.
40
 *
41
 * @author Brad Anderson <[email protected]>
42
 * @since 4.2.0
43
 */
44
class TWeakCallableCollection extends TPriorityList
45
{
46
	use TWeakCollectionTrait;
47
48
	/** @var bool Should invalid WeakReferences automatically be deleted from the list */
49
	private bool $_discardInvalid = true;
50
51
	/**
52
	 * Constructor.
53
	 * Initializes the list with an array or an iterable object.
54
	 * @param null|array|\Iterator|TPriorityList|TPriorityMap $data The initial data.
55
	 *   Default is null, meaning no initial data.
56
	 * @param ?bool $readOnly Whether the list is read-only
57
	 * @param ?numeric $defaultPriority The default priority of items without specified
0 ignored issues
show
Bug introduced by
The type Prado\Collections\numeric was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
58
	 *   priorities. Default null for 10.
59
	 * @paraum ?int $precision The precision of the numeric priorities.  Default null
60
	 *   for 8.
61
	 * @param ?bool $discardInvalid Whether or not to discard invalid WeakReferences.
62
	 *   Default null for the opposite of Read-Only.  Mutable Lists expunge invalid
63
	 *   WeakReferences and Read only lists do not.  Set this bool to override the default
64
	 *   behavior.
65
	 * @param null|mixed $precision
66
	 * @throws \Prado\Exceptions\TInvalidDataTypeException If data is not null and
67
	 *   is neither an array nor an iterator.
68
	 */
69
	public function __construct($data = null, $readOnly = null, $defaultPriority = null, $precision = null, $discardInvalid = null)
70
	{
71
		if ($readOnly === null) {
72
			$readOnly = false;
73
		}
74
		if ($discardInvalid === null) {
75
			$discardInvalid = !$readOnly;
76
		}
77
		$this->_discardInvalid = $discardInvalid;
78
		if ($discardInvalid) {
79
			$this->weakStart();
80
		}
81
		if ($defaultPriority === null) {
82
			$defaultPriority = 10;
83
		}
84
		if ($precision === null) {
85
			$precision = 8;
86
		}
87
		parent::__construct($data, $readOnly, $defaultPriority, $precision);
0 ignored issues
show
Bug introduced by
It seems like $defaultPriority can also be of type integer; however, parameter $defaultPriority of Prado\Collections\TPriorityList::__construct() does only seem to accept Prado\Collections\numeric, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

87
		parent::__construct($data, $readOnly, /** @scrutinizer ignore-type */ $defaultPriority, $precision);
Loading history...
Bug introduced by
It seems like $data can also be of type Prado\Collections\TPriorityList and Prado\Collections\TPriorityMap; however, parameter $data of Prado\Collections\TPriorityList::__construct() does only seem to accept Iterator|array|null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

87
		parent::__construct(/** @scrutinizer ignore-type */ $data, $readOnly, $defaultPriority, $precision);
Loading history...
88
	}
89
90
	/**
91
	 * Cloning a TWeakCallableCollection requires cloning the WeakMap
92
	 * @since 4.2.3
93
	 */
94
	public function __clone()
95
	{
96
		$this->weakClone();
97
		parent::__clone();
98
	}
99
100
	/**
101
	 * Waking up a TWeakCallableCollection requires creating the WeakMap.  No items
102
	 * are saved in TWeakList so only initialization of the WeakMap is required.
103
	 * @since 4.2.3
104
	 */
105
	public function __wakeup()
106
	{
107
		if ($this->_discardInvalid) {
108
			$this->weakStart();
109
		}
110
		parent::__wakeup();
111
	}
112
113
114
	/**
115
	 * TWeakCallableCollection cannot auto listen to global events or there will be
116
	 * catastrophic recursion.
117
	 * @return bool returns false
118
	 */
119
	public function getAutoGlobalListen()
120
	{
121
		return false;
122
	}
123
124
	/**
125
	 * This converts the $items array of callable with WeakReferences back into the
126
	 * actual callable.
127
	 * @param array &$items an array of callable where objects are WeakReference
128
	 */
129
	protected function filterItemsForOutput(&$items)
130
	{
131
		if (!is_array($items)) {
0 ignored issues
show
introduced by
The condition is_array($items) is always true.
Loading history...
132
			return;
133
		}
134
		for ($i = 0, $c = count($items); $i < $c; $i++) {
135
			$this->filterItemForOutput($items[$i]);
136
		}
137
	}
138
139
140
	/**
141
	 * This converts the $items array of callable with WeakReferences back into the
142
	 * actual callable.
143
	 * @param callable &$handler the $handler or $handler[0] may be a WeakReference
144
	 */
145
	protected function filterItemForOutput(&$handler)
146
	{
147
		if (is_array($handler) && is_object($handler[0]) && ($handler[0] instanceof WeakReference)) {
148
			if ($obj = $handler[0]->get()) {
149
				$handler[0] = $obj;
150
			} else {
151
				$handler = null;
152
			}
153
		} elseif (is_object($handler) && ($handler instanceof WeakReference)) {
154
			$handler = $handler->get();
155
		}
156
	}
157
158
159
	/**
160
	 * Converts the $handler callable into a WeakReference version for storage
161
	 * @param callable &$handler callable to convert into a WeakReference version
162
	 * @param bool $validate whether or not to validate the input as a callable
163
	 */
164
	protected function filterItemForInput(&$handler, $validate = false)
165
	{
166
		if ($validate && !is_callable($handler)) {
167
			throw new TInvalidDataValueException('weakcallablecollection_callable_required');
168
		}
169
		if (is_array($handler) && is_object($handler[0])) {
170
			$handler[0] = WeakReference::create($handler[0]);
171
		} elseif (is_object($handler) && !($handler instanceof Closure)) {
172
			$handler = WeakReference::create($handler);
173
		}
174
	}
175
176
	/**
177
	 * When a change in the WeakMap is detected, scrub the list of WeakReference that
178
	 * have lost their object.
179
	 * All invalid WeakReference[s] are optionally removed from the list when {@link
180
	 * getDiscardInvalid} is true.
181
	 * @since 4.2.3
182
	 */
183
	protected function scrubWeakReferences()
184
	{
185
		if (!$this->_discardInvalid || !$this->weakChanged()) {
186
			return;
187
		}
188
		foreach (array_keys($this->_d) as $priority) {
189
			for ($c = $i = count($this->_d[$priority]), $i--; $i >= 0; $i--) {
190
				$a = is_array($this->_d[$priority][$i]);
191
				if ($a && is_object($this->_d[$priority][$i][0]) && ($this->_d[$priority][$i][0] instanceof WeakReference) && $this->_d[$priority][$i][0]->get() === null ||
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($a && is_object($this->...ty][$i]->get() === null, Probably Intended Meaning: $a && is_object($this->_...y][$i]->get() === null)
Loading history...
192
				!$a && is_object($this->_d[$priority][$i]) && ($this->_d[$priority][$i] instanceof WeakReference) && $this->_d[$priority][$i]->get() === null) {
193
					$c--;
194
					$this->_c--;
195
					if ($i === $c) {
196
						array_pop($this->_d[$priority]);
197
					} else {
198
						array_splice($this->_d[$priority], $i, 1);
199
					}
200
				}
201
			}
202
			if (!$c) {
203
				unset($this->_d[$priority]);
204
			}
205
		}
206
		$this->_fd = null;
207
		$this->weakResetCount();
208
	}
209
210
	/**
211
	 * @return bool Does the TWeakList scrub invalid WeakReferences.
212
	 * @since 4.2.3
213
	 */
214
	public function getDiscardInvalid(): bool
215
	{
216
		return $this->_discardInvalid;
217
	}
218
219
	/**
220
	 * All invalid WeakReference[s] are optionally removed from the list on $value
221
	 *  being "true".
222
	 * @param bool $value Sets the TWeakList scrubbing of invalid WeakReferences.
223
	 * @since 4.2.3
224
	 */
225
	protected function setDiscardInvalid($value): void
226
	{
227
		$value = TPropertyValue::ensureBoolean($value);
228
		if ($value && !$this->_discardInvalid) {
229
			$this->weakStart();
230
			foreach (array_keys($this->_d) as $priority) {
231
				for ($i = count($this->_d[$priority]) - 1; $i >= 0; $i--) {
232
					$a = is_array($this->_d[$priority][$i]);
233
					if ($a && is_object($this->_d[$priority][$i][0]) || !$a && is_object($this->_d[$priority][$i])) {
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($a && is_object($this->...his->_d[$priority][$i]), Probably Intended Meaning: $a && (is_object($this->...is->_d[$priority][$i]))
Loading history...
234
						$obj = $a ? $this->_d[$priority][$i][0] : $this->_d[$priority][$i];
235
						if ($obj instanceof WeakReference) {
236
							if($obj = $obj->get()) {
237
								$this->weakAdd($obj);
238
							} else {
239
								parent::removeAtIndexInPriority($i, $priority);
240
							}
241
						} else {
242
							$this->weakAdd($obj);
243
						}
244
					}
245
				}
246
			}
247
		} elseif (!$value && $this->_discardInvalid) {
248
			$this->weakStop();
249
		}
250
		$this->_discardInvalid = $value;
251
	}
252
253
254
	/**
255
	 * This flattens the priority list into a flat array [0,...,n-1]. This is needed to
256
	 * filter the output.
257
	 * All invalid WeakReference[s] are optionally removed from the list before flattening.
258
	 */
259
	protected function flattenPriorities(): void
260
	{
261
		$this->scrubWeakReferences();
262
		parent::flattenPriorities();
263
	}
264
265
	/**
266
	 * This returns a list of the priorities within this list, ordered lowest (first)
267
	 * to highest (last).
268
	 * All invalid WeakReference[s] are optionally removed from the list before getting
269
	 * the priorities.
270
	 * @return array the array of priority numerics in increasing priority number
271
	 * @since 4.2.3
272
	 */
273
	public function getPriorities(): array
274
	{
275
		$this->scrubWeakReferences();
276
		return parent::getPriorities();
277
	}
278
279
	/**
280
	 * Gets the number of items at a priority within the list.
281
	 * All invalid WeakReference[s] are optionally removed from the list before counting.
282
	 * @param null|numeric $priority optional priority at which to count items.  if no
283
	 *    parameter, it will be set to the default {@link getDefaultPriority}
284
	 * @return false|int the number of items in the list at the specified priority
285
	 * @since 4.2.3
286
	 */
287
	public function getPriorityCount($priority = null)
288
	{
289
		$this->scrubWeakReferences();
290
		return parent::getPriorityCount($priority);
291
	}
292
293
	/**
294
	 * Returns an iterator for traversing the items in the list.
295
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
296
	 * This method is required by the interface \IteratorAggregate.
297
	 * @return \Iterator an iterator for traversing the items in the list.
298
	 * @since 4.2.3
299
	 */
300
	public function getIterator(): \Iterator
301
	{
302
		$this->flattenPriorities();
303
		$items = $this->_fd;
304
		$this->filterItemsForOutput($items);
305
		return new \ArrayIterator($items);
306
	}
307
308
	/**
309
	 * Returns the total number of items in the list
310
	 * All invalid WeakReference[s] are optionally removed from the list before counting.
311
	 * @return int the number of items in the list
312
	 * @since 4.2.3
313
	 */
314
	public function getCount(): int
315
	{
316
		$this->scrubWeakReferences();
317
		return parent::getCount();
318
	}
319
320
	/**
321
	 * Returns the item at the index of a flattened priority list. This is needed to
322
	 *  filter the output.  {@link offsetGet} calls this method.
323
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
324
	 * @param int $index the index of the item to get
325
	 * @throws TInvalidDataValueException Issued when the index is invalid
326
	 * @return mixed the element at the offset
327
	 */
328
	public function itemAt($index)
329
	{
330
		$this->scrubWeakReferences();
331
		if ($index >= 0 && $index < $this->_c) {
332
			parent::flattenPriorities();
333
			$item = $this->_fd[$index];
334
			$this->filterItemForOutput($item);
335
			return $item;
336
		} else {
337
			throw new TInvalidDataValueException('list_index_invalid', $index);
338
		}
339
	}
340
341
	/**
342
	 * Gets all the items at a specific priority. This is needed to filter the output.
343
	 * All invalid WeakReference[s] are optionally removed from the list before retrieving.
344
	 * @param null|numeric $priority priority of the items to get.  Defaults to null,
345
	 *    filled in with the default priority, if left blank.
346
	 * @return ?array all items at priority in index order, null if there are no items
347
	 *    at that priority
348
	 */
349
	public function itemsAtPriority($priority = null): ?array
350
	{
351
		$this->scrubWeakReferences();
352
		$items = parent::itemsAtPriority($priority);
353
		$this->filterItemsForOutput($items);
354
		return $items;
355
	}
356
357
	/**
358
	 * Returns the item at an index within a priority. This is needed to filter the
359
	 * output.
360
	 * All invalid WeakReference[s] are optionally removed from the list before retrieving.
361
	 * @param int $index the index into the list of items at priority
362
	 * @param null|numeric $priority the priority which to index.  no parameter or null
363
	 *   will result in the default priority
364
	 * @return mixed the element at the offset, false if no element is found at the offset
365
	 */
366
	public function itemAtIndexInPriority($index, $priority = null)
367
	{
368
		$this->scrubWeakReferences();
369
		$item = parent::itemAtIndexInPriority($index, $priority);
370
		$this->filterItemForOutput($item);
371
		return $item;
372
	}
373
374
	/**
375
	 * Inserts an item at an index.  It reads the priority of the item at index within the
376
	 * flattened list and then inserts the item at that priority-index.
377
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
378
	 * @param int $index the specified position in the flattened list.
379
	 * @param mixed $item new item to add
380
	 * @throws TInvalidDataValueException If the index specified exceeds the bound
381
	 * @throws TInvalidOperationException if the list is read-only
382
	 * @since 4.2.3
383
	 */
384
	public function insertAt($index, $item)
385
	{
386
		if ($this->getReadOnly()) {
387
			throw new TInvalidOperationException('list_readonly', $this::class);
388
		}
389
390
		if (($priority = $this->priorityAt($index, true)) !== false) {
391
			$this->internalInsertAtIndexInPriority($item, $priority[1], $priority[0]);
392
		} else {
393
			throw new TInvalidDataValueException('list_index_invalid', $index);
394
		}
395
	}
396
397
	/**
398
	 * Inserts an item at the specified index within a priority.  This scrubs the list and
399
	 * calls {@link internalInsertAtIndexInPriority}.
400
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
401
	 * @param mixed $item item to add within the list.
402
	 * @param false|int $index index within the priority to add the item, defaults to false
403
	 *    which appends the item at the priority
404
	 * @param null|numeric $priority priority of the item.  defaults to null, which sets it
405
	 *   to the default priority
406
	 * @param bool $preserveCache preserveCache specifies if this is a special quick function
407
	 *   or not. This defaults to false.
408
	 * @throws \Prado\Exceptions\TInvalidDataValueException If the index specified exceeds
409
	 *   the bound
410
	 * @throws \Prado\Exceptions\TInvalidOperationException if the list is read-only
411
	 */
412
	public function insertAtIndexInPriority($item, $index = false, $priority = null, $preserveCache = false)
413
	{
414
		$this->scrubWeakReferences();
415
		return $this->internalInsertAtIndexInPriority($item, $index, $priority, $preserveCache);
416
	}
417
418
	/**
419
	 * Inserts an item at the specified index within a priority.  This does not scrub the
420
	 * list of WeakReference.  This converts the item into a WeakReference if it is an object
421
	 * or contains an object in its callable.  This does not convert Closure into WeakReference.
422
	 * @param mixed $item item to add within the list.
423
	 * @param false|int $index index within the priority to add the item, defaults to false
424
	 *    which appends the item at the priority
425
	 * @param null|numeric $priority priority of the item.  defaults to null, which sets it
426
	 *    to the default priority
427
	 * @param bool $preserveCache preserveCache specifies if this is a special quick function
428
	 *    or not. This defaults to false.
429
	 * @throws \Prado\Exceptions\TInvalidDataValueException If the index specified exceeds the
430
	 *    bound
431
	 * @throws \Prado\Exceptions\TInvalidOperationException if the list is read-only
432
	 * @since 4.2.3
433
	 */
434
	protected function internalInsertAtIndexInPriority($item, $index = false, $priority = null, $preserveCache = false)
435
	{
436
		$itemPriority = null;
437
		if (($isPriorityItem = ($item instanceof IPriorityItem)) && ($priority === null || !is_numeric($priority))) {
438
			$itemPriority = $priority = $item->getPriority();
439
		}
440
		$priority = $this->ensurePriority($priority);
441
		if (($item instanceof IPriorityCapture) && (!$isPriorityItem || $itemPriority !== $priority)) {
442
			$item->setPriority($priority);
0 ignored issues
show
Bug introduced by
$priority of type string is incompatible with the type Prado\Collections\numeric expected by parameter $value of Prado\Collections\IPriorityCapture::setPriority(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

442
			$item->setPriority(/** @scrutinizer ignore-type */ $priority);
Loading history...
443
		}
444
		if (($isObj = is_object($item)) || is_array($item) && is_object($item[0])) {
445
			$this->weakAdd($isObj ? $item : $item[0]);
446
		}
447
		$this->filterItemForInput($item, true);
0 ignored issues
show
Bug introduced by
It seems like $item can also be of type Prado\Collections\IPriorityCapture and Prado\Collections\IPriorityItem and Prado\Collections\IPrior...ctions\IPriorityCapture; however, parameter $handler of Prado\Collections\TWeakC...n::filterItemForInput() does only seem to accept callable, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

447
		$this->filterItemForInput(/** @scrutinizer ignore-type */ $item, true);
Loading history...
448
		return parent::insertAtIndexInPriority($item, $index, $priority, $preserveCache);
0 ignored issues
show
Bug introduced by
$priority of type string is incompatible with the type Prado\Collections\numeric|null expected by parameter $priority of Prado\Collections\TPrior...sertAtIndexInPriority(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

448
		return parent::insertAtIndexInPriority($item, $index, /** @scrutinizer ignore-type */ $priority, $preserveCache);
Loading history...
449
	}
450
451
	/**
452
	 * Removes an item from the priority list.
453
	 * The list will search for the item.  The first matching item found will be removed from
454
	 * the list.
455
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
456
	 * @param mixed $item item the item to be removed.
457
	 * @param null|bool|float $priority priority of item to remove. without this parameter it
458
	 *   defaults to false.  A value of false means any priority. null will be filled in with
459
	 *   the default priority.
460
	 * @throws TInvalidDataValueException If the item does not exist
461
	 * @return int index within the flattened list at which the item is being removed
462
	 * @since 4.2.3
463
	 */
464
	public function remove($item, $priority = false)
465
	{
466
		if ($this->getReadOnly()) {
467
			throw new TInvalidOperationException('list_readonly', $this::class);
468
		}
469
470
		if (($p = $this->priorityOf($item, true)) !== false) {
0 ignored issues
show
introduced by
The condition $p = $this->priorityOf($item, true) !== false is always true.
Loading history...
471
			if ($priority !== false) {
472
				$priority = $this->ensurePriority($priority);
473
				if ($p[0] != $priority) {
474
					throw new TInvalidDataValueException('list_item_inexistent');
475
				}
476
			}
477
			$this->internalRemoveAtIndexInPriority($p[1], $p[0]);
478
			return $p[2];
479
		} else {
480
			throw new TInvalidDataValueException('list_item_inexistent');
481
		}
482
	}
483
484
	/**
485
	 * Removes an item at the specified index in the flattened list.
486
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
487
	 * @param int $index index of the item to be removed.
488
	 * @throws TInvalidDataValueException If the index specified exceeds the bound
489
	 * @throws TInvalidOperationException if the list is read-only
490
	 * @return mixed the removed item.
491
	 * @since 4.2.3
492
	 */
493
	public function removeAt($index)
494
	{
495
		if ($this->getReadOnly()) {
496
			throw new TInvalidOperationException('list_readonly', $this::class);
497
		}
498
499
		if (($priority = $this->priorityAt($index, true)) !== false) {
500
			return $this->internalRemoveAtIndexInPriority($priority[1], $priority[0]);
501
		}
502
		throw new TInvalidDataValueException('list_index_invalid', $index);
503
	}
504
505
	/**
506
	 * Removes the item at a specific index within a priority.  This is needed to filter
507
	 * the output.
508
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
509
	 * @param int $index index of item to remove within the priority.
510
	 * @param null|numeric $priority priority of the item to remove, defaults to null,
511
	 *    or left blank, it is then set to the default priority
512
	 * @throws TInvalidDataValueException If the item does not exist
513
	 * @return mixed the removed item.
514
	 */
515
	public function removeAtIndexInPriority($index, $priority = null)
516
	{
517
		$this->scrubWeakReferences();
518
		return $this->internalRemoveAtIndexInPriority($index, $priority);
519
	}
520
521
	/**
522
	 * Removes the item at a specific index within a priority.  This is needed to filter
523
	 * the output.
524
	 * @param int $index index of item to remove within the priority.
525
	 * @param null|numeric $priority priority of the item to remove, defaults to null, or
526
	 *    left blank, it is then set to the default priority
527
	 * @throws TInvalidDataValueException If the item does not exist
528
	 * @return mixed the removed item.
529
	 * @since 4.2.3
530
	 */
531
	protected function internalRemoveAtIndexInPriority($index, $priority = null)
532
	{
533
		$item = parent::removeAtIndexInPriority($index, $priority);
534
		$this->filterItemForOutput($item);
535
		if (($isObj = is_object($item)) || is_array($item) && is_object($item[0])) {
536
			$this->weakRemove($obj = $isObj ? $item : $item[0]);
0 ignored issues
show
Bug introduced by
It seems like $obj = $isObj ? $item : $item[0] can also be of type array; however, parameter $object of Prado\Collections\TWeakC...ollection::weakRemove() does only seem to accept object, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

536
			$this->weakRemove(/** @scrutinizer ignore-type */ $obj = $isObj ? $item : $item[0]);
Loading history...
537
		}
538
		return $item;
539
	}
540
541
	/**
542
	 * Removes all items in the priority list by calling removeAtIndexInPriority from the
543
	 * last item to the first.
544
	 * @since 4.2.3
545
	 */
546
	public function clear(): void
547
	{
548
		if ($this->getReadOnly()) {
549
			throw new TInvalidOperationException('list_readonly', get_class($this));
550
		}
551
552
		$c = $this->_c;
553
		foreach (array_keys($this->_d) as $priority) {
554
			for ($index = count($this->_d[$priority]) - 1; $index >= 0; $index--) {
555
				parent::removeAtIndexInPriority($index, $priority);
556
			}
557
		}
558
559
		if ($c) {
560
			$this->weakRestart();
561
		}
562
	}
563
564
	/**
565
	 * @param mixed $item the item
566
	 * @return bool whether the list contains the item
567
	 * @since 4.2.3
568
	 */
569
	public function contains($item): bool
570
	{
571
		$this->filterItemForInput($item);
572
		if ($this->_fd !== null) {
573
			return array_search($item, $this->_fd, true) !== false;
574
		} else {
575
			foreach (array_keys($this->_d) as $priority) {
576
				if (array_search($item, $this->_d[$priority], true) !== false) {
577
					return true;
578
				}
579
			}
580
		}
581
		return false;
582
	}
583
584
	/**
585
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
586
	 * @param mixed $item item being indexed.
587
	 * @return int the index of the item in the flattened list (0 based), -1 if not found.
588
	 */
589
	public function indexOf($item)
590
	{
591
		$this->flattenPriorities();
592
		$this->filterItemForInput($item);
593
		if (($index = array_search($item, $this->_fd, true)) === false) {
0 ignored issues
show
Bug introduced by
It seems like $this->_fd can also be of type null; however, parameter $haystack of array_search() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

593
		if (($index = array_search($item, /** @scrutinizer ignore-type */ $this->_fd, true)) === false) {
Loading history...
594
			return -1;
595
		} else {
596
			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...
597
		}
598
	}
599
600
	/**
601
	 * Returns the priority of a particular item.  This is needed to filter the input.
602
	 * All invalid WeakReference[s] are optionally removed from the list before indexing
603
	 * when $withindex is "true" due to the more complex processing.
604
	 * @param mixed $item the item to look for within the list.
605
	 * @param bool $withindex this specifies if the full positional data of the item
606
	 *   within the list is returned.  This defaults to false, if no parameter is provided,
607
	 *   so only provides the priority number of the item by default.
608
	 * @return array|numeric the priority of the item in the list, false if not found.
609
	 *   if withindex is true, an array is returned of [0 => $priority, 1 => $priorityIndex,
610
	 *    2 => flattenedIndex, 'priority' => $priority, 'index' => $priorityIndex,
611
	 *   'absindex' => flattenedIndex]
612
	 */
613
	public function priorityOf($item, $withindex = false)
614
	{
615
		if ($withindex) {
616
			$this->scrubWeakReferences();
617
		}
618
		$this->filterItemForInput($item);
619
		return parent::priorityOf($item, $withindex);
0 ignored issues
show
Bug Best Practice introduced by
The expression return parent::priorityOf($item, $withindex) could also return false which is incompatible with the documented return type Prado\Collections\numeric|array. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
620
	}
621
622
	/**
623
	 * Returns the priority of an item at a particular flattened index.  The index after
624
	 * the last item does not exist but receives a priority from the last item so that
625
	 * priority information about any new items being appended is available.
626
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
627
	 * @param int $index index of the item within the list
628
	 * @param bool $withindex this specifies if the full positional data of the item
629
	 *   within the list is returned.  This defaults to false, if no parameter is provided,
630
	 *   so only provides the priority number of the item by default.
631
	 * @return array|false|numeric the priority of the item in the list, false if not found.
632
	 *   if $withindex is true, an array is returned of [0 => $priority, 1 => $priorityIndex,
633
	 *   2 => flattenedIndex, 'priority' => $priority, 'index' => $priorityIndex, 'absindex'
634
	 *   => flattenedIndex]
635
	 * @since 4.2.3
636
	 */
637
	public function priorityAt($index, $withindex = false)
638
	{
639
		$this->scrubWeakReferences();
640
		return parent::priorityAt($index, $withindex);
641
	}
642
643
	/**
644
	 * This inserts an item before another item within the list.  It uses the same priority
645
	 * as the found index item and places the new item before it.
646
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
647
	 * @param mixed $indexitem the item to index
648
	 * @param mixed $item the item to add before indexitem
649
	 * @throws TInvalidDataValueException If the item does not exist
650
	 * @return int where the item has been inserted in the flattened list
651
	 * @since 4.2.3
652
	 */
653
	public function insertBefore($indexitem, $item)
654
	{
655
		if ($this->getReadOnly()) {
656
			throw new TInvalidOperationException('list_readonly', $this::class);
657
		}
658
659
		if (($priority = $this->priorityOf($indexitem, true)) === false) {
0 ignored issues
show
introduced by
The condition $priority = $this->prior...exitem, true) === false is always false.
Loading history...
660
			throw new TInvalidDataValueException('list_item_inexistent');
661
		}
662
663
		$this->internalInsertAtIndexInPriority($item, $priority[1], $priority[0]);
664
665
		return $priority[2];
666
	}
667
668
	/**
669
	 * This inserts an item after another item within the list.  It uses the same priority
670
	 * as the found index item and places the new item after it.
671
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
672
	 * @param mixed $indexitem the item to index
673
	 * @param mixed $item the item to add after indexitem
674
	 * @throws TInvalidDataValueException If the item does not exist
675
	 * @return int where the item has been inserted in the flattened list
676
	 * @since 4.2.3
677
	 */
678
	public function insertAfter($indexitem, $item)
679
	{
680
		if ($this->getReadOnly()) {
681
			throw new TInvalidOperationException('list_readonly', $this::class);
682
		}
683
684
		if (($priority = $this->priorityOf($indexitem, true)) === false) {
0 ignored issues
show
introduced by
The condition $priority = $this->prior...exitem, true) === false is always false.
Loading history...
685
			throw new TInvalidDataValueException('list_item_inexistent');
686
		}
687
688
		$this->internalInsertAtIndexInPriority($item, $priority[1] + 1, $priority[0]);
689
690
		return $priority[2] + 1;
691
	}
692
693
	/**
694
	 * All invalid WeakReference[s] are optionally removed from the list before returning.
695
	 * @return array the priority list of items in array
696
	 * @since 4.2.3
697
	 */
698
	public function toArray(): array
699
	{
700
		$this->flattenPriorities();
701
		$items = $this->_fd;
702
		$this->filterItemsForOutput($items);
703
		return $items;
704
	}
705
706
707
	/**
708
	 * All invalid WeakReference[s] are optionally removed from the list before returning.
709
	 * @return array the array of priorities keys with values of arrays of callables.
710
	 *   The priorities are sorted so important priorities, lower numerics, are first.
711
	 */
712
	public function toPriorityArray(): array
713
	{
714
		$this->scrubWeakReferences();
715
		$result = parent::toPriorityArray();
716
		foreach (array_keys($result) as $key) {
717
			$this->filterItemsForOutput($result[$key]);
718
		}
719
		return $result;
720
	}
721
722
723
	/**
724
	 * All invalid WeakReference[s] are optionally removed from the list before returning.
725
	 * @return array the array of priorities keys with values of arrays of callables with
726
	 *   WeakReference rather than objects.  The priorities are sorted so important priorities,
727
	 *   lower numerics, are first.
728
	 */
729
	public function toPriorityArrayWeak()
730
	{
731
		$this->scrubWeakReferences();
732
		return parent::toPriorityArray();
733
	}
734
735
	/**
736
	 * Combines the map elements which have a priority below the parameter value.  This
737
	 * is needed to filter the output.
738
	 * All invalid WeakReference[s] are optionally removed from the list before returning.
739
	 * @param numeric $priority the cut-off priority.  All items of priority less than
740
	 *   this are returned.
741
	 * @param bool $inclusive whether or not the input cut-off priority is inclusive.
742
	 *   Default: false, not inclusive.
743
	 * @return array the array of priorities keys with values of arrays of items that
744
	 *   are below a specified priority.  The priorities are sorted so important priorities,
745
	 *   lower numerics, are first.
746
	 * @since 4.2.3
747
	 */
748
	public function toArrayBelowPriority($priority, bool $inclusive = false): array
749
	{
750
		$this->scrubWeakReferences();
751
		$items = parent::toArrayBelowPriority($priority, $inclusive);
752
		$this->filterItemsForOutput($items);
753
		return $items;
754
	}
755
756
	/**
757
	 * Combines the map elements which have a priority above the parameter value. This
758
	 * is needed to filter the output.
759
	 * All invalid WeakReference[s] are optionally removed from the list before returning.
760
	 * @param numeric $priority the cut-off priority.  All items of priority greater
761
	 *   than this are returned.
762
	 * @param bool $inclusive whether or not the input cut-off priority is inclusive.
763
	 *   Default: true, inclusive.
764
	 * @return array the array of priorities keys with values of arrays of items that
765
	 *   are above a specified priority.  The priorities are sorted so important priorities,
766
	 *   lower numerics, are first.
767
	 * @since 4.2.3
768
	 */
769
	public function toArrayAbovePriority($priority, bool $inclusive = true): array
770
	{
771
		$this->scrubWeakReferences();
772
		$items = parent::toArrayAbovePriority($priority, $inclusive);
773
		$this->filterItemsForOutput($items);
774
		return $items;
775
	}
776
777
	/**
778
	 * Copies iterable data into the list.
779
	 * Note, existing data in the list will be cleared first.
780
	 * @param mixed $data the data to be copied from, must be an array or object implementing
781
	 *   Traversable
782
	 * @throws TInvalidDataTypeException If data is neither an array nor a Traversable.
783
	 * @since 4.2.3
784
	 */
785
	public function copyFrom($data): void
786
	{
787
		if ($data instanceof TPriorityList) {
788
			if ($this->_c > 0) {
789
				$this->clear();
790
			}
791
			$array = $data->toPriorityArray();
792
			foreach (array_keys($array) as $priority) {
793
				for ($i = 0, $c = count($array[$priority]); $i < $c; $i++) {
794
					$this->internalInsertAtIndexInPriority($array[$priority][$i], false, $priority);
795
				}
796
			}
797
		} elseif ($data instanceof TPriorityMap) {
798
			if ($this->_c > 0) {
799
				$this->clear();
800
			}
801
			$array = $data->toPriorityArray();
802
			foreach (array_keys($array) as $priority) {
803
				foreach ($array[$priority] as $item) {
804
					$this->internalInsertAtIndexInPriority($item, false, $priority);
805
				}
806
			}
807
		} elseif (is_array($data) || ($data instanceof Traversable)) {
808
			if ($this->_c > 0) {
809
				$this->clear();
810
			}
811
			foreach ($data as $item) {
812
				$this->internalInsertAtIndexInPriority($item);
813
			}
814
		} elseif ($data !== null) {
815
			throw new TInvalidDataTypeException('list_data_not_iterable');
816
		}
817
	}
818
819
	/**
820
	 * Merges iterable data into the priority list.
821
	 * New data will be appended to the end of the existing data.  If another TPriorityList
822
	 * is merged, the incoming parameter items will be appended at the priorities they are
823
	 * present.  These items will be added to the end of the existing items with equal
824
	 * priorities, if there are any.
825
	 * @param mixed $data the data to be merged with, must be an array or object implementing
826
	 *   Traversable
827
	 * @throws TInvalidDataTypeException If data is neither an array nor an iterator.
828
	 * @since 4.2.3
829
	 */
830
	public function mergeWith($data): void
831
	{
832
		if ($data instanceof TPriorityList) {
833
			$array = $data->toPriorityArray();
834
			foreach (array_keys($array) as $priority) {
835
				for ($i = 0, $c = count($array[$priority]); $i < $c; $i++) {
836
					$this->internalInsertAtIndexInPriority($array[$priority][$i], false, $priority);
837
				}
838
			}
839
		} elseif ($data instanceof TPriorityMap) {
840
			$array = $data->toPriorityArray();
841
			foreach (array_keys($array) as $priority) {
842
				foreach ($array[$priority] as $item) {
843
					$this->internalInsertAtIndexInPriority($item, false, $priority);
844
				}
845
			}
846
		} elseif (is_array($data) || ($data instanceof Traversable)) {
847
			foreach ($data as $item) {
848
				$this->internalInsertAtIndexInPriority($item);
849
			}
850
		} elseif ($data !== null) {
851
			throw new TInvalidDataTypeException('list_data_not_iterable');
852
		}
853
	}
854
855
	/**
856
	 * Sets the element at the specified offset. This method is required by the interface
857
	 * \ArrayAccess.  Setting elements in a priority list is not straight forword when
858
	 * appending and setting at the end boundary.  When appending without an offset (a
859
	 * null offset), the item will be added at the default priority.  The item may not be
860
	 * the last item in the list.  When appending with an offset equal to the count of the
861
	 * list, the item will get be appended with the last items priority.
862
	 *
863
	 * All together, when setting the location of an item, the item stays in that location,
864
	 * but appending an item into a priority list doesn't mean the item is at the end of
865
	 * the list.
866
	 *
867
	 * All invalid WeakReference[s] are optionally removed from the list when an $offset
868
	 * is given.
869
	 * @param int $offset the offset to set element
870
	 * @param mixed $item the element value
871
	 * @since 4.2.3
872
	 */
873
	public function offsetSet($offset, $item): void
874
	{
875
		if ($this->getReadOnly()) {
876
			throw new TInvalidOperationException('list_readonly', $this::class);
877
		}
878
879
		if ($offset === null) {
0 ignored issues
show
introduced by
The condition $offset === null is always false.
Loading history...
880
			$this->internalInsertAtIndexInPriority($item, false, null, true);
881
			return;
882
		}
883
		if ($offset === $this->getCount()) {
884
			$priority = parent::priorityAt($offset, true);
885
		} else {
886
			$priority = parent::priorityAt($offset, true);
887
			$this->internalRemoveAtIndexInPriority($priority[1], $priority[0]);
888
		}
889
		$this->internalInsertAtIndexInPriority($item, $priority[1], $priority[0]);
890
	}
891
892
	/**
893
	 * Returns an array with the names of all variables of this object that should
894
	 * NOT be serialized because their value is the default one or useless to be cached
895
	 * for the next page loads.  Reimplement in derived classes to add new variables,
896
	 * but remember to  also to call the parent implementation first.
897
	 * @param array $exprops by reference
898
	 * @since 4.2.3
899
	 */
900
	protected function _getZappableSleepProps(&$exprops)
901
	{
902
		$c = $this->_c;
903
		$this->_c = 0;
904
		parent::_getZappableSleepProps($exprops);
905
		$this->_c = $c;
906
907
		$this->_weakZappableSleepProps($exprops);
908
		if ($this->_discardInvalid === true) {
909
			$exprops[] = "\0" . __CLASS__ . "\0_discardInvalid";
910
		}
911
	}
912
}
913