Passed
Push — master ( 4fc6c5...494471 )
by Fabio
07:54 queued 03:10
created

TWeakCallableCollection::ensureDiscardInvalid()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 2
nc 2
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
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\Prado;
16
use Prado\TPropertyValue;
17
18
use Closure;
19
use Traversable;
20
use WeakReference;
21
22
/**
23
 * TWeakCallableCollection class
24
 *
25
 * TWeakCallableCollection implements a priority ordered list collection of callables.
26
 * This extends {@link TPriorityList}.  This holds the callables for object event handlers
27
 * and global event handlers by converting all callable objects into a WeakReference.
28
 * TWeakCallableCollection prevents circular references in global events that would
29
 * otherwise block object destruction, and thus removal of the callable in __destruct.
30
 * All data out has the callable objects converted back to the regular object reference
31
 * in a callable.
32
 *
33
 * This uses PHP 8 WeakMap to track any system changes to the weak references of
34
 * objects the list is using -when {@link getDiscardInvalid DiscardInvalid} is true.
35
 * By default, when the map is read only then the items are not scrubbed, but the
36
 * scrubbing behavior can be enabled for read only lists. In this instance, no new
37
 * items can be added but only a list of valid  callables is kept.
38
 *
39
 * By default, lists that are mutable (aka. not read only) will discard invalid callables
40
 * automatically, but the scrubbing behavior can be disabled for mutable lists if needed.
41
 *
42
 * @author Brad Anderson <[email protected]>
43
 * @since 4.2.0
44
 */
45
class TWeakCallableCollection extends TPriorityList
46
{
47
	use TWeakCollectionTrait;
48
49
	/** @var ?bool Should invalid WeakReferences automatically be deleted from the list */
50
	private ?bool $_discardInvalid = null;
51
52
	/**
53
	 * Constructor.
54
	 * Initializes the list with an array or an iterable object.
55
	 * @param null|array|\Iterator|TPriorityList|TPriorityMap $data The initial data.
56
	 *   Default is null, meaning no initial data.
57
	 * @param ?bool $readOnly Whether the list is read-only
58
	 * @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...
59
	 *   priorities. Default null for 10.
60
	 * @paraum ?int $precision The precision of the numeric priorities.  Default null
61
	 *   for 8.
62
	 * @param ?bool $discardInvalid Whether or not to discard invalid WeakReferences.
63
	 *   Default null for the opposite of Read-Only.  Mutable Lists expunge invalid
64
	 *   WeakReferences and Read only lists do not.  Set this bool to override the default
65
	 *   behavior.
66
	 * @param null|int $precision The numeric precision of the priority.
67
	 * @throws \Prado\Exceptions\TInvalidDataTypeException If data is not null and
68
	 *   is neither an array nor an iterator.
69
	 */
70
	public function __construct($data = null, $readOnly = null, $defaultPriority = null, $precision = null, $discardInvalid = null)
71
	{
72
		if ($set = ($discardInvalid === false || $discardInvalid === null && $readOnly === true)) {
73
			$this->setDiscardInvalid(false);
74
		}
75
		parent::__construct($data, $readOnly, $defaultPriority, $precision);
0 ignored issues
show
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

75
		parent::__construct(/** @scrutinizer ignore-type */ $data, $readOnly, $defaultPriority, $precision);
Loading history...
76
		if (!$set) {
77
			$this->setDiscardInvalid($discardInvalid);
78
		}
79
	}
80
81
	/**
82
	 * Cloning a TWeakCallableCollection requires cloning the WeakMap
83
	 * @since 4.2.3
84
	 */
85
	public function __clone()
86
	{
87
		$this->weakClone();
88
		parent::__clone();
89
	}
90
91
	/**
92
	 * Waking up a TWeakCallableCollection requires creating the WeakMap.  No items
93
	 * are saved in TWeakList so only initialization of the WeakMap is required.
94
	 * @since 4.2.3
95
	 */
96
	public function __wakeup()
97
	{
98
		if ($this->_discardInvalid) {
99
			$this->weakStart();
100
		}
101
		parent::__wakeup();
102
	}
103
104
105
	/**
106
	 * TWeakCallableCollection cannot auto listen to global events or there will be
107
	 * catastrophic recursion.
108
	 * @return bool returns false
109
	 */
110
	public function getAutoGlobalListen()
111
	{
112
		return false;
113
	}
114
115
	/**
116
	 * This converts the $items array of callable with WeakReferences back into the
117
	 * actual callable.
118
	 * @param array &$items an array of callable where objects are WeakReference
119
	 */
120
	protected function filterItemsForOutput(&$items)
121
	{
122
		if (!is_array($items)) {
0 ignored issues
show
introduced by
The condition is_array($items) is always true.
Loading history...
123
			return;
124
		}
125
		for ($i = 0, $c = count($items); $i < $c; $i++) {
126
			$this->filterItemForOutput($items[$i]);
127
		}
128
	}
129
130
131
	/**
132
	 * This converts the $items array of callable with WeakReferences back into the
133
	 * actual callable.
134
	 * @param callable &$handler the $handler or $handler[0] may be a WeakReference
135
	 */
136
	protected function filterItemForOutput(&$handler)
137
	{
138
		if (is_array($handler) && is_object($handler[0]) && ($handler[0] instanceof WeakReference)) {
139
			if ($obj = $handler[0]->get()) {
140
				$handler[0] = $obj;
141
			} else {
142
				$handler = null;
143
			}
144
		} elseif (is_object($handler) && ($handler instanceof WeakReference)) {
145
			$handler = $handler->get();
146
		}
147
	}
148
149
150
	/**
151
	 * Converts the $handler callable into a WeakReference version for storage
152
	 * @param callable &$handler callable to convert into a WeakReference version
153
	 * @param bool $validate whether or not to validate the input as a callable
154
	 */
155
	protected function filterItemForInput(&$handler, $validate = false)
156
	{
157
		if ($validate && !is_callable($handler)) {
158
			throw new TInvalidDataValueException('weakcallablecollection_callable_required');
159
		}
160
		if (is_array($handler) && is_object($handler[0])) {
161
			$handler[0] = WeakReference::create($handler[0]);
162
		} elseif (is_object($handler) && !($handler instanceof Closure)) {
163
			$handler = WeakReference::create($handler);
164
		}
165
	}
166
167
	/**
168
	 * When a change in the WeakMap is detected, scrub the list of WeakReference that
169
	 * have lost their object.
170
	 * All invalid WeakReference[s] are optionally removed from the list when {@link
171
	 * getDiscardInvalid} is true.
172
	 * @since 4.2.3
173
	 */
174
	protected function scrubWeakReferences()
175
	{
176
		if (!$this->getDiscardInvalid() || !$this->weakChanged()) {
177
			return;
178
		}
179
		foreach (array_keys($this->_d) as $priority) {
180
			for ($c = $i = count($this->_d[$priority]), $i--; $i >= 0; $i--) {
181
				$a = is_array($this->_d[$priority][$i]);
182
				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...
183
				!$a && is_object($this->_d[$priority][$i]) && ($this->_d[$priority][$i] instanceof WeakReference) && $this->_d[$priority][$i]->get() === null) {
184
					$c--;
185
					$this->_c--;
186
					if ($i === $c) {
187
						array_pop($this->_d[$priority]);
188
					} else {
189
						array_splice($this->_d[$priority], $i, 1);
190
					}
191
				}
192
			}
193
			if (!$c) {
194
				unset($this->_d[$priority]);
195
			}
196
		}
197
		$this->_fd = null;
198
		$this->weakResetCount();
199
	}
200
201
	/**
202
	 * @return bool Does the TWeakList scrub invalid WeakReferences.
203
	 * @since 4.2.3
204
	 */
205
	public function getDiscardInvalid(): bool
206
	{
207
		$this->ensureDiscardInvalid();
208
		return (bool) $this->_discardInvalid;
209
	}
210
211
	/**
212
	 * Ensures that DiscardInvalid is set.
213
	 */
214
	protected function ensureDiscardInvalid()
215
	{
216
		if ($this->_discardInvalid === null) {
217
			$this->setDiscardInvalid(!$this->getReadOnly());
218
		}
219
	}
220
221
	/**
222
	 * All invalid WeakReference[s] are optionally removed from the list on $value
223
	 *  being "true".
224
	 * @param null|bool|string $value Sets the TWeakList scrubbing of invalid WeakReferences.
225
	 * @since 4.2.3
226
	 */
227
	public function setDiscardInvalid($value): void
228
	{
229
		if($value === $this->_discardInvalid) {
230
			return;
231
		}
232
		if ($this->_discardInvalid !== null && !Prado::isCallingSelf()) {
233
			throw new TInvalidOperationException('weak_no_set_discard_invalid', $this::class);
234
		}
235
		$value = TPropertyValue::ensureBoolean($value);
236
		if ($value && !$this->_discardInvalid) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->_discardInvalid of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
237
			$this->weakStart();
238
			foreach (array_keys($this->_d) as $priority) {
239
				for ($i = count($this->_d[$priority]) - 1; $i >= 0; $i--) {
240
					$a = is_array($this->_d[$priority][$i]);
241
					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...
242
						$obj = $a ? $this->_d[$priority][$i][0] : $this->_d[$priority][$i];
243
						if ($obj instanceof WeakReference) {
244
							if($obj = $obj->get()) {
245
								$this->weakAdd($obj);
246
							} else {
247
								parent::removeAtIndexInPriority($i, $priority);
248
							}
249
						} else { // Closure
250
							$this->weakAdd($obj);
251
						}
252
					}
253
				}
254
			}
255
		} elseif (!$value && $this->_discardInvalid) {
256
			$this->weakStop();
257
		}
258
		$this->_discardInvalid = $value;
259
	}
260
261
262
	/**
263
	 * This flattens the priority list into a flat array [0,...,n-1]. This is needed to
264
	 * filter the output.
265
	 * All invalid WeakReference[s] are optionally removed from the list before flattening.
266
	 */
267
	protected function flattenPriorities(): void
268
	{
269
		$this->scrubWeakReferences();
270
		parent::flattenPriorities();
271
	}
272
273
	/**
274
	 * This returns a list of the priorities within this list, ordered lowest (first)
275
	 * to highest (last).
276
	 * All invalid WeakReference[s] are optionally removed from the list before getting
277
	 * the priorities.
278
	 * @return array the array of priority numerics in increasing priority number
279
	 * @since 4.2.3
280
	 */
281
	public function getPriorities(): array
282
	{
283
		$this->scrubWeakReferences();
284
		return parent::getPriorities();
285
	}
286
287
	/**
288
	 * Gets the number of items at a priority within the list.
289
	 * All invalid WeakReference[s] are optionally removed from the list before counting.
290
	 * @param null|numeric $priority optional priority at which to count items.  if no
291
	 *    parameter, it will be set to the default {@link getDefaultPriority}
292
	 * @return int the number of items in the list at the specified priority
293
	 * @since 4.2.3
294
	 */
295
	public function getPriorityCount($priority = null)
296
	{
297
		$this->scrubWeakReferences();
298
		return parent::getPriorityCount($priority);
299
	}
300
301
	/**
302
	 * Returns an iterator for traversing the items in the list.
303
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
304
	 * This method is required by the interface \IteratorAggregate.
305
	 * @return \Iterator an iterator for traversing the items in the list.
306
	 * @since 4.2.3
307
	 */
308
	public function getIterator(): \Iterator
309
	{
310
		$this->flattenPriorities();
311
		$items = $this->_fd;
312
		$this->filterItemsForOutput($items);
313
		return new \ArrayIterator($items);
314
	}
315
316
	/**
317
	 * Returns the total number of items in the list
318
	 * All invalid WeakReference[s] are optionally removed from the list before counting.
319
	 * @return int the number of items in the list
320
	 * @since 4.2.3
321
	 */
322
	public function getCount(): int
323
	{
324
		$this->scrubWeakReferences();
325
		return parent::getCount();
326
	}
327
328
	/**
329
	 * Returns the item at the index of a flattened priority list. This is needed to
330
	 *  filter the output.  {@link offsetGet} calls this method.
331
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
332
	 * @param int $index the index of the item to get
333
	 * @throws TInvalidDataValueException Issued when the index is invalid
334
	 * @return mixed the element at the offset
335
	 */
336
	public function itemAt($index)
337
	{
338
		$this->scrubWeakReferences();
339
		if ($index >= 0 && $index < $this->_c) {
340
			parent::flattenPriorities();
341
			$item = $this->_fd[$index];
342
			$this->filterItemForOutput($item);
343
			return $item;
344
		} else {
345
			throw new TInvalidDataValueException('list_index_invalid', $index);
346
		}
347
	}
348
349
	/**
350
	 * Gets all the items at a specific priority. This is needed to filter the output.
351
	 * All invalid WeakReference[s] are optionally removed from the list before retrieving.
352
	 * @param null|numeric $priority priority of the items to get.  Defaults to null,
353
	 *    filled in with the default priority, if left blank.
354
	 * @return ?array all items at priority in index order, null if there are no items
355
	 *    at that priority
356
	 */
357
	public function itemsAtPriority($priority = null): ?array
358
	{
359
		$this->scrubWeakReferences();
360
		$items = parent::itemsAtPriority($priority);
361
		$this->filterItemsForOutput($items);
362
		return $items;
363
	}
364
365
	/**
366
	 * Returns the item at an index within a priority. This is needed to filter the
367
	 * output.
368
	 * All invalid WeakReference[s] are optionally removed from the list before retrieving.
369
	 * @param int $index the index into the list of items at priority
370
	 * @param null|numeric $priority the priority which to index.  no parameter or null
371
	 *   will result in the default priority
372
	 * @return mixed the element at the offset, false if no element is found at the offset
373
	 */
374
	public function itemAtIndexInPriority($index, $priority = null)
375
	{
376
		$this->scrubWeakReferences();
377
		$item = parent::itemAtIndexInPriority($index, $priority);
378
		$this->filterItemForOutput($item);
379
		return $item;
380
	}
381
382
	/**
383
	 * Inserts an item at an index.  It reads the priority of the item at index within the
384
	 * flattened list and then inserts the item at that priority-index.
385
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
386
	 * @param int $index the specified position in the flattened list.
387
	 * @param mixed $item new item to add
388
	 * @throws TInvalidDataValueException If the index specified exceeds the bound
389
	 * @throws TInvalidOperationException if the list is read-only
390
	 * @since 4.2.3
391
	 */
392
	public function insertAt($index, $item)
393
	{
394
		if ($this->getReadOnly()) {
395
			throw new TInvalidOperationException('list_readonly', $this::class);
396
		}
397
398
		if (($priority = $this->priorityAt($index, true)) !== false) {
399
			$this->internalInsertAtIndexInPriority($item, $priority[1], $priority[0]);
400
		} else {
401
			throw new TInvalidDataValueException('list_index_invalid', $index);
402
		}
403
	}
404
405
	/**
406
	 * Inserts an item at the specified index within a priority.  This scrubs the list and
407
	 * calls {@link internalInsertAtIndexInPriority}.
408
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
409
	 * @param mixed $item item to add within the list.
410
	 * @param null|false|int $index index within the priority to add the item, defaults to null
411
	 *    which appends the item at the priority
412
	 * @param null|numeric $priority priority of the item.  defaults to null, which sets it
413
	 *   to the default priority
414
	 * @param bool $preserveCache preserveCache specifies if this is a special quick function
415
	 *   or not. This defaults to false.
416
	 * @throws \Prado\Exceptions\TInvalidDataValueException If the index specified exceeds
417
	 *   the bound
418
	 * @throws \Prado\Exceptions\TInvalidOperationException if the list is read-only
419
	 */
420
	public function insertAtIndexInPriority($item, $index = null, $priority = null, $preserveCache = false)
421
	{
422
		$this->scrubWeakReferences();
423
		return $this->internalInsertAtIndexInPriority($item, $index, $priority, $preserveCache);
424
	}
425
426
	/**
427
	 * Inserts an item at the specified index within a priority.  This does not scrub the
428
	 * list of WeakReference.  This converts the item into a WeakReference if it is an object
429
	 * or contains an object in its callable.  This does not convert Closure into WeakReference.
430
	 * @param mixed $item item to add within the list.
431
	 * @param null|false|int $index index within the priority to add the item, defaults to null
432
	 *    which appends the item at the priority
433
	 * @param null|numeric $priority priority of the item.  defaults to null, which sets it
434
	 *    to the default priority
435
	 * @param bool $preserveCache preserveCache specifies if this is a special quick function
436
	 *    or not. This defaults to false.
437
	 * @throws \Prado\Exceptions\TInvalidDataValueException If the index specified exceeds the
438
	 *    bound
439
	 * @throws \Prado\Exceptions\TInvalidOperationException if the list is read-only
440
	 * @since 4.2.3
441
	 */
442
	protected function internalInsertAtIndexInPriority($item, $index = null, $priority = null, $preserveCache = false)
443
	{
444
		$this->ensureDiscardInvalid();
445
		$itemPriority = null;
446
		if (($isPriorityItem = ($item instanceof IPriorityItem)) && ($priority === null || !is_numeric($priority))) {
447
			$itemPriority = $priority = $item->getPriority();
448
		}
449
		$priority = $this->ensurePriority($priority);
450
		if (($item instanceof IPriorityCapture) && (!$isPriorityItem || $itemPriority !== $priority)) {
451
			$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

451
			$item->setPriority(/** @scrutinizer ignore-type */ $priority);
Loading history...
452
		}
453
		if (($isObj = is_object($item)) || is_array($item) && is_object($item[0])) {
454
			$this->weakAdd($isObj ? $item : $item[0]);
455
		}
456
		$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

456
		$this->filterItemForInput(/** @scrutinizer ignore-type */ $item, true);
Loading history...
457
		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

457
		return parent::insertAtIndexInPriority($item, $index, /** @scrutinizer ignore-type */ $priority, $preserveCache);
Loading history...
458
	}
459
460
	/**
461
	 * Removes an item from the priority list.
462
	 * The list will search for the item.  The first matching item found will be removed from
463
	 * the list.
464
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
465
	 * @param mixed $item item the item to be removed.
466
	 * @param null|bool|float $priority priority of item to remove. without this parameter it
467
	 *   defaults to false.  A value of false means any priority. null will be filled in with
468
	 *   the default priority.
469
	 * @throws TInvalidDataValueException If the item does not exist
470
	 * @return int index within the flattened list at which the item is being removed
471
	 * @since 4.2.3
472
	 */
473
	public function remove($item, $priority = false)
474
	{
475
		if ($this->getReadOnly()) {
476
			throw new TInvalidOperationException('list_readonly', $this::class);
477
		}
478
479
		if (($p = $this->priorityOf($item, true)) !== false) {
480
			if ($priority !== false) {
481
				$priority = $this->ensurePriority($priority);
482
				if ($p[0] != $priority) {
483
					throw new TInvalidDataValueException('list_item_inexistent');
484
				}
485
			}
486
			$this->internalRemoveAtIndexInPriority($p[1], $p[0]);
487
			return $p[2];
488
		} else {
489
			throw new TInvalidDataValueException('list_item_inexistent');
490
		}
491
	}
492
493
	/**
494
	 * Removes an item at the specified index in the flattened list.
495
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
496
	 * @param int $index index of the item to be removed.
497
	 * @throws TInvalidDataValueException If the index specified exceeds the bound
498
	 * @throws TInvalidOperationException if the list is read-only
499
	 * @return mixed the removed item.
500
	 * @since 4.2.3
501
	 */
502
	public function removeAt($index)
503
	{
504
		if ($this->getReadOnly()) {
505
			throw new TInvalidOperationException('list_readonly', $this::class);
506
		}
507
508
		if (($priority = $this->priorityAt($index, true)) !== false) {
509
			return $this->internalRemoveAtIndexInPriority($priority[1], $priority[0]);
510
		}
511
		throw new TInvalidDataValueException('list_index_invalid', $index);
512
	}
513
514
	/**
515
	 * Removes the item at a specific index within a priority.  This is needed to filter
516
	 * the output.
517
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
518
	 * @param int $index index of item to remove within the priority.
519
	 * @param null|numeric $priority priority of the item to remove, defaults to null,
520
	 *    or left blank, it is then set to the default priority
521
	 * @throws TInvalidDataValueException If the item does not exist
522
	 * @return mixed the removed item.
523
	 */
524
	public function removeAtIndexInPriority($index, $priority = null)
525
	{
526
		$this->scrubWeakReferences();
527
		return $this->internalRemoveAtIndexInPriority($index, $priority);
528
	}
529
530
	/**
531
	 * Removes the item at a specific index within a priority.  This is needed to filter
532
	 * the output.
533
	 * @param int $index index of item to remove within the priority.
534
	 * @param null|numeric $priority priority of the item to remove, defaults to null, or
535
	 *    left blank, it is then set to the default priority
536
	 * @throws TInvalidDataValueException If the item does not exist
537
	 * @return mixed the removed item.
538
	 * @since 4.2.3
539
	 */
540
	protected function internalRemoveAtIndexInPriority($index, $priority = null)
541
	{
542
		$item = parent::removeAtIndexInPriority($index, $priority);
543
		$this->filterItemForOutput($item);
544
		if (($isObj = is_object($item)) || is_array($item) && is_object($item[0])) {
545
			$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

545
			$this->weakRemove(/** @scrutinizer ignore-type */ $obj = $isObj ? $item : $item[0]);
Loading history...
546
		}
547
		return $item;
548
	}
549
550
	/**
551
	 * Removes all items in the priority list by calling removeAtIndexInPriority from the
552
	 * last item to the first.
553
	 * @since 4.2.3
554
	 */
555
	public function clear(): void
556
	{
557
		if ($this->getReadOnly()) {
558
			throw new TInvalidOperationException('list_readonly', get_class($this));
559
		}
560
561
		$c = $this->_c;
562
		foreach (array_keys($this->_d) as $priority) {
563
			for ($index = count($this->_d[$priority]) - 1; $index >= 0; $index--) {
564
				parent::removeAtIndexInPriority($index, $priority);
565
			}
566
		}
567
568
		if ($c) {
569
			$this->weakRestart();
570
		}
571
	}
572
573
	/**
574
	 * @param mixed $item the item
575
	 * @return bool whether the list contains the item
576
	 * @since 4.2.3
577
	 */
578
	public function contains($item): bool
579
	{
580
		$this->filterItemForInput($item);
581
		if ($this->_fd !== null) {
582
			return array_search($item, $this->_fd, true) !== false;
583
		} else {
584
			foreach (array_keys($this->_d) as $priority) {
585
				if (array_search($item, $this->_d[$priority], true) !== false) {
586
					return true;
587
				}
588
			}
589
		}
590
		return false;
591
	}
592
593
	/**
594
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
595
	 * @param mixed $item item being indexed.
596
	 * @return int the index of the item in the flattened list (0 based), -1 if not found.
597
	 */
598
	public function indexOf($item)
599
	{
600
		$this->flattenPriorities();
601
		$this->filterItemForInput($item);
602
		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

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