Passed
Push — master ( 1e2338...e1e446 )
by Fabio
05:44
created

TPriorityMap   F

Complexity

Total Complexity 75

Size/Duplication

Total Lines 348
Duplicated Lines 0 %

Test Coverage

Coverage 92.91%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 141
dl 0
loc 348
rs 2.4
c 2
b 0
f 0
ccs 118
cts 127
cp 0.9291
wmc 75

15 Methods

Rating   Name   Duplication   Size   Complexity  
A priorityAt() 0 9 3
A clear() 0 8 3
C add() 0 40 14
C copyFrom() 0 31 14
A contains() 0 4 2
A _getZappableSleepProps() 0 10 3
A setPriorityAt() 0 9 3
A priorityOf() 0 9 3
B remove() 0 39 10
A itemAt() 0 8 5
A getKeys() 0 4 1
A __construct() 0 5 1
B mergeWith() 0 22 11
A getPriorityCombineStyle() 0 3 1
A getCount() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like TPriorityMap often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use TPriorityMap, and based on these observations, apply Extract Interface, too.

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

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

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