Passed
Push — master ( d1cca9...5f4a0f )
by Fabio
06:13
created

internalInsertAtIndexInPriority()   C

Complexity

Conditions 17
Paths 99

Size

Total Lines 35
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 17
eloc 26
nc 99
nop 4
dl 0
loc 35
rs 5.2166
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]) && !($handler[0] instanceof IWeakRetainable)) {
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 $items item or array of items 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($items, $index = null, $priority = null, $preserveCache = false)
512
	{
513
		$this->collapseDiscardInvalid();
514
		if (is_callable($items, true)) {
515
			$items = [$items];
516
		} elseif (!is_array($items)) {
517
			throw new TInvalidDataValueException('weakcallablecollection_callable_required');
518
		}
519
		$return = null;
520
		foreach($items as $item) {
521
			$itemPriority = null;
522
			if (($isPriorityItem = ($item instanceof IPriorityItem)) && ($priority === null || !is_numeric($priority))) {
523
				$itemPriority = $priority = $item->getPriority();
524
			}
525
			$priority = $this->ensurePriority($priority);
526
			if (($item instanceof IPriorityCapture) && (!$isPriorityItem || $itemPriority !== $priority)) {
527
				$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

527
				$item->setPriority(/** @scrutinizer ignore-type */ $priority);
Loading history...
528
			}
529
			if (($isObj = is_object($item)) || is_array($item) && is_object($item[0])) {
530
				$this->weakCustomAdd($isObj ? $item : $item[0]);
531
			}
532
			$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

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

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

633
			$this->weakCustomRemove(/** @scrutinizer ignore-type */ $obj = $isObj ? $item : $item[0]);
Loading history...
634
		}
635
		return $item;
636
	}
637
638
	/**
639
	 * Removes all items in the priority list by calling removeAtIndexInPriority from the
640
	 * last item to the first.
641
	 * @since 4.2.3
642
	 */
643
	public function clear(): void
644
	{
645
		if ($this->getReadOnly()) {
646
			throw new TInvalidOperationException('list_readonly', get_class($this));
647
		}
648
649
		$c = $this->_c;
650
		foreach (array_keys($this->_d) as $priority) {
651
			for ($index = count($this->_d[$priority]) - 1; $index >= 0; $index--) {
652
				parent::removeAtIndexInPriority($index, $priority);
653
			}
654
		}
655
656
		if ($c) {
657
			$this->weakRestart();
658
		}
659
	}
660
661
	/**
662
	 * @param mixed $item the item
663
	 * @return bool whether the list contains the item
664
	 * @since 4.2.3
665
	 */
666
	public function contains($item): bool
667
	{
668
		return $this->indexOf($item) !== -1;
669
	}
670
671
	/**
672
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
673
	 * @param mixed $item item being indexed.
674
	 * @return int the index of the item in the flattened list (0 based), -1 if not found.
675
	 */
676
	public function indexOf($item)
677
	{
678
		$this->filterItemForInput($item);
679
		$this->flattenPriorities();
680
681
		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

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