Passed
Push — master ( 494471...7bbba0 )
by Fabio
06:36 queued 01:48
created

TPriorityMap::keyOf()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
ccs 4
cts 4
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
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
	 *   priorities.  Default null for 10.
81
	 * @param ?int $precision the precision of the numeric priorities.  Default null for 8.
82
	 * @throws TInvalidDataTypeException If data is not null and neither an array nor an iterator.
83
	 */
84
	public function __construct($data = null, $readOnly = null, $defaultPriority = null, $precision = null)
85
	{
86
		$this->setPrecision($precision);
87
		$this->setDefaultPriority($defaultPriority);
88
		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

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

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

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

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