Passed
Push — master ( a3198c...0d815f )
by Fabio
04:46
created

TPriorityMap::getNextIntegerKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

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

86
		$this->setDefaultPriority(/** @scrutinizer ignore-type */ $defaultPriority);
Loading history...
87
		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

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

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

218
			$value->setPriority(/** @scrutinizer ignore-type */ $priority);
Loading history...
219 1
		}
220
221 1
		if (!$this->getReadOnly()) {
222
			if ($key === null) {
223
				$key = $this->_ic++;
224
			} elseif (is_numeric($key)) {
225
				$this->_ic = (int) max($this->_ic, floor($key) + 1);
226
			}
227
			foreach (array_keys($this->_d) as $innerpriority) {
228
				if (array_key_exists($key, $this->_d[$innerpriority])) {
229
					unset($this->_d[$innerpriority][$key]);
230
					$this->_c--;
231
					if (count($this->_d[$innerpriority]) === 0) {
232 29
						unset($this->_d[$innerpriority]);
233
					}
234 29
					break;
235 29
				}
236 29
			}
237
			if (!isset($this->_d[$priority])) {
238 1
				$this->_d[$priority] = [$key => $value];
239
				$this->_o = false;
240
			} else {
241 1
				$this->_d[$priority][$key] = $value;
242 1
			}
243
			$this->_c++;
244
			$this->_fd = null;
245
			$this->dyAddItem($key, $value);
246
		} else {
247
			throw new TInvalidOperationException('map_readonly', $this::class);
248
		}
249
		return $priority;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $priority returns the type string which is incompatible with the documented return type Prado\Collections\numeric.
Loading history...
250
	}
251
252
	/**
253 1
	 * Removes an item from the map by its key. If no priority, or false, is specified
254
	 * then priority is irrelevant. If null is used as a parameter for priority, then
255 1
	 * the priority will be the default priority.  If a priority is specified, or
256 1
	 * the default priority is specified, only key-value pairs in that priority
257
	 * will be affected.
258 1
	 * @param mixed $key the key of the item to be removed
259
	 * @param null|false|numeric $priority priority.  False is any priority, null is the
260 1
	 * default priority, and numeric is a specific priority
261 1
	 * @throws TInvalidOperationException if the map is read-only
262 1
	 * @return mixed the removed value, null if no such key exists.
263 1
	 */
264
	public function remove($key, $priority = false)
265 1
	{
266
		if (!$this->getReadOnly()) {
267
			if ($priority === false) {
268
				$this->sortPriorities();
269
				foreach (array_keys($this->_d) as $priority) {
270
					if (array_key_exists($key, $this->_d[$priority])) {
271
						$value = $this->_d[$priority][$key];
272
						unset($this->_d[$priority][$key]);
273 3
						$this->_c--;
274
						if (count($this->_d[$priority]) === 0) {
275 3
							unset($this->_d[$priority]);
276
							$this->_o = false;
277
						}
278 3
						$this->_fd = null;
279
						$this->dyRemoveItem($key, $value);
280 3
						return $value;
281
					}
282
				}
283
				return null;
284
			} else {
285
				$priority = $this->ensurePriority($priority);
286
				if (isset($this->_d[$priority]) && (isset($this->_d[$priority][$key]) || array_key_exists($key, $this->_d[$priority]))) {
287
					$value = $this->_d[$priority][$key];
288 2
					unset($this->_d[$priority][$key]);
289
					$this->_c--;
290 2
					if (count($this->_d[$priority]) === 0) {
291 2
						unset($this->_d[$priority]);
292 2
						$this->_o = false;
293 2
					}
294
					$this->_fd = null;
295
					$this->dyRemoveItem($key, $value);
296 1
					return $value;
297
				} else {
298
					return null;
299
				}
300
			}
301
		} else {
302
			throw new TInvalidOperationException('map_readonly', $this::class);
303
		}
304 3
	}
305
306 3
	/**
307 3
	 * Removes all items in the map.  {@link remove} is called on all items.
308 3
	 */
309 3
	public function clear(): void
310
	{
311
		foreach (array_keys($this->_d) as $priority) {
312 1
			foreach (array_keys($this->_d[$priority]) as $key) {
313
				$this->remove($key);
314
			}
315
		}
316
		$this->_ic = 0;
317
	}
318
319
	/**
320
	 * @param mixed $key the key
321
	 * @return bool whether the map contains an item with the specified key
322
	 */
323
	public function contains($key): bool
324
	{
325
		$this->flattenPriorities();
326
		return isset($this->_fd[$key]) || array_key_exists($key, $this->_fd);
327
	}
328 53
329
	/**
330 53
	 * Copies iterable data into the map.
331 53
	 * Note, existing data in the map will be cleared first.
332
	 * @param array|TPriorityList|TPriorityMap|\Traversable $data the data to be copied from, must be an array, object implementing
333 53
	 * @throws TInvalidDataTypeException If data is neither an array nor an iterator.
334
	 */
335 53
	public function copyFrom($data): void
336 53
	{
337 36
		if ($data instanceof TPriorityMap) {
338 4
			if ($this->getCount() > 0) {
339 4
				$this->clear();
340 4
			}
341 36
			foreach ($data->getPriorities() as $priority) {
342
				foreach ($data->itemsAtPriority($priority) as $key => $value) {
343
					$this->add($key, $value, $priority);
344
				}
345 53
			}
346 53
		} elseif ($data instanceof TPriorityList) {
347 53
			if ($this->getCount() > 0) {
348
				$this->clear();
349 36
			}
350
			$index = 0;
0 ignored issues
show
Unused Code introduced by
The assignment to $index is dead and can be removed.
Loading history...
351 53
			$array = $data->toPriorityArray();
352 53
			foreach (array_keys($array) as $priority) {
353
				for ($i = 0, $c = count($array[$priority]); $i < $c; $i++) {
354 1
					$this->add(null, $array[$priority][$i], $priority);
355
				}
356 53
			}
357
		} elseif (is_array($data) || $data instanceof \Traversable) {
0 ignored issues
show
introduced by
$data is always a sub-type of Traversable.
Loading history...
358
			if ($this->getCount() > 0) {
359
				$this->clear();
360
			}
361
			foreach ($data as $key => $value) {
362
				$this->add($key, $value);
363
			}
364
		} elseif ($data !== null) {
365
			throw new TInvalidDataTypeException('map_data_not_iterable');
366
		}
367
	}
368
369
	/**
370
	 * Merges iterable data into the map.
371 26
	 * Existing data in the map will be kept and overwritten if the keys are the same.
372
	 * @param array|TPriorityList|TPriorityMap|\Traversable $data the data to be merged with, must be an array,
373 26
	 * object implementing Traversable, or a TPriorityMap
374 25
	 * @throws TInvalidDataTypeException If data is neither an array nor an iterator.
375 7
	 */
376
	public function mergeWith($data): void
377
	{
378 25
		if ($data instanceof TPriorityMap) {
379 19
			foreach ($data->getPriorities() as $priority) {
380 19
				foreach ($data->itemsAtPriority($priority) as $key => $value) {
381 19
					$this->add($key, $value, $priority);
382 19
				}
383 19
			}
384 19
		} elseif ($data instanceof TPriorityList) {
385 19
			$index = 0;
0 ignored issues
show
Unused Code introduced by
The assignment to $index is dead and can be removed.
Loading history...
386 14
			$array = $data->toPriorityArray();
387 14
			foreach (array_keys($array) as $priority) {
388
				for ($i = 0, $c = count($array[$priority]); $i < $c; $i++) {
389 19
					$this->add(null, $array[$priority][$i], $priority);
390 19
				}
391
			}
392
		} elseif (is_array($data) || $data instanceof \Traversable) {
0 ignored issues
show
introduced by
$data is always a sub-type of Traversable.
Loading history...
393 2
			foreach ($data as $key => $value) {
394
				$this->add($key, $value);
395 8
			}
396 8
		} elseif ($data !== null) {
397 7
			throw new TInvalidDataTypeException('map_data_not_iterable');
398 7
		}
399 7
	}
400 7
401 6
	/**
402 6
	 * Returns an array with the names of all variables of this object that should NOT be serialized
403
	 * because their value is the default one or useless to be cached for the next page loads.
404 7
	 * Reimplement in derived classes to add new variables, but remember to  also to call the parent
405 7
	 * implementation first.
406
	 * @param array $exprops by reference
407 1
	 * @since 4.2.3
408
	 */
409
	protected function _getZappableSleepProps(&$exprops)
410
	{
411 1
		parent::_getZappableSleepProps($exprops);
412
		if ($this->_c === 0) {
413
			$exprops[] = "\0*\0_c";
414
		}
415
		if ($this->_ic === 0) {
416
			$exprops[] = "\0*\0_ic";
417
		}
418 3
		$this->_priorityZappableSleepProps($exprops);
419
	}
420
}
421