Passed
Push — master ( 6d0871...96ed97 )
by Fabio
05:41
created

TWeakCallableCollection::insertAtIndexInPriority()   B

Complexity

Conditions 7
Paths 4

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 7
c 1
b 0
f 0
nc 4
nop 4
dl 0
loc 11
rs 8.8333
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
	 * @var bool wether or not WeakReference is available
30
	 */
31
	private static $_weak;
32
33
34
	/**
35
	 * Constructor.
36
	 * Initializes the list with an array or an iterable object. Discovers the availability of the
37
	 * {@link WeakReference} object in PHP 7.4.0+.
38
	 * @param null|array|\Iterator $data the initial data. Default is null, meaning no initial data.
39
	 * @param bool $readOnly whether the list is read-only
40
	 * @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...
41
	 * @param int $precision the precision of the numeric priorities
42
	 * @throws \Prado\Exceptions\TInvalidDataTypeException If data is not null and is neither an array nor an iterator.
43
	 */
44
	public function __construct($data = null, $readOnly = false, $defaultPriority = 10, $precision = 8)
45
	{
46
		if (self::$_weak === null) {
0 ignored issues
show
introduced by
The condition self::_weak === null is always false.
Loading history...
47
			self::$_weak = class_exists('\WeakReference', false);
48
		}
49
		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

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

204
		return $this->filterItemForOutput(/** @scrutinizer ignore-type */ parent::itemAtIndexInPriority($index, $priority));
Loading history...
205
	}
206
207
	/**
208
	 * Inserts an item at the specified index within a priority.  This is needed to filter the input.
209
	 * @param mixed $item item to add within the list.
210
	 * @param false|int $index index within the priority to add the item, defaults to false which appends the item at the priority
211
	 * @param null|numeric $priority priority of the item.  defaults to null, which sets it to the default priority
212
	 * @param bool $preserveCache preserveCache specifies if this is a special quick function or not. This defaults to false.
213
	 * @throws \Prado\Exceptions\TInvalidDataValueException If the index specified exceeds the bound
214
	 * @throws \Prado\Exceptions\TInvalidOperationException if the list is read-only
215
	 */
216
	public function insertAtIndexInPriority($item, $index = false, $priority = null, $preserveCache = false)
217
	{
218
		$itemPriority = null;
219
		if (($isPriorityItem = ($item instanceof IPriorityItem)) && ($priority === null || !is_numeric($priority))) {
220
			$itemPriority = $priority = $item->getPriority();
221
		}
222
		$priority = $this->ensurePriority($priority);
223
		if (($item instanceof IPriorityCapture) && (!$isPriorityItem || $itemPriority !== $priority)) {
224
			$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

224
			$item->setPriority(/** @scrutinizer ignore-type */ $priority);
Loading history...
225
		}
226
		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

226
		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

226
		return parent::insertAtIndexInPriority($this->filterItemForInput(/** @scrutinizer ignore-type */ $item, true), $index, $priority, $preserveCache);
Loading history...
227
	}
228
229
	/**
230
	 * Removes the item at a specific index within a priority.  This is needed to filter the output.
231
	 * @param int $index index of item to remove within the priority.
232
	 * @param null|numeric $priority priority of the item to remove, defaults to null, or left blank, it is then set to the default priority
233
	 * @throws TInvalidDataValueException If the item does not exist
234
	 * @return mixed the removed item.
235
	 */
236
	public function removeAtIndexInPriority($index, $priority = null)
237
	{
238
		return $this->filterItemForOutput(parent::removeAtIndexInPriority($index, $priority));
239
	}
240
241
	/**
242
	 * This is needed to filter the input.
243
	 * @param mixed $item item being indexed.
244
	 * @return int the index of the item in the flattened list (0 based), -1 if not found.
245
	 */
246
	public function indexOf($item)
247
	{
248
		if (($index = array_search($this->filterItemForInput($item), $this->flattenPrioritiesWeak(), true)) === false) {
249
			return -1;
250
		} else {
251
			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...
252
		}
253
	}
254
255
	/**
256
	 * Returns the priority of a particular item.  This is needed to filter the input.
257
	 * @param mixed $item the item to look for within the list.
258
	 * @param bool $withindex this specifies if the full positional data of the item within the list is returned.
259
	 * 		This defaults to false, if no parameter is provided, so only provides the priority number of the item by default.
260
	 * @return array|numeric the priority of the item in the list, false if not found.
261
	 *   if withindex is true, an array is returned of [0 => $priority, 1 => $priorityIndex, 2 => flattenedIndex,
262
	 * 'priority' => $priority, 'index' => $priorityIndex, 'absindex' => flattenedIndex]
263
	 */
264
	public function priorityOf($item, $withindex = false)
265
	{
266
		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...
267
	}
268
269
270
	/**
271
	 *  This is needed to filter the output.
272
	 * @return array the array of priorities keys with values of arrays of callables.
273
	 * The priorities are sorted so important priorities, lower numerics, are first.
274
	 */
275
	public function toPriorityArray()
276
	{
277
		$result = [];
278
		foreach (parent::toPriorityArray() as $i => $v) {
279
			$result[$i] = $this->filterItemsForOutput($v);
280
		}
281
		return $result;
282
	}
283
284
285
	/**
286
	 * @return array the array of priorities keys with values of arrays of callables with
287
	 * WeakReference rather than objects.  The priorities are sorted so important priorities,
288
	 * lower numerics, are first.
289
	 */
290
	public function toPriorityArrayWeak()
291
	{
292
		return parent::toPriorityArray();
293
	}
294
295
	/**
296
	 * Combines the map elements which have a priority below the parameter value.  This is needed to filter the output.
297
	 * @param numeric $priority the cut-off priority.  All items of priority less than this are returned.
298
	 * @param bool $inclusive whether or not the input cut-off priority is inclusive.  Default: false, not inclusive.
299
	 * @return array the array of priorities keys with values of arrays of items that are below a specified priority.
300
	 *  The priorities are sorted so important priorities, lower numerics, are first.
301
	 */
302
	public function toArrayBelowPriority($priority, $inclusive = false)
303
	{
304
		return $this->filterItemsForOutput(parent::toArrayBelowPriority($priority, $inclusive));
305
	}
306
307
	/**
308
	 * Combines the map elements which have a priority above the parameter value. This is needed to filter the output.
309
	 * @param numeric $priority the cut-off priority.  All items of priority greater than this are returned.
310
	 * @param bool $inclusive whether or not the input cut-off priority is inclusive.  Default: true, inclusive.
311
	 * @return array the array of priorities keys with values of arrays of items that are above a specified priority.
312
	 *  The priorities are sorted so important priorities, lower numerics, are first.
313
	 */
314
	public function toArrayAbovePriority($priority, $inclusive = true)
315
	{
316
		return $this->filterItemsForOutput(parent::toArrayAbovePriority($priority, $inclusive));
317
	}
318
}
319