Passed
Push — master ( 246025...18c029 )
by Fabio
05:31
created

TWeakCallableCollection::remove()   B

Complexity

Conditions 8
Paths 8

Size

Total Lines 34
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

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

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

534
				$item->setPriority(/** @scrutinizer ignore-type */ $priority);
Loading history...
535
			}
536
			if (($isObj = is_object($item)) || is_array($item) && is_object($item[0])) {
537
				$this->weakCustomAdd($isObj ? $item : $item[0]);
538
			}
539
			$this->filterItemForInput($item, true);
0 ignored issues
show
Bug introduced by
$item of type Prado\Collections\IPrior...ctions\IPriorityCapture is incompatible with the type callable expected by parameter $handler of Prado\Collections\TWeakC...n::filterItemForInput(). ( Ignorable by Annotation )

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

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

540
			$result = parent::insertAtIndexInPriority($item, $index, /** @scrutinizer ignore-type */ $priority, $preserveCache);
Loading history...
541
			if ($return === null) {
542
				$return = $result;
543
			} elseif(!is_array($return)) {
544
				$return = [$return, $result];
545
			} else {
546
				$return[] = $result;
547
			}
548
			if (is_int($index)) {
549
				$index++;
550
			}
551
		}
552
		return $return;
553
	}
554
555
	/**
556
	 * Removes an item from the priority list.
557
	 * The list will search for the item.  The first matching item found will be removed from
558
	 * the list.
559
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
560
	 * @param mixed $item item the item to be removed.
561
	 * @param null|bool|float $priority priority of item to remove. without this parameter it
562
	 *   defaults to false.  A value of false means any priority. null will be filled in with
563
	 *   the default priority.
564
	 * @throws TInvalidDataValueException If the item does not exist
565
	 * @return int index within the flattened list at which the item is being removed
566
	 * @since 4.2.3
567
	 */
568
	public function remove($item, $priority = false)
569
	{
570
		if ($this->getReadOnly()) {
571
			throw new TInvalidOperationException('list_readonly', $this::class);
572
		}
573
574
		if ($priority !== false) {
575
			$this->filterItemForInput($item);
576
			$this->sortPriorities();
577
578
			$priority = $this->ensurePriority($priority);
579
580
			$absindex = 0;
581
			foreach (array_keys($this->_d) as $p) {
582
				if ($p < $priority) {
583
					$absindex += count($this->_d[$p]);
584
					continue;
585
				} elseif ($p == $priority) {
586
					if (($index = array_search($item, $this->_d[$p], true)) !== false) {
587
						$absindex += $index;
588
						$this->removeAtIndexInPriority($index, $p);
0 ignored issues
show
Bug introduced by
It seems like $index can also be of type string; however, parameter $index of Prado\Collections\TWeakC...moveAtIndexInPriority() does only seem to accept integer, 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

588
						$this->removeAtIndexInPriority(/** @scrutinizer ignore-type */ $index, $p);
Loading history...
589
						return $absindex;
590
					}
591
				}
592
				break;
593
			}
594
			throw new TInvalidDataValueException('list_item_inexistent');
595
		}
596
597
		if (($p = $this->priorityOf($item, true)) !== false) {
598
			$this->internalRemoveAtIndexInPriority($p[1], $p[0]);
599
			return $p[2];
600
		} else {
601
			throw new TInvalidDataValueException('list_item_inexistent');
602
		}
603
	}
604
605
	/**
606
	 * Removes an item at the specified index in the flattened list.
607
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
608
	 * @param int $index index of the item to be removed.
609
	 * @throws TInvalidDataValueException If the index specified exceeds the bound
610
	 * @throws TInvalidOperationException if the list is read-only
611
	 * @return mixed the removed item.
612
	 * @since 4.2.3
613
	 */
614
	public function removeAt($index)
615
	{
616
		if ($this->getReadOnly()) {
617
			throw new TInvalidOperationException('list_readonly', $this::class);
618
		}
619
620
		if (($priority = $this->priorityAt($index, true)) !== false) {
621
			return $this->internalRemoveAtIndexInPriority($priority[1], $priority[0]);
622
		}
623
		throw new TInvalidDataValueException('list_index_invalid', $index);
624
	}
625
626
	/**
627
	 * Removes the item at a specific index within a priority.  This is needed to filter
628
	 * the output.
629
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
630
	 * @param int $index index of item to remove within the priority.
631
	 * @param null|numeric $priority priority of the item to remove, defaults to null,
632
	 *    or left blank, it is then set to the default priority
633
	 * @throws TInvalidDataValueException If the item does not exist
634
	 * @return mixed the removed item.
635
	 */
636
	public function removeAtIndexInPriority($index, $priority = null)
637
	{
638
		$this->scrubWeakReferences();
639
		return $this->internalRemoveAtIndexInPriority($index, $priority);
640
	}
641
642
	/**
643
	 * Removes the item at a specific index within a priority.  This is needed to filter
644
	 * the output.
645
	 * @param int $index index of item to remove within the priority.
646
	 * @param null|numeric $priority priority of the item to remove, defaults to null, or
647
	 *    left blank, it is then set to the default priority
648
	 * @throws TInvalidDataValueException If the item does not exist
649
	 * @return mixed the removed item.
650
	 * @since 4.2.3
651
	 */
652
	protected function internalRemoveAtIndexInPriority($index, $priority = null)
653
	{
654
		$item = parent::removeAtIndexInPriority($index, $priority);
655
		$this->filterItemForOutput($item);
656
		if (($isObj = is_object($item)) || is_array($item) && is_object($item[0])) {
657
			$this->weakCustomRemove($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...ion::weakCustomRemove() 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

657
			$this->weakCustomRemove(/** @scrutinizer ignore-type */ $obj = $isObj ? $item : $item[0]);
Loading history...
658
		}
659
		return $item;
660
	}
661
662
	/**
663
	 * Removes all items in the priority list by calling removeAtIndexInPriority from the
664
	 * last item to the first.
665
	 * @since 4.2.3
666
	 */
667
	public function clear(): void
668
	{
669
		if ($this->getReadOnly()) {
670
			throw new TInvalidOperationException('list_readonly', get_class($this));
671
		}
672
673
		$c = $this->_c;
674
		foreach (array_keys($this->_d) as $priority) {
675
			for ($index = count($this->_d[$priority]) - 1; $index >= 0; $index--) {
676
				parent::removeAtIndexInPriority($index, $priority);
677
			}
678
		}
679
680
		if ($c) {
681
			$this->weakRestart();
682
		}
683
	}
684
685
	/**
686
	 * @param mixed $item the item
687
	 * @return bool whether the list contains the item
688
	 * @since 4.2.3
689
	 */
690
	public function contains($item): bool
691
	{
692
		return $this->indexOf($item) !== -1;
693
	}
694
695
	/**
696
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
697
	 * @param mixed $item item being indexed.
698
	 * @param mixed $priority
699
	 * @return int the index of the item in the flattened list (0 based), -1 if not found.
700
	 */
701
	public function indexOf($item, $priority = false)
702
	{
703
		$this->filterItemForInput($item);
704
		if ($priority !== false) {
705
			$this->sortPriorities();
706
707
			$priority = $this->ensurePriority($priority);
708
709
			$absindex = 0;
710
			foreach (array_keys($this->_d) as $p) {
711
				if ($p < $priority) {
712
					$absindex += count($this->_d[$p]);
713
					continue;
714
				} elseif ($p == $priority) {
715
					$index = false;
716
					foreach($this->_d[$p] as $index => $pItem) {
717
						if ($item === $pItem || ($pItem instanceof TEventHandler) && $pItem->isSameHandler($item, true)) {
718
							break;
719
						}
720
						$index = false;
721
					}
722
					if ($index !== false) {
723
						$absindex += $index;
724
						return $absindex;
725
					}
726
				}
727
				return -1;
728
			}
729
			return -1;
730
		}
731
		$this->flattenPriorities();
732
733
		if (($index = array_search($item, $this->_fd, true)) === false && $this->_eventHandlerCount) {
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

733
		if (($index = array_search($item, /** @scrutinizer ignore-type */ $this->_fd, true)) === false && $this->_eventHandlerCount) {
Loading history...
734
			foreach($this->_fd as $index => $pItem) {
735
				if (($pItem instanceof TEventHandler) && $pItem->isSameHandler($item, true)) {
736
					break;
737
				}
738
				$index = false;
739
			}
740
		}
741
		if ($index === false) {
742
			return -1;
743
		} else {
744
			return $index;
745
		}
746
	}
747
748
	/**
749
	 * Returns the priority of a particular item.  This is needed to filter the input.
750
	 * All invalid WeakReference[s] are optionally removed from the list before indexing
751
	 * when $withindex is "true" due to the more complex processing.
752
	 * @param mixed $item the item to look for within the list.
753
	 * @param bool $withindex this specifies if the full positional data of the item
754
	 *   within the list is returned.  This defaults to false, if no parameter is provided,
755
	 *   so only provides the priority number of the item by default.
756
	 * @return array|false|numeric the priority of the item in the list, false if not found.
757
	 *   if withindex is true, an array is returned of [0 => $priority, 1 => $priorityIndex,
758
	 *    2 => flattenedIndex, 'priority' => $priority, 'index' => $priorityIndex,
759
	 *   'absindex' => flattenedIndex]
760
	 */
761
	public function priorityOf($item, $withindex = false)
762
	{
763
		if ($withindex) {
764
			$this->scrubWeakReferences();
765
		}
766
		$this->filterItemForInput($item);
767
		$this->sortPriorities();
768
769
		$absindex = 0;
770
		foreach (array_keys($this->_d) as $priority) {
771
			if (($index = array_search($item, $this->_d[$priority], true)) !== false) {
772
				$absindex += $index;
773
				return $withindex ? [$priority, $index, $absindex,
774
						'priority' => $priority, 'index' => $index, 'absindex' => $absindex, ] : $priority;
775
			} else {
776
				$absindex += count($this->_d[$priority]);
777
			}
778
		}
779
		if (!$this->_eventHandlerCount) {
780
			return false;
781
		}
782
783
		$absindex = 0;
784
		foreach (array_keys($this->_d) as $priority) {
785
			$index = false;
786
			foreach($this->_d[$priority] as $index => $pItem) {
787
				if(($pItem instanceof TEventHandler) && $pItem->isSameHandler($item, true)) {
788
					break;
789
				}
790
				$index = false;
791
			}
792
			if ($index !== false) {
793
				$absindex += $index;
794
				return $withindex ? [$priority, $index, $absindex,
795
						'priority' => $priority, 'index' => $index, 'absindex' => $absindex, ] : $priority;
796
			} else {
797
				$absindex += count($this->_d[$priority]);
798
			}
799
		}
800
801
		return false;
802
	}
803
804
	/**
805
	 * Returns the priority of an item at a particular flattened index.  The index after
806
	 * the last item does not exist but receives a priority from the last item so that
807
	 * priority information about any new items being appended is available.
808
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
809
	 * @param int $index index of the item within the list
810
	 * @param bool $withindex this specifies if the full positional data of the item
811
	 *   within the list is returned.  This defaults to false, if no parameter is provided,
812
	 *   so only provides the priority number of the item by default.
813
	 * @return array|false|numeric the priority of the item in the list, false if not found.
814
	 *   if $withindex is true, an array is returned of [0 => $priority, 1 => $priorityIndex,
815
	 *   2 => flattenedIndex, 'priority' => $priority, 'index' => $priorityIndex, 'absindex'
816
	 *   => flattenedIndex]
817
	 * @since 4.2.3
818
	 */
819
	public function priorityAt($index, $withindex = false)
820
	{
821
		$this->scrubWeakReferences();
822
		return parent::priorityAt($index, $withindex);
0 ignored issues
show
Bug Best Practice introduced by
The expression return parent::priorityAt($index, $withindex) also could return the type array which is incompatible with the return type mandated by Prado\Collections\IPrior...ollection::priorityAt() of Prado\Collections\numeric|false.
Loading history...
823
	}
824
825
	/**
826
	 * This inserts an item before another item within the list.  It uses the same priority
827
	 * as the found index item and places the new item before it.
828
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
829
	 * @param mixed $indexitem the item to index
830
	 * @param mixed $item the item to add before indexitem
831
	 * @throws TInvalidDataValueException If the item does not exist
832
	 * @return int where the item has been inserted in the flattened list
833
	 * @since 4.2.3
834
	 */
835
	public function insertBefore($indexitem, $item)
836
	{
837
		if ($this->getReadOnly()) {
838
			throw new TInvalidOperationException('list_readonly', $this::class);
839
		}
840
841
		if (($priority = $this->priorityOf($indexitem, true)) === false) {
842
			throw new TInvalidDataValueException('list_item_inexistent');
843
		}
844
845
		$this->internalInsertAtIndexInPriority($item, $priority[1], $priority[0]);
846
847
		return $priority[2];
848
	}
849
850
	/**
851
	 * This inserts an item after another item within the list.  It uses the same priority
852
	 * as the found index item and places the new item after it.
853
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
854
	 * @param mixed $indexitem the item to index
855
	 * @param mixed $item the item to add after indexitem
856
	 * @throws TInvalidDataValueException If the item does not exist
857
	 * @return int where the item has been inserted in the flattened list
858
	 * @since 4.2.3
859
	 */
860
	public function insertAfter($indexitem, $item)
861
	{
862
		if ($this->getReadOnly()) {
863
			throw new TInvalidOperationException('list_readonly', $this::class);
864
		}
865
866
		if (($priority = $this->priorityOf($indexitem, true)) === false) {
867
			throw new TInvalidDataValueException('list_item_inexistent');
868
		}
869
870
		$this->internalInsertAtIndexInPriority($item, $priority[1] + 1, $priority[0]);
871
872
		return $priority[2] + 1;
873
	}
874
875
	/**
876
	 * All invalid WeakReference[s] are optionally removed from the list before returning.
877
	 * @return array the priority list of items in array
878
	 * @since 4.2.3
879
	 */
880
	public function toArray(): array
881
	{
882
		$this->flattenPriorities();
883
		$items = $this->_fd;
884
		$this->filterItemsForOutput($items);
885
		return $items;
886
	}
887
888
889
	/**
890
	 * All invalid WeakReference[s] are optionally removed from the list before returning.
891
	 * @return array the array of priorities keys with values of arrays of callables.
892
	 *   The priorities are sorted so important priorities, lower numerics, are first.
893
	 */
894
	public function toPriorityArray(): array
895
	{
896
		$this->scrubWeakReferences();
897
		$result = parent::toPriorityArray();
898
		foreach (array_keys($result) as $key) {
899
			$this->filterItemsForOutput($result[$key]);
900
		}
901
		return $result;
902
	}
903
904
905
	/**
906
	 * All invalid WeakReference[s] are optionally removed from the list before returning.
907
	 * @return array the array of priorities keys with values of arrays of callables with
908
	 *   WeakReference rather than objects.  The priorities are sorted so important priorities,
909
	 *   lower numerics, are first.
910
	 */
911
	public function toPriorityArrayWeak()
912
	{
913
		$this->scrubWeakReferences();
914
		return parent::toPriorityArray();
915
	}
916
917
	/**
918
	 * Combines the map elements which have a priority below the parameter value.  This
919
	 * is needed to filter the output.
920
	 * All invalid WeakReference[s] are optionally removed from the list before returning.
921
	 * @param numeric $priority the cut-off priority.  All items of priority less than
922
	 *   this are returned.
923
	 * @param bool $inclusive whether or not the input cut-off priority is inclusive.
924
	 *   Default: false, not inclusive.
925
	 * @return array the array of priorities keys with values of arrays of items that
926
	 *   are below a specified priority.  The priorities are sorted so important priorities,
927
	 *   lower numerics, are first.
928
	 * @since 4.2.3
929
	 */
930
	public function toArrayBelowPriority($priority, bool $inclusive = false): array
931
	{
932
		$this->scrubWeakReferences();
933
		$items = parent::toArrayBelowPriority($priority, $inclusive);
934
		$this->filterItemsForOutput($items);
935
		return $items;
936
	}
937
938
	/**
939
	 * Combines the map elements which have a priority above the parameter value. This
940
	 * is needed to filter the output.
941
	 * All invalid WeakReference[s] are optionally removed from the list before returning.
942
	 * @param numeric $priority the cut-off priority.  All items of priority greater
943
	 *   than this are returned.
944
	 * @param bool $inclusive whether or not the input cut-off priority is inclusive.
945
	 *   Default: true, inclusive.
946
	 * @return array the array of priorities keys with values of arrays of items that
947
	 *   are above a specified priority.  The priorities are sorted so important priorities,
948
	 *   lower numerics, are first.
949
	 * @since 4.2.3
950
	 */
951
	public function toArrayAbovePriority($priority, bool $inclusive = true): array
952
	{
953
		$this->scrubWeakReferences();
954
		$items = parent::toArrayAbovePriority($priority, $inclusive);
955
		$this->filterItemsForOutput($items);
956
		return $items;
957
	}
958
959
	/**
960
	 * Copies iterable data into the list.
961
	 * Note, existing data in the list will be cleared first.
962
	 * @param mixed $data the data to be copied from, must be an array or object implementing
963
	 *   Traversable
964
	 * @throws TInvalidDataTypeException If data is neither an array nor a Traversable.
965
	 * @since 4.2.3
966
	 */
967
	public function copyFrom($data): void
968
	{
969
		if ($data instanceof TPriorityList) {
970
			if ($this->_c > 0) {
971
				$this->clear();
972
			}
973
			$array = $data->toPriorityArray();
974
			foreach (array_keys($array) as $priority) {
975
				for ($i = 0, $c = count($array[$priority]); $i < $c; $i++) {
976
					$this->internalInsertAtIndexInPriority($array[$priority][$i], null, $priority);
977
				}
978
			}
979
		} elseif ($data instanceof TPriorityMap) {
980
			if ($this->_c > 0) {
981
				$this->clear();
982
			}
983
			$array = $data->toPriorityArray();
984
			foreach (array_keys($array) as $priority) {
985
				foreach ($array[$priority] as $item) {
986
					$this->internalInsertAtIndexInPriority($item, null, $priority);
987
				}
988
			}
989
		} elseif (is_array($data) || ($data instanceof Traversable)) {
990
			if ($this->_c > 0) {
991
				$this->clear();
992
			}
993
			foreach ($data as $item) {
994
				$this->internalInsertAtIndexInPriority($item);
995
			}
996
		} elseif ($data !== null) {
997
			throw new TInvalidDataTypeException('list_data_not_iterable');
998
		}
999
	}
1000
1001
	/**
1002
	 * Merges iterable data into the priority list.
1003
	 * New data will be appended to the end of the existing data.  If another TPriorityList
1004
	 * is merged, the incoming parameter items will be appended at the priorities they are
1005
	 * present.  These items will be added to the end of the existing items with equal
1006
	 * priorities, if there are any.
1007
	 * @param mixed $data the data to be merged with, must be an array or object implementing
1008
	 *   Traversable
1009
	 * @throws TInvalidDataTypeException If data is neither an array nor an iterator.
1010
	 * @since 4.2.3
1011
	 */
1012
	public function mergeWith($data): void
1013
	{
1014
		if ($data instanceof TPriorityList) {
1015
			$array = $data->toPriorityArray();
1016
			foreach (array_keys($array) as $priority) {
1017
				for ($i = 0, $c = count($array[$priority]); $i < $c; $i++) {
1018
					$this->internalInsertAtIndexInPriority($array[$priority][$i], null, $priority);
1019
				}
1020
			}
1021
		} elseif ($data instanceof TPriorityMap) {
1022
			$array = $data->toPriorityArray();
1023
			foreach (array_keys($array) as $priority) {
1024
				foreach ($array[$priority] as $item) {
1025
					$this->internalInsertAtIndexInPriority($item, null, $priority);
1026
				}
1027
			}
1028
		} elseif (is_array($data) || ($data instanceof Traversable)) {
1029
			foreach ($data as $item) {
1030
				$this->internalInsertAtIndexInPriority($item);
1031
			}
1032
		} elseif ($data !== null) {
1033
			throw new TInvalidDataTypeException('list_data_not_iterable');
1034
		}
1035
	}
1036
1037
	/**
1038
	 * Sets the element at the specified offset. This method is required by the interface
1039
	 * \ArrayAccess.  Setting elements in a priority list is not straight forword when
1040
	 * appending and setting at the end boundary.  When appending without an offset (a
1041
	 * null offset), the item will be added at the default priority.  The item may not be
1042
	 * the last item in the list.  When appending with an offset equal to the count of the
1043
	 * list, the item will get be appended with the last items priority.
1044
	 *
1045
	 * All together, when setting the location of an item, the item stays in that location,
1046
	 * but appending an item into a priority list doesn't mean the item is at the end of
1047
	 * the list.
1048
	 *
1049
	 * All invalid WeakReference[s] are optionally removed from the list when an $offset
1050
	 * is given.
1051
	 * @param int $offset the offset to set element
1052
	 * @param mixed $item the element value
1053
	 * @since 4.2.3
1054
	 */
1055
	public function offsetSet($offset, $item): void
1056
	{
1057
		if ($this->getReadOnly()) {
1058
			throw new TInvalidOperationException('list_readonly', $this::class);
1059
		}
1060
1061
		if ($offset === null) {
0 ignored issues
show
introduced by
The condition $offset === null is always false.
Loading history...
1062
			$this->internalInsertAtIndexInPriority($item, null, null, true);
1063
			return;
1064
		}
1065
		if (0 <= $offset && $offset <= ($count = $this->getCount())) {
1066
			$priority = parent::priorityAt($offset, true);
1067
			if ($offset !== $count) {
1068
				$this->internalRemoveAtIndexInPriority($priority[1], $priority[0]);
1069
			}
1070
		} else {
1071
			throw new TInvalidDataValueException('list_index_invalid', $offset);
1072
		}
1073
		$this->internalInsertAtIndexInPriority($item, $priority[1], $priority[0]);
1074
	}
1075
1076
	/**
1077
	 * Returns an array with the names of all variables of this object that should
1078
	 * NOT be serialized because their value is the default one or useless to be cached
1079
	 * for the next page loads.  Reimplement in derived classes to add new variables,
1080
	 * but remember to  also to call the parent implementation first.
1081
	 * @param array $exprops by reference
1082
	 * @since 4.2.3
1083
	 */
1084
	protected function _getZappableSleepProps(&$exprops)
1085
	{
1086
		$c = $this->_c;
1087
		$this->_c = 0;
1088
		parent::_getZappableSleepProps($exprops);
1089
		$this->_c = $c;
1090
1091
		$this->_weakZappableSleepProps($exprops);
1092
		if ($this->_discardInvalid === null) {
1093
			$exprops[] = "\0" . __CLASS__ . "\0_discardInvalid";
1094
		}
1095
	}
1096
}
1097