Passed
Push — master ( 30451f...036a09 )
by Fabio
07:34 queued 02:23
created

TWeakCallableCollection::scrubWeakReferences()   D

Complexity

Conditions 16
Paths 204

Size

Total Lines 36
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 16
eloc 25
nc 204
nop 0
dl 0
loc 36
rs 4.6833
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\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
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
			$this->_eventHandlerCount++;
142
		}
143
		return $this->weakAdd($object);
0 ignored issues
show
Bug introduced by
It seems like $object can also be of type null; however, parameter $object of Prado\Collections\TWeakC...leCollection::weakAdd() 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

143
		return $this->weakAdd(/** @scrutinizer ignore-type */ $object);
Loading history...
144
	}
145
146
	/**
147
	 * This is a custom function for removing objects to the weak map.  Specifically,
148
	 * if the object being removed is a TEventHandler, we use the {@link TEventHandler::getHandlerObject}
149
	 * object instead of the TEventHandler itself.
150
	 * @param object $object The object to remove to the managed weak map.
151
	 * @since 4.2.3
152
	 */
153
	protected function weakCustomRemove(object $object)
154
	{
155
		if($object instanceof TEventHandler) {
156
			$object = $object->getHandlerObject();
157
			$this->_eventHandlerCount--;
158
		}
159
		return $this->weakRemove($object);
0 ignored issues
show
Bug introduced by
It seems like $object can also be of type null; 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

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

520
			$item->setPriority(/** @scrutinizer ignore-type */ $priority);
Loading history...
521
		}
522
		if (($isObj = is_object($item)) || is_array($item) && is_object($item[0])) {
523
			$this->weakCustomAdd($isObj ? $item : $item[0]);
524
		}
525
		$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

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

526
		return parent::insertAtIndexInPriority($item, $index, /** @scrutinizer ignore-type */ $priority, $preserveCache);
Loading history...
527
	}
528
529
	/**
530
	 * Removes an item from the priority list.
531
	 * The list will search for the item.  The first matching item found will be removed from
532
	 * the list.
533
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
534
	 * @param mixed $item item the item to be removed.
535
	 * @param null|bool|float $priority priority of item to remove. without this parameter it
536
	 *   defaults to false.  A value of false means any priority. null will be filled in with
537
	 *   the default priority.
538
	 * @throws TInvalidDataValueException If the item does not exist
539
	 * @return int index within the flattened list at which the item is being removed
540
	 * @since 4.2.3
541
	 */
542
	public function remove($item, $priority = false)
543
	{
544
		if ($this->getReadOnly()) {
545
			throw new TInvalidOperationException('list_readonly', $this::class);
546
		}
547
548
		if (($p = $this->priorityOf($item, true)) !== false) {
549
			if ($priority !== false) {
550
				$priority = $this->ensurePriority($priority);
551
				if ($p[0] != $priority) {
552
					throw new TInvalidDataValueException('list_item_inexistent');
553
				}
554
			}
555
			$this->internalRemoveAtIndexInPriority($p[1], $p[0]);
556
			return $p[2];
557
		} else {
558
			throw new TInvalidDataValueException('list_item_inexistent');
559
		}
560
	}
561
562
	/**
563
	 * Removes an item at the specified index in the flattened list.
564
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
565
	 * @param int $index index of the item to be removed.
566
	 * @throws TInvalidDataValueException If the index specified exceeds the bound
567
	 * @throws TInvalidOperationException if the list is read-only
568
	 * @return mixed the removed item.
569
	 * @since 4.2.3
570
	 */
571
	public function removeAt($index)
572
	{
573
		if ($this->getReadOnly()) {
574
			throw new TInvalidOperationException('list_readonly', $this::class);
575
		}
576
577
		if (($priority = $this->priorityAt($index, true)) !== false) {
578
			return $this->internalRemoveAtIndexInPriority($priority[1], $priority[0]);
579
		}
580
		throw new TInvalidDataValueException('list_index_invalid', $index);
581
	}
582
583
	/**
584
	 * Removes the item at a specific index within a priority.  This is needed to filter
585
	 * the output.
586
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
587
	 * @param int $index index of item to remove within the priority.
588
	 * @param null|numeric $priority priority of the item to remove, defaults to null,
589
	 *    or left blank, it is then set to the default priority
590
	 * @throws TInvalidDataValueException If the item does not exist
591
	 * @return mixed the removed item.
592
	 */
593
	public function removeAtIndexInPriority($index, $priority = null)
594
	{
595
		$this->scrubWeakReferences();
596
		return $this->internalRemoveAtIndexInPriority($index, $priority);
597
	}
598
599
	/**
600
	 * Removes the item at a specific index within a priority.  This is needed to filter
601
	 * the output.
602
	 * @param int $index index of item to remove within the priority.
603
	 * @param null|numeric $priority priority of the item to remove, defaults to null, or
604
	 *    left blank, it is then set to the default priority
605
	 * @throws TInvalidDataValueException If the item does not exist
606
	 * @return mixed the removed item.
607
	 * @since 4.2.3
608
	 */
609
	protected function internalRemoveAtIndexInPriority($index, $priority = null)
610
	{
611
		$item = parent::removeAtIndexInPriority($index, $priority);
612
		$this->filterItemForOutput($item);
613
		if (($isObj = is_object($item)) || is_array($item) && is_object($item[0])) {
614
			$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

614
			$this->weakCustomRemove(/** @scrutinizer ignore-type */ $obj = $isObj ? $item : $item[0]);
Loading history...
615
		}
616
		return $item;
617
	}
618
619
	/**
620
	 * Removes all items in the priority list by calling removeAtIndexInPriority from the
621
	 * last item to the first.
622
	 * @since 4.2.3
623
	 */
624
	public function clear(): void
625
	{
626
		if ($this->getReadOnly()) {
627
			throw new TInvalidOperationException('list_readonly', get_class($this));
628
		}
629
630
		$c = $this->_c;
631
		foreach (array_keys($this->_d) as $priority) {
632
			for ($index = count($this->_d[$priority]) - 1; $index >= 0; $index--) {
633
				parent::removeAtIndexInPriority($index, $priority);
634
			}
635
		}
636
637
		if ($c) {
638
			$this->weakRestart();
639
		}
640
	}
641
642
	/**
643
	 * @param mixed $item the item
644
	 * @return bool whether the list contains the item
645
	 * @since 4.2.3
646
	 */
647
	public function contains($item): bool
648
	{
649
		return $this->indexOf($item) !== -1;
650
	}
651
652
	/**
653
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
654
	 * @param mixed $item item being indexed.
655
	 * @return int the index of the item in the flattened list (0 based), -1 if not found.
656
	 */
657
	public function indexOf($item)
658
	{
659
		$this->filterItemForInput($item);
660
		$this->flattenPriorities();
661
662
		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

662
		if (($index = array_search($item, /** @scrutinizer ignore-type */ $this->_fd, true)) === false && $this->_eventHandlerCount) {
Loading history...
663
			foreach($this->_fd as $index => $pItem) {
664
				if (($pItem instanceof TEventHandler) && $pItem->isSameHandler($item, true)) {
665
					break;
666
				}
667
				$index = false;
668
			}
669
		}
670
		if ($index === false) {
671
			return -1;
672
		} else {
673
			return $index;
674
		}
675
	}
676
677
	/**
678
	 * Returns the priority of a particular item.  This is needed to filter the input.
679
	 * All invalid WeakReference[s] are optionally removed from the list before indexing
680
	 * when $withindex is "true" due to the more complex processing.
681
	 * @param mixed $item the item to look for within the list.
682
	 * @param bool $withindex this specifies if the full positional data of the item
683
	 *   within the list is returned.  This defaults to false, if no parameter is provided,
684
	 *   so only provides the priority number of the item by default.
685
	 * @return array|false|numeric the priority of the item in the list, false if not found.
686
	 *   if withindex is true, an array is returned of [0 => $priority, 1 => $priorityIndex,
687
	 *    2 => flattenedIndex, 'priority' => $priority, 'index' => $priorityIndex,
688
	 *   'absindex' => flattenedIndex]
689
	 */
690
	public function priorityOf($item, $withindex = false)
691
	{
692
		if ($withindex) {
693
			$this->scrubWeakReferences();
694
		}
695
		$this->filterItemForInput($item);
696
		$this->sortPriorities();
697
698
		$absindex = 0;
699
		foreach (array_keys($this->_d) as $priority) {
700
			if (($index = array_search($item, $this->_d[$priority], true)) !== false) {
701
				$absindex += $index;
702
				return $withindex ? [$priority, $index, $absindex,
703
						'priority' => $priority, 'index' => $index, 'absindex' => $absindex, ] : $priority;
704
			} else {
705
				$absindex += count($this->_d[$priority]);
706
			}
707
		}
708
		if (!$this->_eventHandlerCount) {
709
			return false;
710
		}
711
712
		$absindex = 0;
713
		foreach (array_keys($this->_d) as $priority) {
714
			$index = false;
715
			foreach($this->_d[$priority] as $index => $pItem) {
716
				if(($pItem instanceof TEventHandler) && $pItem->isSameHandler($item, true)) {
717
					break;
718
				}
719
				$index = false;
720
			}
721
			if ($index !== false) {
722
				$absindex += $index;
723
				return $withindex ? [$priority, $index, $absindex,
724
						'priority' => $priority, 'index' => $index, 'absindex' => $absindex, ] : $priority;
725
			} else {
726
				$absindex += count($this->_d[$priority]);
727
			}
728
		}
729
730
		return false;
731
	}
732
733
	/**
734
	 * Returns the priority of an item at a particular flattened index.  The index after
735
	 * the last item does not exist but receives a priority from the last item so that
736
	 * priority information about any new items being appended is available.
737
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
738
	 * @param int $index index of the item within the list
739
	 * @param bool $withindex this specifies if the full positional data of the item
740
	 *   within the list is returned.  This defaults to false, if no parameter is provided,
741
	 *   so only provides the priority number of the item by default.
742
	 * @return array|false|numeric the priority of the item in the list, false if not found.
743
	 *   if $withindex is true, an array is returned of [0 => $priority, 1 => $priorityIndex,
744
	 *   2 => flattenedIndex, 'priority' => $priority, 'index' => $priorityIndex, 'absindex'
745
	 *   => flattenedIndex]
746
	 * @since 4.2.3
747
	 */
748
	public function priorityAt($index, $withindex = false)
749
	{
750
		$this->scrubWeakReferences();
751
		return parent::priorityAt($index, $withindex);
752
	}
753
754
	/**
755
	 * This inserts an item before another item within the list.  It uses the same priority
756
	 * as the found index item and places the new item before it.
757
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
758
	 * @param mixed $indexitem the item to index
759
	 * @param mixed $item the item to add before indexitem
760
	 * @throws TInvalidDataValueException If the item does not exist
761
	 * @return int where the item has been inserted in the flattened list
762
	 * @since 4.2.3
763
	 */
764
	public function insertBefore($indexitem, $item)
765
	{
766
		if ($this->getReadOnly()) {
767
			throw new TInvalidOperationException('list_readonly', $this::class);
768
		}
769
770
		if (($priority = $this->priorityOf($indexitem, true)) === false) {
771
			throw new TInvalidDataValueException('list_item_inexistent');
772
		}
773
774
		$this->internalInsertAtIndexInPriority($item, $priority[1], $priority[0]);
775
776
		return $priority[2];
777
	}
778
779
	/**
780
	 * This inserts an item after another item within the list.  It uses the same priority
781
	 * as the found index item and places the new item after it.
782
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
783
	 * @param mixed $indexitem the item to index
784
	 * @param mixed $item the item to add after indexitem
785
	 * @throws TInvalidDataValueException If the item does not exist
786
	 * @return int where the item has been inserted in the flattened list
787
	 * @since 4.2.3
788
	 */
789
	public function insertAfter($indexitem, $item)
790
	{
791
		if ($this->getReadOnly()) {
792
			throw new TInvalidOperationException('list_readonly', $this::class);
793
		}
794
795
		if (($priority = $this->priorityOf($indexitem, true)) === false) {
796
			throw new TInvalidDataValueException('list_item_inexistent');
797
		}
798
799
		$this->internalInsertAtIndexInPriority($item, $priority[1] + 1, $priority[0]);
800
801
		return $priority[2] + 1;
802
	}
803
804
	/**
805
	 * All invalid WeakReference[s] are optionally removed from the list before returning.
806
	 * @return array the priority list of items in array
807
	 * @since 4.2.3
808
	 */
809
	public function toArray(): array
810
	{
811
		$this->flattenPriorities();
812
		$items = $this->_fd;
813
		$this->filterItemsForOutput($items);
814
		return $items;
815
	}
816
817
818
	/**
819
	 * All invalid WeakReference[s] are optionally removed from the list before returning.
820
	 * @return array the array of priorities keys with values of arrays of callables.
821
	 *   The priorities are sorted so important priorities, lower numerics, are first.
822
	 */
823
	public function toPriorityArray(): array
824
	{
825
		$this->scrubWeakReferences();
826
		$result = parent::toPriorityArray();
827
		foreach (array_keys($result) as $key) {
828
			$this->filterItemsForOutput($result[$key]);
829
		}
830
		return $result;
831
	}
832
833
834
	/**
835
	 * All invalid WeakReference[s] are optionally removed from the list before returning.
836
	 * @return array the array of priorities keys with values of arrays of callables with
837
	 *   WeakReference rather than objects.  The priorities are sorted so important priorities,
838
	 *   lower numerics, are first.
839
	 */
840
	public function toPriorityArrayWeak()
841
	{
842
		$this->scrubWeakReferences();
843
		return parent::toPriorityArray();
844
	}
845
846
	/**
847
	 * Combines the map elements which have a priority below the parameter value.  This
848
	 * is needed to filter the output.
849
	 * All invalid WeakReference[s] are optionally removed from the list before returning.
850
	 * @param numeric $priority the cut-off priority.  All items of priority less than
851
	 *   this are returned.
852
	 * @param bool $inclusive whether or not the input cut-off priority is inclusive.
853
	 *   Default: false, not inclusive.
854
	 * @return array the array of priorities keys with values of arrays of items that
855
	 *   are below a specified priority.  The priorities are sorted so important priorities,
856
	 *   lower numerics, are first.
857
	 * @since 4.2.3
858
	 */
859
	public function toArrayBelowPriority($priority, bool $inclusive = false): array
860
	{
861
		$this->scrubWeakReferences();
862
		$items = parent::toArrayBelowPriority($priority, $inclusive);
863
		$this->filterItemsForOutput($items);
864
		return $items;
865
	}
866
867
	/**
868
	 * Combines the map elements which have a priority above the parameter value. This
869
	 * is needed to filter the output.
870
	 * All invalid WeakReference[s] are optionally removed from the list before returning.
871
	 * @param numeric $priority the cut-off priority.  All items of priority greater
872
	 *   than this are returned.
873
	 * @param bool $inclusive whether or not the input cut-off priority is inclusive.
874
	 *   Default: true, inclusive.
875
	 * @return array the array of priorities keys with values of arrays of items that
876
	 *   are above a specified priority.  The priorities are sorted so important priorities,
877
	 *   lower numerics, are first.
878
	 * @since 4.2.3
879
	 */
880
	public function toArrayAbovePriority($priority, bool $inclusive = true): array
881
	{
882
		$this->scrubWeakReferences();
883
		$items = parent::toArrayAbovePriority($priority, $inclusive);
884
		$this->filterItemsForOutput($items);
885
		return $items;
886
	}
887
888
	/**
889
	 * Copies iterable data into the list.
890
	 * Note, existing data in the list will be cleared first.
891
	 * @param mixed $data the data to be copied from, must be an array or object implementing
892
	 *   Traversable
893
	 * @throws TInvalidDataTypeException If data is neither an array nor a Traversable.
894
	 * @since 4.2.3
895
	 */
896
	public function copyFrom($data): void
897
	{
898
		if ($data instanceof TPriorityList) {
899
			if ($this->_c > 0) {
900
				$this->clear();
901
			}
902
			$array = $data->toPriorityArray();
903
			foreach (array_keys($array) as $priority) {
904
				for ($i = 0, $c = count($array[$priority]); $i < $c; $i++) {
905
					$this->internalInsertAtIndexInPriority($array[$priority][$i], null, $priority);
906
				}
907
			}
908
		} elseif ($data instanceof TPriorityMap) {
909
			if ($this->_c > 0) {
910
				$this->clear();
911
			}
912
			$array = $data->toPriorityArray();
913
			foreach (array_keys($array) as $priority) {
914
				foreach ($array[$priority] as $item) {
915
					$this->internalInsertAtIndexInPriority($item, null, $priority);
916
				}
917
			}
918
		} elseif (is_array($data) || ($data instanceof Traversable)) {
919
			if ($this->_c > 0) {
920
				$this->clear();
921
			}
922
			foreach ($data as $item) {
923
				$this->internalInsertAtIndexInPriority($item);
924
			}
925
		} elseif ($data !== null) {
926
			throw new TInvalidDataTypeException('list_data_not_iterable');
927
		}
928
	}
929
930
	/**
931
	 * Merges iterable data into the priority list.
932
	 * New data will be appended to the end of the existing data.  If another TPriorityList
933
	 * is merged, the incoming parameter items will be appended at the priorities they are
934
	 * present.  These items will be added to the end of the existing items with equal
935
	 * priorities, if there are any.
936
	 * @param mixed $data the data to be merged with, must be an array or object implementing
937
	 *   Traversable
938
	 * @throws TInvalidDataTypeException If data is neither an array nor an iterator.
939
	 * @since 4.2.3
940
	 */
941
	public function mergeWith($data): void
942
	{
943
		if ($data instanceof TPriorityList) {
944
			$array = $data->toPriorityArray();
945
			foreach (array_keys($array) as $priority) {
946
				for ($i = 0, $c = count($array[$priority]); $i < $c; $i++) {
947
					$this->internalInsertAtIndexInPriority($array[$priority][$i], null, $priority);
948
				}
949
			}
950
		} elseif ($data instanceof TPriorityMap) {
951
			$array = $data->toPriorityArray();
952
			foreach (array_keys($array) as $priority) {
953
				foreach ($array[$priority] as $item) {
954
					$this->internalInsertAtIndexInPriority($item, null, $priority);
955
				}
956
			}
957
		} elseif (is_array($data) || ($data instanceof Traversable)) {
958
			foreach ($data as $item) {
959
				$this->internalInsertAtIndexInPriority($item);
960
			}
961
		} elseif ($data !== null) {
962
			throw new TInvalidDataTypeException('list_data_not_iterable');
963
		}
964
	}
965
966
	/**
967
	 * Sets the element at the specified offset. This method is required by the interface
968
	 * \ArrayAccess.  Setting elements in a priority list is not straight forword when
969
	 * appending and setting at the end boundary.  When appending without an offset (a
970
	 * null offset), the item will be added at the default priority.  The item may not be
971
	 * the last item in the list.  When appending with an offset equal to the count of the
972
	 * list, the item will get be appended with the last items priority.
973
	 *
974
	 * All together, when setting the location of an item, the item stays in that location,
975
	 * but appending an item into a priority list doesn't mean the item is at the end of
976
	 * the list.
977
	 *
978
	 * All invalid WeakReference[s] are optionally removed from the list when an $offset
979
	 * is given.
980
	 * @param int $offset the offset to set element
981
	 * @param mixed $item the element value
982
	 * @since 4.2.3
983
	 */
984
	public function offsetSet($offset, $item): void
985
	{
986
		if ($this->getReadOnly()) {
987
			throw new TInvalidOperationException('list_readonly', $this::class);
988
		}
989
990
		if ($offset === null) {
0 ignored issues
show
introduced by
The condition $offset === null is always false.
Loading history...
991
			$this->internalInsertAtIndexInPriority($item, null, null, true);
992
			return;
993
		}
994
		if (0 <= $offset && $offset <= ($count = $this->getCount())) {
995
			$priority = parent::priorityAt($offset, true);
996
			if ($offset !== $count) {
997
				$this->internalRemoveAtIndexInPriority($priority[1], $priority[0]);
998
			}
999
		} else {
1000
			throw new TInvalidDataValueException('list_index_invalid', $offset);
1001
		}
1002
		$this->internalInsertAtIndexInPriority($item, $priority[1], $priority[0]);
1003
	}
1004
1005
	/**
1006
	 * Returns an array with the names of all variables of this object that should
1007
	 * NOT be serialized because their value is the default one or useless to be cached
1008
	 * for the next page loads.  Reimplement in derived classes to add new variables,
1009
	 * but remember to  also to call the parent implementation first.
1010
	 * @param array $exprops by reference
1011
	 * @since 4.2.3
1012
	 */
1013
	protected function _getZappableSleepProps(&$exprops)
1014
	{
1015
		$c = $this->_c;
1016
		$this->_c = 0;
1017
		parent::_getZappableSleepProps($exprops);
1018
		$this->_c = $c;
1019
1020
		$this->_weakZappableSleepProps($exprops);
1021
		if ($this->_discardInvalid === null) {
1022
			$exprops[] = "\0" . __CLASS__ . "\0_discardInvalid";
1023
		}
1024
	}
1025
}
1026