Passed
Pull Request — master (#879)
by Fabio
14:17 queued 08:31
created

TWeakCallableCollection::removeAtIndexInPriority()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 2
dl 0
loc 3
rs 10
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
14
/**
15
 * TWeakCallableCollection class
16
 *
17
 * TWeakCallableCollection implements a priority ordered list collection of callables.  This extends
18
 * {@link TPriorityList}.  This holds the callables for object event handlers and global event handlers by
19
 * converting all callable objects into a WeakReference (for PHP 7.4+).  TWeakCallableCollection prevents circular
20
 * references in global events that would otherwise block object destruction, and thus removal of the callable
21
 * in __destruct. All data out has the callable objects converted back to the regular object reference in a callable.
22
 *
23
 * @author Brad Anderson <[email protected]>
24
 * @since 4.2.0
25
 */
26
class TWeakCallableCollection extends TPriorityList
27
{
28
	/**
29
	 * Constructor.
30
	 * Initializes the list with an array or an iterable object. Discovers the availability of the
31
	 * {@link WeakReference} object in PHP 7.4.0+.
32
	 * @param null|array|\Iterator $data the initial data. Default is null, meaning no initial data.
33
	 * @param bool $readOnly whether the list is read-only
34
	 * @param numeric $defaultPriority the default priority of items without specified priorities.
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...
35
	 * @param int $precision the precision of the numeric priorities
36
	 * @throws \Prado\Exceptions\TInvalidDataTypeException If data is not null and is neither an array nor an iterator.
37
	 */
38
	public function __construct($data = null, $readOnly = false, $defaultPriority = 10, $precision = 8)
39
	{
40
		parent::__construct($data, $readOnly, $defaultPriority, $precision);
0 ignored issues
show
Bug introduced by
It seems like $defaultPriority can also be of type integer; however, parameter $defaultPriority of Prado\Collections\TPriorityList::__construct() does only seem to accept Prado\Collections\numeric, maybe add an additional type check? ( Ignorable by Annotation )

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

40
		parent::__construct($data, $readOnly, /** @scrutinizer ignore-type */ $defaultPriority, $precision);
Loading history...
41
	}
42
43
44
	/**
45
	 * TWeakCallableCollection cannot auto listen to global events or there will be a loop.
46
	 *
47
	 * @return bool returns false
48
	 */
49
	public function getAutoGlobalListen()
50
	{
51
		return false;
52
	}
53
54
	/**
55
	 * returns whether or not WeakReference is enabled
56
	 * @return bool is WeakReference available
57
	 * @deprecated since 4.3: this method will always return true
58
	 */
59
	public static function getWeakReferenceEnabled()
60
	{
61
		return true;
62
	}
63
64
65
	/**
66
	 * If WeakReference is available, converts the $items array of callable that
67
	 * has WeakReferences rather than the actual object back into a regular callable.
68
	 * @param array $items an array of callable where objects are WeakReference
69
	 * @return array array of callable where WeakReference are converted back to the object
70
	 */
71
	protected function filterItemsForOutput($items)
72
	{
73
		if (!is_array($items)) {
0 ignored issues
show
introduced by
The condition is_array($items) is always true.
Loading history...
74
			return $items;
75
		}
76
		$result = [];
77
		foreach ($items as $i => $handler) {
78
			if (is_array($handler) && is_object($handler[0]) && is_a($handler[0], '\WeakReference')) {
79
				$result[] = [$handler[0]->get(), $handler[1]];
80
			} elseif (is_object($handler) && is_a($handler, '\WeakReference')) {
81
				$result[] = $handler->get();
82
			} else {
83
				$result[] = $handler;
84
			}
85
		}
86
		return $result;
87
	}
88
89
90
	/**
91
	 * Converts the $handler callable that has WeakReferences rather than the actual object
92
	 * back into a regular callable.
93
	 * @param callable $handler but the $handler or $handler[0] is a WeakReference
94
	 * @return mixed callable by removing the WeakReferences
95
	 */
96
	protected function filterItemForOutput($handler)
97
	{
98
		if (is_array($handler) && is_object($handler[0]) && is_a($handler[0], '\WeakReference')) {
99
			return [$handler[0]->get(), $handler[1]];
100
		} elseif (is_object($handler) && is_a($handler, '\WeakReference')) {
101
			return $handler->get();
102
		}
103
		return $handler;
104
	}
105
106
107
	/**
108
	 * Converts the $handler callable into a WeakReference version for storage
109
	 * @param callable $handler callable to convert into a WeakReference version
110
	 * @param bool $validate whether or not to validate the input as a callable
111
	 * @return mixed callable but with the objects as WeakReferences
112
	 */
113
	protected function filterItemForInput($handler, $validate = false)
114
	{
115
		if ($validate && !is_callable($handler, false)) {
116
			throw new TInvalidDataValueException('weakcallablecollection_callable_required');
117
		}
118
		if (is_array($handler) && is_object($handler[0]) && !is_a($handler[0], '\WeakReference')) {
119
			return [\WeakReference::create($handler[0]), $handler[1]];
120
		} elseif (is_object($handler) && !is_a($handler, '\WeakReference')) {
121
			return \WeakReference::create($handler);
122
		}
123
124
		return $handler;
125
	}
126
127
	/**
128
	 * This flattens the priority list into a flat array [0,...,n-1]. This is needed to filter the output.
129
	 * @return array array of items in the list in priority and index order
130
	 */
131
	protected function flattenPriorities(): array
132
	{
133
		return $this->filterItemsForOutput(parent::flattenPriorities());
134
	}
135
136
	/**
137
	 * This flattens the priority list into a flat array [0,...,n-1], but
138
	 * leaves the objects in the array as WeakReference rather than standard
139
	 * callable.
140
	 * @return array array of items in the list in priority and index order,
141
	 *    where objects are WeakReference
142
	 */
143
	protected function flattenPrioritiesWeak(): array
144
	{
145
		return parent::flattenPriorities();
146
	}
147
148
149
	/**
150
	 * Returns the item at the index of a flattened priority list. This is needed to filter the output.
151
	 * {@link offsetGet} calls this method.
152
	 * @param int $index the index of the item to get
153
	 * @throws TInvalidDataValueException Issued when the index is invalid
154
	 * @return mixed the element at the offset
155
	 */
156
	public function itemAt($index)
157
	{
158
		if ($index >= 0 && $index < $this->getCount()) {
159
			$arr = $this->flattenPrioritiesWeak();
160
			return $this->filterItemForOutput($arr[$index]);
161
		} else {
162
			throw new TInvalidDataValueException('list_index_invalid', $index);
163
		}
164
	}
165
166
	/**
167
	 * Gets all the items at a specific priority. This is needed to filter the output.
168
	 * @param null|numeric $priority priority of the items to get.  Defaults to null, filled
169
	 * in with the default priority, if left blank.
170
	 * @return ?array all items at priority in index order, null if there are no items at that priority
171
	 */
172
	public function itemsAtPriority($priority = null): ?array
173
	{
174
		return $this->filterItemsForOutput(parent::itemsAtPriority($priority));
175
	}
176
177
	/**
178
	 * Returns the item at an index within a priority. This is needed to filter the output.
179
	 * @param int $index the index into the list of items at priority
180
	 * @param numeric $priority the priority which to index.  no parameter or null will result in the default priority
181
	 * @return mixed the element at the offset, false if no element is found at the offset
182
	 */
183
	public function itemAtIndexInPriority($index, $priority = null)
184
	{
185
		return $this->filterItemForOutput(parent::itemAtIndexInPriority($index, $priority));
0 ignored issues
show
Bug introduced by
It seems like parent::itemAtIndexInPriority($index, $priority) can also be of type false; however, parameter $handler of Prado\Collections\TWeakC...::filterItemForOutput() 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

185
		return $this->filterItemForOutput(/** @scrutinizer ignore-type */ parent::itemAtIndexInPriority($index, $priority));
Loading history...
186
	}
187
188
	/**
189
	 * Inserts an item at the specified index within a priority.  This is needed to filter the input.
190
	 * @param mixed $item item to add within the list.
191
	 * @param false|int $index index within the priority to add the item, defaults to false which appends the item at the priority
192
	 * @param null|numeric $priority priority of the item.  defaults to null, which sets it to the default priority
193
	 * @param bool $preserveCache preserveCache specifies if this is a special quick function or not. This defaults to false.
194
	 * @throws \Prado\Exceptions\TInvalidDataValueException If the index specified exceeds the bound
195
	 * @throws \Prado\Exceptions\TInvalidOperationException if the list is read-only
196
	 */
197
	public function insertAtIndexInPriority($item, $index = false, $priority = null, $preserveCache = false)
198
	{
199
		$itemPriority = null;
200
		if (($isPriorityItem = ($item instanceof IPriorityItem)) && ($priority === null || !is_numeric($priority))) {
201
			$itemPriority = $priority = $item->getPriority();
202
		}
203
		$priority = $this->ensurePriority($priority);
204
		if (($item instanceof IPriorityCapture) && (!$isPriorityItem || $itemPriority !== $priority)) {
205
			$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

205
			$item->setPriority(/** @scrutinizer ignore-type */ $priority);
Loading history...
206
		}
207
		return parent::insertAtIndexInPriority($this->filterItemForInput($item, true), $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

207
		return parent::insertAtIndexInPriority($this->filterItemForInput($item, true), $index, /** @scrutinizer ignore-type */ $priority, $preserveCache);
Loading history...
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

207
		return parent::insertAtIndexInPriority($this->filterItemForInput(/** @scrutinizer ignore-type */ $item, true), $index, $priority, $preserveCache);
Loading history...
208
	}
209
210
	/**
211
	 * Removes the item at a specific index within a priority.  This is needed to filter the output.
212
	 * @param int $index index of item to remove within the priority.
213
	 * @param null|numeric $priority priority of the item to remove, defaults to null, or left blank, it is then set to the default priority
214
	 * @throws TInvalidDataValueException If the item does not exist
215
	 * @return mixed the removed item.
216
	 */
217
	public function removeAtIndexInPriority($index, $priority = null)
218
	{
219
		return $this->filterItemForOutput(parent::removeAtIndexInPriority($index, $priority));
220
	}
221
222
	/**
223
	 * This is needed to filter the input.
224
	 * @param mixed $item item being indexed.
225
	 * @return int the index of the item in the flattened list (0 based), -1 if not found.
226
	 */
227
	public function indexOf($item)
228
	{
229
		if (($index = array_search($this->filterItemForInput($item), $this->flattenPrioritiesWeak(), true)) === false) {
230
			return -1;
231
		} else {
232
			return $index;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $index also could return the type string which is incompatible with the documented return type integer.
Loading history...
233
		}
234
	}
235
236
	/**
237
	 * Returns the priority of a particular item.  This is needed to filter the input.
238
	 * @param mixed $item the item to look for within the list.
239
	 * @param bool $withindex this specifies if the full positional data of the item within the list is returned.
240
	 * 		This defaults to false, if no parameter is provided, so only provides the priority number of the item by default.
241
	 * @return array|numeric the priority of the item in the list, false if not found.
242
	 *   if withindex is true, an array is returned of [0 => $priority, 1 => $priorityIndex, 2 => flattenedIndex,
243
	 * 'priority' => $priority, 'index' => $priorityIndex, 'absindex' => flattenedIndex]
244
	 */
245
	public function priorityOf($item, $withindex = false)
246
	{
247
		return parent::priorityOf($this->filterItemForInput($item), $withindex);
0 ignored issues
show
Bug Best Practice introduced by
The expression return parent::priorityO...put($item), $withindex) could also return false which is incompatible with the documented return type Prado\Collections\numeric|array. Did you maybe forget to handle an error condition?

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

Loading history...
248
	}
249
250
	/**
251
	 *  This is needed to filter the output.
252
	 * @return array the array of priorities keys with values of arrays of callables.
253
	 * The priorities are sorted so important priorities, lower numerics, are first.
254
	 */
255
	public function toPriorityArray(): array
256
	{
257
		$result = [];
258
		foreach (parent::toPriorityArray() as $i => $v) {
259
			$result[$i] = $this->filterItemsForOutput($v);
260
		}
261
		return $result;
262
	}
263
264
	/**
265
	 * @return array the array of priorities keys with values of arrays of callables with
266
	 * WeakReference rather than objects.  The priorities are sorted so important priorities,
267
	 * lower numerics, are first.
268
	 */
269
	public function toPriorityArrayWeak()
270
	{
271
		return parent::toPriorityArray();
272
	}
273
274
	/**
275
	 * Combines the map elements which have a priority below the parameter value.  This is needed to filter the output.
276
	 * @param numeric $priority the cut-off priority.  All items of priority less than this are returned.
277
	 * @param bool $inclusive whether or not the input cut-off priority is inclusive.  Default: false, not inclusive.
278
	 * @return array the array of priorities keys with values of arrays of items that are below a specified priority.
279
	 *  The priorities are sorted so important priorities, lower numerics, are first.
280
	 */
281
	public function toArrayBelowPriority($priority, $inclusive = false): array
282
	{
283
		return $this->filterItemsForOutput(parent::toArrayBelowPriority($priority, $inclusive));
284
	}
285
286
	/**
287
	 * Combines the map elements which have a priority above the parameter value. This is needed to filter the output.
288
	 * @param numeric $priority the cut-off priority.  All items of priority greater than this are returned.
289
	 * @param bool $inclusive whether or not the input cut-off priority is inclusive.  Default: true, inclusive.
290
	 * @return array the array of priorities keys with values of arrays of items that are above a specified priority.
291
	 *  The priorities are sorted so important priorities, lower numerics, are first.
292
	 */
293
	public function toArrayAbovePriority($priority, $inclusive = true): array
294
	{
295
		return $this->filterItemsForOutput(parent::toArrayAbovePriority($priority, $inclusive));
296
	}
297
298
	/**
299
	 * Returns an array with the names of all variables of this object that should NOT be serialized
300
	 * because their value is the default one or useless to be cached for the next page loads.
301
	 * Reimplement in derived classes to add new variables, but remember to  also to call the parent
302
	 * implementation first.
303
	 * @param array $exprops by reference
304
	 * @since 4.2.3
305
	 */
306
	protected function _getZappableSleepProps(&$exprops)
307
	{
308
		$c = $this->_c;
309
		$this->_c = 0;
310
		parent::_getZappableSleepProps($exprops);
311
		$this->_c = $c;
312
	}
313
}
314