Passed
Push — enums ( 8bfe56...7bf697 )
by Fabio
06:38 queued 01:28
created

TPriorityMap::copyFrom()   C

Complexity

Conditions 14
Paths 18

Size

Total Lines 31
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 14
eloc 21
nc 18
nop 1
dl 0
loc 31
rs 6.2666
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
 * 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
	/**
72
	 * Constructor.
73
	 * Initializes the array with an array or an iterable object.
74
	 * @param null|array|TPriorityList|TPriorityMap|\Traversable $data the initial data. Default is null, meaning no initialization.
75
	 * @param bool $readOnly whether the list is read-only
76
	 * @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...
77
	 * @param int $precision the precision of the numeric priorities
78
	 * @throws TInvalidDataTypeException If data is not null and neither an array nor an iterator.
79
	 */
80
	public function __construct($data = null, $readOnly = false, $defaultPriority = 10, $precision = 8)
81
	{
82
		$this->setPrecision($precision);
83
		$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

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

84
		parent::__construct(/** @scrutinizer ignore-type */ $data, $readOnly);
Loading history...
85
	}
86
87
	/**
88
	 * @return int the number of items in the map
89
	 */
90
	public function getCount(): int
91
	{
92
		return $this->_c;
93
	}
94
95
	/**
96
	 * Returns the keys within the map ordered through the priority of each key-value pair
97
	 * @return array the key list
98
	 */
99
	public function getKeys(): array
100
	{
101
		$this->sortPriorities();
102
		return array_merge(...array_map('array_keys', $this->_d));
103
	}
104
105
	/**
106
	 * Returns the item with the specified key.  If a priority is specified, only items
107
	 * within that specific priority will be selected.
108
	 * @param mixed $key the key
109
	 * @param null|false|numeric $priority the priority.  null is the default priority, false is any priority,
110
	 *    and numeric is a specific priority.  default: false, any priority.
111
	 * @return mixed the element at the offset, null if no element is found at the offset
112
	 */
113
	public function itemAt($key, $priority = false)
114
	{
115
		if ($priority === false) {
116
			$this->flattenPriorities();
117
			return array_key_exists($key, $this->_fd) ? $this->_fd[$key] : $this->dyNoItem(null, $key);
118
		} else {
119
			$priority = $this->ensurePriority($priority);
120
			return (isset($this->_d[$priority]) && array_key_exists($key, $this->_d[$priority])) ? $this->_d[$priority][$key] : $this->dyNoItem(null, $key);
121
		}
122
	}
123
124
	/**
125
	 * This changes an item's priority.  Specify the item and the new priority.
126
	 * This method is exactly the same as {@link offsetGet}.
127
	 * @param mixed $key the key
128
	 * @param null|numeric $priority the priority.  default: null, filled in with the default priority numeric.
129
	 * @return numeric old priority of the item
130
	 */
131
	public function setPriorityAt($key, $priority = null)
132
	{
133
		$priority = $this->ensurePriority($priority);
134
		$oldpriority = $this->priorityAt($key);
135
		if ($oldpriority !== false && $oldpriority != $priority) {
136
			$value = $this->remove($key, $oldpriority);
137
			$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

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

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