TPriorityMap::remove()   B
last analyzed

Complexity

Conditions 10
Paths 8

Size

Total Lines 39
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 10.0578

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
eloc 31
nc 8
nop 2
dl 0
loc 39
ccs 11
cts 12
cp 0.9167
crap 10.0578
rs 7.6666
c 1
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
/**
4
 * TPriorityMap, TPriorityMapIterator classes
5
 *
6
 * @author Brad Anderson <[email protected]>
7
 * @link https://github.com/pradosoft/prado
8
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
9
 */
10
11
namespace Prado\Collections;
12
13
use Prado\Exceptions\TInvalidOperationException;
14
use Prado\Exceptions\TInvalidDataTypeException;
15
use Prado\TPropertyValue;
16
17
/**
18
 * TPriorityMap class
19
 *
20
 * TPriorityMap implements a collection that takes key-value pairs with
21
 * a priority to allow key-value pairs to be ordered.  This ordering is
22
 * important when flattening the map. When flattening the map, if some
23
 * key-value pairs are required to be before or after others, use this
24
 * class to keep order to your map.
25
 *
26
 * You can access, add or remove an item with a key by using
27
 * {@see itemAt}, {@see add}, and {@see remove}.  These functions
28
 * can optionally take a priority parameter to allow access to specific
29
 * priorities.  TPriorityMap is functionally backward compatible
30
 * with {@see \Prado\Collections\TMap}.
31
 *
32
 * To get the number of the items in the map, use {@see getCount}.
33
 * TPriorityMap can also be used like a regular array as follows,
34
 * ```php
35
 * $map[$key]=$value; // add a key-value pair
36
 * unset($map[$key]); // remove the value with the specified key
37
 * if(isset($map[$key])) // if the map contains the key
38
 * foreach($map as $key=>$value) // traverse the items in the map
39
 * $n=count($map);  // returns the number of items in the map
40
 * ```
41
 * Using standard array access method like these will always use
42
 * the default priority.
43
 *
44
 * An item that doesn't specify a priority will receive the default
45
 * priority.  The default priority is set during the instantiation
46
 * of a new TPriorityMap. If no custom default priority is specified,
47
 * the standard default priority of 10 is used.
48
 *
49
 * Priorities with significant digits below precision will be rounded.
50
 *
51
 * A priority may also be a numeric with decimals.  This is set
52
 * during the instantiation of a new TPriorityMap.
53
 * The default is 8 decimal places for a priority.  If a negative number
54
 * is used, rounding occurs into the integer space rather than in
55
 * the decimal space.  See {@see round}.
56
 *
57
 * @author Brad Anderson <[email protected]>
58
 * @since 3.2a
59
 * @method void dyAddItem(mixed $key, mixed $value)
60
 * @method void dyRemoveItem(mixed $key, mixed $value)
61
 * @method mixed dyNoItem(mixed $returnValue, mixed $key)
62
 */
63
class TPriorityMap extends TMap implements IPriorityCollection
64
{
65
	use TPriorityCollectionTrait;
66
67
	/**
68
	 * @var int number of items contain within the map
69
	 */
70
	protected int $_c = 0;
71
72
	/** @var int The next highest integer key at which we can add an item. */
73
	protected int $_ic = 0;
74
75
	/**
76
	 * Constructor.
77
	 * Initializes the array with an array or an iterable object.
78
	 * @param null|array|TPriorityList|TPriorityMap|\Traversable $data the initial data. Default is null, meaning no initialization.
79
	 * @param ?bool $readOnly whether the list is read-only
80
	 * @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...
81
	 *   priorities.  Default null for 10.
82
	 * @param ?int $precision the precision of the numeric priorities.  Default null for 8.
83
	 * @throws TInvalidDataTypeException If data is not null and neither an array nor an iterator.
84
	 */
85
	public function __construct($data = null, $readOnly = null, $defaultPriority = null, $precision = null)
86
	{
87
		$this->setPrecision($precision);
88
		$this->setDefaultPriority($defaultPriority);
89
		parent::__construct($data, $readOnly);
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\TMap::__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

89
		parent::__construct(/** @scrutinizer ignore-type */ $data, $readOnly);
Loading history...
90
	}
91
92
	/**
93
	 * This is required for TPriorityCollectionTrait to determine the style of combining
94 53
	 * arrays.
95
	 * @return bool This returns false for array_replace (map style).  true would be
96 53
	 *   array_merge (list style).
97 4
	 */
98
	private function getPriorityCombineStyle(): bool
0 ignored issues
show
Unused Code introduced by
The method getPriorityCombineStyle() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
99 53
	{
100 53
		return false;
101 53
	}
102 53
103
	/**
104
	 * @return int This is the key for the next appended item that doesn't have its
105
	 *   own key.
106
	 */
107 53
	public function getNextIntegerKey(): int
108
	{
109 53
		return $this->_ic;
110
	}
111
112
	/**
113
	 * @return int the number of items in the map
114
	 */
115
	public function getCount(): int
116 53
	{
117
		return $this->_c;
118 53
	}
119 53
120
	/**
121
	 * Returns the keys within the map ordered through the priority of each key-value pair
122
	 * @return array the key list
123
	 */
124 2
	public function getKeys(): array
125
	{
126 2
		$this->sortPriorities();
127
		return array_merge(...array_map('array_keys', $this->_d));
128
	}
129
130
	/**
131
	 * Returns the item with the specified key.  If a priority is specified, only items
132
	 * within that specific priority will be selected.
133 53
	 * @param mixed $key the key
134
	 * @param null|false|numeric $priority the priority.  null is the default priority, false is any priority,
135 53
	 *    and numeric is a specific priority.  default: false, any priority.
136 53
	 * @return mixed the element at the offset, null if no element is found at the offset
137
	 */
138
	public function itemAt($key, $priority = false)
139
	{
140
		if ($priority === false) {
141
			$this->flattenPriorities();
142
			return array_key_exists($key, $this->_fd) ? $this->_fd[$key] : $this->dyNoItem(null, $key);
143 2
		} else {
144
			$priority = $this->ensurePriority($priority);
145 2
			return (isset($this->_d[$priority]) && array_key_exists($key, $this->_d[$priority])) ? $this->_d[$priority][$key] : $this->dyNoItem(null, $key);
146
		}
147
	}
148
149
	/**
150
	 * This changes an item's priority.  Specify the item and the new priority.
151
	 * This method is exactly the same as {@see offsetGet}.
152 48
	 * @param mixed $key the key
153
	 * @param null|numeric $priority the priority.  default: null, filled in with the default priority numeric.
154 48
	 * @return numeric old priority of the item
155 48
	 */
156 48
	public function setPriorityAt($key, $priority = null)
157
	{
158 48
		$priority = $this->ensurePriority($priority);
159
		$oldpriority = $this->priorityAt($key);
160
		if ($oldpriority !== false && $oldpriority != $priority) {
161
			$value = $this->remove($key, $oldpriority);
162
			$this->add($key, $value, $priority);
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\TPriorityMap::add(). ( Ignorable by Annotation )

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

162
			$this->add($key, $value, /** @scrutinizer ignore-type */ $priority);
Loading history...
163
		}
164 42
		return $oldpriority;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $oldpriority could also return false which is incompatible with the documented return type Prado\Collections\numeric. 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...
165
	}
166 42
167 34
	/**
168
	 * Returns the priority of a particular item within the map.  This searches the map for the item.
169
	 * @param mixed $item item to look for within the map
170 42
	 * @return false|numeric priority of the item in the map.  False if not found.
171 42
	 */
172 42
	public function priorityOf($item)
173 41
	{
174
		$this->sortPriorities();
175 42
		foreach (array_keys($this->_d) as $priority) {
176
			if (($index = array_search($item, $this->_d[$priority], true)) !== false) {
0 ignored issues
show
Unused Code introduced by
The assignment to $index is dead and can be removed.
Loading history...
177
				return $priority;
178
			}
179
		}
180
		return false;
181 20
	}
182
183 20
	/**
184
	 * Returns the priority of an item at a particular key.  This searches the map for the item.
185
	 * @param mixed $key index of the item within the map
186
	 * @return false|numeric priority of the item in the map. False if not found.
187
	 */
188
	public function priorityAt($key)
189
	{
190
		$this->sortPriorities();
191
		foreach (array_keys($this->_d) as $priority) {
192
			if (isset($this->_d[$priority][$key]) || array_key_exists($key, $this->_d[$priority])) {
193
				return $priority;
194
			}
195
		}
196
		return false;
197
	}
198
199
	/**
200
	 * Adds an item into the map.  A third parameter may be used to set the priority
201
	 * of the item within the map.  Priority is primarily used during when flattening
202
	 * the map into an array where order may be and important factor of the key-value
203
	 * pairs within the array.
204
	 * Note, if the specified key already exists, the old value will be overwritten.
205
	 * No duplicate keys are allowed regardless of priority.
206
	 * @param mixed $key
207
	 * @param mixed $value
208
	 * @param null|numeric $priority default: null, filled in with default priority
209 3
	 * @throws TInvalidOperationException if the map is read-only
210
	 * @return mixed The key of the item, which is calculated when the $key is null.
211 3
	 */
212 3
	public function add($key, $value, $priority = null): mixed
213
	{
214
		$itemPriority = null;
215
		if (($isPriorityItem = ($value instanceof IPriorityItem)) && ($priority === null || !is_numeric($priority))) {
216
			$itemPriority = $priority = $value->getPriority();
217
		}
218
		$priority = $this->ensurePriority($priority);
219 1
		if (($value instanceof IPriorityCapture) && (!$isPriorityItem || $itemPriority !== $priority)) {
220
			$value->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

220
			$value->setPriority(/** @scrutinizer ignore-type */ $priority);
Loading history...
221 1
		}
222
223
		$this->collapseReadOnly();
224
		if (!$this->getReadOnly()) {
225
			if ($key === null) {
226
				$key = $this->_ic++;
227
			} elseif (is_numeric($key)) {
228
				$this->_ic = (int) max($this->_ic, floor($key) + 1);
229
			}
230
			foreach (array_keys($this->_d) as $innerpriority) {
231
				if (array_key_exists($key, $this->_d[$innerpriority])) {
232 29
					unset($this->_d[$innerpriority][$key]);
233
					$this->_c--;
234 29
					if (empty($this->_d[$innerpriority])) {
235 29
						unset($this->_d[$innerpriority]);
236 29
					}
237
					break;
238 1
				}
239
			}
240
			if (!isset($this->_d[$priority])) {
241 1
				$this->_d[$priority] = [$key => $value];
242 1
				$this->_o = false;
243
			} else {
244
				$this->_d[$priority][$key] = $value;
245
			}
246
			$this->_c++;
247
			$this->_fd = null;
248
			$this->dyAddItem($key, $value);
249
		} else {
250
			throw new TInvalidOperationException('map_readonly', $this::class);
251
		}
252
		return $key;
253 1
	}
254
255 1
	/**
256 1
	 * Removes an item from the map by its key. If no priority, or false, is specified
257
	 * then priority is irrelevant. If null is used as a parameter for priority, then
258 1
	 * the priority will be the default priority.  If a priority is specified, or
259
	 * the default priority is specified, only key-value pairs in that priority
260 1
	 * will be affected.
261 1
	 * @param mixed $key the key of the item to be removed
262 1
	 * @param null|false|numeric $priority priority.  False is any priority, null is the
263 1
	 * default priority, and numeric is a specific priority
264
	 * @throws TInvalidOperationException if the map is read-only
265 1
	 * @return mixed the removed value, null if no such key exists.
266
	 */
267
	public function remove($key, $priority = false)
268
	{
269
		if (!$this->getReadOnly()) {
270
			if ($priority === false) {
271
				$this->sortPriorities();
272
				foreach (array_keys($this->_d) as $priority) {
273 3
					if (array_key_exists($key, $this->_d[$priority])) {
274
						$value = $this->_d[$priority][$key];
275 3
						unset($this->_d[$priority][$key]);
276
						$this->_c--;
277
						if (empty($this->_d[$priority])) {
278 3
							unset($this->_d[$priority]);
279
							$this->_o = false;
280 3
						}
281
						$this->_fd = null;
282
						$this->dyRemoveItem($key, $value);
283
						return $value;
284
					}
285
				}
286
				return null;
287
			} else {
288 2
				$priority = $this->ensurePriority($priority);
289
				if (isset($this->_d[$priority]) && (isset($this->_d[$priority][$key]) || array_key_exists($key, $this->_d[$priority]))) {
290 2
					$value = $this->_d[$priority][$key];
291 2
					unset($this->_d[$priority][$key]);
292 2
					$this->_c--;
293 2
					if (empty($this->_d[$priority])) {
294
						unset($this->_d[$priority]);
295
						$this->_o = false;
296 1
					}
297
					$this->_fd = null;
298
					$this->dyRemoveItem($key, $value);
299
					return $value;
300
				} else {
301
					return null;
302
				}
303
			}
304 3
		} else {
305
			throw new TInvalidOperationException('map_readonly', $this::class);
306 3
		}
307 3
	}
308 3
309 3
	/**
310
	 * Removes all items in the map.  {@see remove} is called on all items.
311
	 */
312 1
	public function clear(): void
313
	{
314
		foreach (array_keys($this->_d) as $priority) {
315
			foreach (array_keys($this->_d[$priority]) as $key) {
316
				$this->remove($key);
317
			}
318
		}
319
		$this->_ic = 0;
320
	}
321
322
	/**
323
	 * @param mixed $key the key
324
	 * @return bool whether the map contains an item with the specified key
325
	 */
326
	public function contains($key): bool
327
	{
328 53
		$this->flattenPriorities();
329
		return isset($this->_fd[$key]) || array_key_exists($key, $this->_fd);
330 53
	}
331 53
332
	/**
333 53
	 * @param mixed $item the item
334
	 * @param bool $multiple Return an array of all the keys. Default true.
335 53
	 * @return false|mixed the key of the item in the map, false if not found.
336 53
	 */
337 36
	public function keyOf($item, bool $multiple = true): mixed
338 4
	{
339 4
		$this->flattenPriorities();
340 4
		if ($multiple) {
341 36
			$return = [];
342
			foreach ($this->_fd as $key => $value) {
343
				if ($item === $value) {
344
					$return[$key] = $item;
345 53
				}
346 53
			}
347 53
			return $return;
348
		} else {
349 36
			return array_search($item, $this->_fd, true);
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

349
			return array_search($item, /** @scrutinizer ignore-type */ $this->_fd, true);
Loading history...
350
		}
351 53
	}
352 53
353
	/**
354 1
	 * Copies iterable data into the map.
355
	 * Note, existing data in the map will be cleared first.
356 53
	 * @param array|TPriorityList|TPriorityMap|\Traversable $data the data to be copied from, must be an array, object implementing
357
	 * @throws TInvalidDataTypeException If data is neither an array nor an iterator.
358
	 */
359
	public function copyFrom($data): void
360
	{
361
		if ($data instanceof TPriorityMap) {
362
			if ($this->getCount() > 0) {
363
				$this->clear();
364
			}
365
			foreach ($data->getPriorities() as $priority) {
366
				foreach ($data->itemsAtPriority($priority) as $key => $value) {
367
					$this->add($key, $value, $priority);
368
				}
369
			}
370
		} elseif ($data instanceof TPriorityList) {
371 26
			if ($this->getCount() > 0) {
372
				$this->clear();
373 26
			}
374 25
			$index = 0;
0 ignored issues
show
Unused Code introduced by
The assignment to $index is dead and can be removed.
Loading history...
375 7
			$array = $data->toPriorityArray();
376
			foreach (array_keys($array) as $priority) {
377
				for ($i = 0, $c = count($array[$priority]); $i < $c; $i++) {
378 25
					$this->add(null, $array[$priority][$i], $priority);
379 19
				}
380 19
			}
381 19
		} elseif (is_array($data) || $data instanceof \Traversable) {
0 ignored issues
show
introduced by
$data is always a sub-type of Traversable.
Loading history...
382 19
			if ($this->getCount() > 0) {
383 19
				$this->clear();
384 19
			}
385 19
			foreach ($data as $key => $value) {
386 14
				$this->add($key, $value);
387 14
			}
388
		} elseif ($data !== null) {
389 19
			throw new TInvalidDataTypeException('map_data_not_iterable');
390 19
		}
391
	}
392
393 2
	/**
394
	 * Merges iterable data into the map.
395 8
	 * Existing data in the map will be kept and overwritten if the keys are the same.
396 8
	 * @param array|TPriorityList|TPriorityMap|\Traversable $data the data to be merged with, must be an array,
397 7
	 * object implementing Traversable, or a TPriorityMap
398 7
	 * @throws TInvalidDataTypeException If data is neither an array nor an iterator.
399 7
	 */
400 7
	public function mergeWith($data): void
401 6
	{
402 6
		if ($data instanceof TPriorityMap) {
403
			foreach ($data->getPriorities() as $priority) {
404 7
				foreach ($data->itemsAtPriority($priority) as $key => $value) {
405 7
					$this->add($key, $value, $priority);
406
				}
407 1
			}
408
		} elseif ($data instanceof TPriorityList) {
409
			$index = 0;
0 ignored issues
show
Unused Code introduced by
The assignment to $index is dead and can be removed.
Loading history...
410
			$array = $data->toPriorityArray();
411 1
			foreach (array_keys($array) as $priority) {
412
				for ($i = 0, $c = count($array[$priority]); $i < $c; $i++) {
413
					$this->add(null, $array[$priority][$i], $priority);
414
				}
415
			}
416
		} elseif (is_array($data) || $data instanceof \Traversable) {
0 ignored issues
show
introduced by
$data is always a sub-type of Traversable.
Loading history...
417
			foreach ($data as $key => $value) {
418 3
				$this->add($key, $value);
419
			}
420 3
		} elseif ($data !== null) {
421 3
			throw new TInvalidDataTypeException('map_data_not_iterable');
422 3
		}
423
	}
424
425 3
	/**
426
	 * Returns an array with the names of all variables of this object that should NOT be serialized
427
	 * because their value is the default one or useless to be cached for the next page loads.
428
	 * Reimplement in derived classes to add new variables, but remember to  also to call the parent
429
	 * implementation first.
430
	 * @param array $exprops by reference
431 31
	 * @since 4.3.0
432
	 */
433 31
	protected function _getZappableSleepProps(&$exprops)
434 31
	{
435
		parent::_getZappableSleepProps($exprops);
436
		if ($this->_c === 0) {
437
			$exprops[] = "\0*\0_c";
438
		}
439
		if ($this->_ic === 0) {
440
			$exprops[] = "\0*\0_ic";
441
		}
442
		$this->_priorityZappableSleepProps($exprops);
443 30
	}
444
}
445