Completed
Branch master (099915)
by Fabio
08:02
created

TPriorityList::copyFrom()   B

Complexity

Conditions 10
Paths 12

Size

Total Lines 20
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 10.0363

Importance

Changes 0
Metric Value
cc 10
eloc 13
c 0
b 0
f 0
nc 12
nop 1
dl 0
loc 20
ccs 13
cts 14
cp 0.9286
crap 10.0363
rs 7.6666

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
 * TPriorityList, TPriorityListIterator 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
 * @package Prado\Collections
9
 */
10
11
namespace Prado\Collections;
12
13
use Prado\Exceptions\TInvalidDataTypeException;
14
use Prado\Exceptions\TInvalidDataValueException;
15
use Prado\Exceptions\TInvalidOperationException;
16
use Prado\TPropertyValue;
17
18
/**
19
 * TPriorityList class
20
 *
21
 * TPriorityList implements a priority ordered list collection class.  It allows you to specify
22
 * any numeric for priorities down to a specific precision.  The lower the numeric, the high the priority of the item in the
23
 * list.  Thus -10 has a higher priority than -5, 0, 10 (the default), 18, 10005, etc.  Per {@link round}, precision may be negative and
24
 * thus rounding can go by 10, 100, 1000, etc, instead of just .1, .01, .001, etc. The default precision allows for 8 decimal
25
 * places. There is also a default priority of 10, if no different default priority is specified or no item specific priority is indicated.
26
 * If you replace TList with this class it will  work exactly the same with items inserted set to the default priority, until you start
27
 * using different priorities than the default priority.
28
 *
29
 * As you access the PHP array features of this class, it flattens and caches the results.  If at all possible, this
30
 * will keep the cache fresh even when manipulated.  If this is not possible the cache is cleared.
31
 * When an array of items are needed and the cache is outdated, the cache is recreated from the items and their priorities
32
 *
33
 * You can access, append, insert, remove an item by using
34
 * {@link itemAt}, {@link add}, {@link insertAt}, and {@link remove}.
35
 * To get the number of the items in the list, use {@link getCount}.
36
 * TPriorityList can also be used like a regular array as follows,
37
 * <code>
38
 * $list[]=$item;  // append with the default priority.  It may not be the last item if other items in the list are prioritized after the default priority
39
 * $list[$index]=$item; // $index must be between 0 and $list->Count-1.  This sets the element regardless of priority.  Priority stays the same.
40
 * $list[$index]=$item; // $index is $list->Count.  This appends the item to the end of the list with the same priority as the last item in the list.
41
 * unset($list[$index]); // remove the item at $index
42
 * if(isset($list[$index])) // if the list has an item at $index
43
 * foreach($list as $index=>$item) // traverse each item in the list in proper priority order and add/insert order
44
 * $n=count($list); // returns the number of items in the list
45
 * </code>
46
 *
47
 * To extend TPriorityList for doing your own operations with each addition or removal,
48
 * override {@link insertAtIndexInPriority()} and {@link removeAtIndexInPriority()} and then call the parent.
49
 *
50
 * @author Brad Anderson <[email protected]>
51
 * @package Prado\Collections
52
 * @since 3.2a
53
 */
54
class TPriorityList extends TList
55
{
56
	/**
57
	 * @var array internal data storage
58
	 */
59
	private $_d = [];
60
	/**
61
	 * @var bool indicates if the _d is currently ordered.
62
	 */
63
	private $_o = false;
64
	/**
65
	 * @var array cached flattened internal data storage
66
	 */
67
	private $_fd;
68
	/**
69
	 * @var int number of items contain within the list
70
	 */
71
	private $_c = 0;
72
	/**
73
	 * @var numeric 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...
74
	 */
75
	private $_dp = 10;
76
	/**
77
	 * @var int the precision of the numeric priorities within this priority list.
78
	 */
79
	private $_p = 8;
80
81
	/**
82
	 * Constructor.
83
	 * Initializes the list with an array or an iterable object.
84
	 * @param null|array|Iterator $data the intial data. Default is null, meaning no initial data.
0 ignored issues
show
Bug introduced by
The type Prado\Collections\Iterator was not found. Did you mean Iterator? If so, make sure to prefix the type with \.
Loading history...
85
	 * @param bool $readOnly whether the list is read-only
86
	 * @param numeric $defaultPriority the default priority of items without specified priorities.
87
	 * @param int $precision the precision of the numeric priorities
88
	 * @throws TInvalidDataTypeException If data is not null and is neither an array nor an iterator.
89
	 */
90 90
	public function __construct($data = null, $readOnly = false, $defaultPriority = 10, $precision = 8)
91
	{
92 90
		parent::__construct();
93 90
		if ($data !== null) {
94 25
			$this->copyFrom($data);
95
		}
96 90
		$this->setReadOnly($readOnly);
97 90
		$this->setPrecision($precision);
98 90
		$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...t::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

98
		$this->setDefaultPriority(/** @scrutinizer ignore-type */ $defaultPriority);
Loading history...
99 90
	}
100
101
	/**
102
	 * Returns the number of items in the list.
103
	 * This method is required by \Countable interface.
104
	 * @return int number of items in the list.
105
	 */
106 3
	public function count()
107
	{
108 3
		return $this->getCount();
109
	}
110
111
	/**
112
	 * Returns the total number of items in the list
113
	 * @return int the number of items in the list
114
	 */
115 133
	public function getCount()
116
	{
117 133
		return $this->_c;
118
	}
119
120
	/**
121
	 * Gets the number of items at a priority within the list
122
	 * @param null|numeric $priority optional priority at which to count items.  if no parameter, it will be set to the default {@link getDefaultPriority}
123
	 * @return int the number of items in the list at the specified priority
124
	 */
125 2
	public function getPriorityCount($priority = null)
126
	{
127 2
		if ($priority === null) {
128 2
			$priority = $this->getDefaultPriority();
129
		}
130 2
		$priority = (string) round(TPropertyValue::ensureFloat($priority), $this->_p);
131
132 2
		if (!isset($this->_d[$priority]) || !is_array($this->_d[$priority])) {
133
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type integer.
Loading history...
134
		}
135 2
		return count($this->_d[$priority]);
136
	}
137
138
	/**
139
	 * @return numeric gets the default priority of inserted items without a specified priority
140
	 */
141 116
	public function getDefaultPriority()
142
	{
143 116
		return $this->_dp;
144
	}
145
146
	/**
147
	 * This must be called internally or when instantiated.
148
	 * @param numeric $value sets the default priority of inserted items without a specified priority
149
	 */
150 90
	protected function setDefaultPriority($value)
151
	{
152 90
		$this->_dp = (string) round(TPropertyValue::ensureFloat($value), $this->_p);
0 ignored issues
show
Documentation Bug introduced by
It seems like (string)round(Prado\TPro...oat($value), $this->_p) of type string is incompatible with the declared type Prado\Collections\numeric of property $_dp.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
153 90
	}
154
155
	/**
156
	 * @return int The precision of numeric priorities, defaults to 8
157
	 */
158 1
	public function getPrecision()
159
	{
160 1
		return $this->_p;
161
	}
162
163
	/**
164
	 * This must be called internally or when instantiated.
165
	 * @param int $value The precision of numeric priorities.
166
	 */
167 90
	protected function setPrecision($value)
168
	{
169 90
		$this->_p = TPropertyValue::ensureInteger($value);
170 90
	}
171
172
	/**
173
	 * Returns an iterator for traversing the items in the list.
174
	 * This method is required by the interface \IteratorAggregate.
175
	 * @return Iterator an iterator for traversing the items in the list.
176
	 */
177 2
	public function getIterator()
178
	{
179 2
		return new \ArrayIterator($this->flattenPriorities());
0 ignored issues
show
Bug Best Practice introduced by
The expression return new ArrayIterator...s->flattenPriorities()) returns the type ArrayIterator which is incompatible with the documented return type Prado\Collections\Iterator.
Loading history...
180
	}
181
182
	/**
183
	 * This returns a list of the priorities within this list, ordered lowest to highest.
184
	 * @return array the array of priority numerics in decreasing priority order
185
	 */
186 16
	public function getPriorities()
187
	{
188 16
		$this->sortPriorities();
189 16
		return array_keys($this->_d);
190
	}
191
192
193
	/**
194
	 * This orders the priority list internally.
195
	 */
196 121
	protected function sortPriorities()
197
	{
198 121
		if (!$this->_o) {
199 112
			ksort($this->_d, SORT_NUMERIC);
200 112
			$this->_o = true;
201
		}
202 121
	}
203
204
	/**
205
	 * This flattens the priority list into a flat array [0,...,n-1]
206
	 * @return array array of items in the list in priority and index order
207
	 */
208 93
	protected function flattenPriorities()
209
	{
210 93
		if (is_array($this->_fd)) {
0 ignored issues
show
introduced by
The condition is_array($this->_fd) is always true.
Loading history...
211 68
			return $this->_fd;
212
		}
213
214 59
		$this->sortPriorities();
215 59
		$this->_fd = [];
216 59
		foreach ($this->_d as $priority => $itemsatpriority) {
217 56
			$this->_fd = array_merge($this->_fd, $itemsatpriority);
218
		}
219 59
		return $this->_fd;
220
	}
221
222
223
	/**
224
	 * Returns the item at the index of a flattened priority list.
225
	 * {@link offsetGet} calls this method.
226
	 * @param int $index the index of the item to get
227
	 * @throws TInvalidDataValueException Issued when the index is invalid
228
	 * @return mixed the element at the offset
229
	 */
230 8
	public function itemAt($index)
231
	{
232 8
		if ($index >= 0 && $index < $this->getCount()) {
233 8
			$arr = $this->flattenPriorities();
234 8
			return $arr[$index];
235
		} else {
236 2
			throw new TInvalidDataValueException('list_index_invalid', $index);
237
		}
238
	}
239
240
	/**
241
	 * Gets all the items at a specific priority.
242
	 * @param null|numeric $priority priority of the items to get.  Defaults to null, filled in with the default priority, if left blank.
243
	 * @return array all items at priority in index order, null if there are no items at that priority
244
	 */
245 16
	public function itemsAtPriority($priority = null)
246
	{
247 16
		if ($priority === null) {
248 1
			$priority = $this->getDefaultPriority();
249
		}
250 16
		$priority = (string) round(TPropertyValue::ensureFloat($priority), $this->_p);
251
252 16
		return isset($this->_d[$priority]) ? $this->_d[$priority] : null;
253
	}
254
255
	/**
256
	 * Returns the item at an index within a priority
257
	 * @param int $index the index into the list of items at priority
258
	 * @param numeric $priority the priority which to index.  no parameter or null will result in the default priority
259
	 * @return mixed the element at the offset, false if no element is found at the offset
260
	 */
261 1
	public function itemAtIndexInPriority($index, $priority = null)
262
	{
263 1
		if ($priority === null) {
264 1
			$priority = $this->getDefaultPriority();
265
		}
266 1
		$priority = (string) round(TPropertyValue::ensureFloat($priority), $this->_p);
267
268 1
		return !isset($this->_d[$priority]) ? false : (
269 1
			isset($this->_d[$priority][$index]) ? $this->_d[$priority][$index] : false
270
			);
271
	}
272
273
	/**
274
	 * Appends an item into the list at the end of the specified priority.  The position of the added item may
275
	 * not be at the end of the list.
276
	 * @param mixed $item item to add into the list at priority
277
	 * @param null|numeric $priority priority blank or null for the default priority
278
	 * @throws TInvalidOperationException if the map is read-only
279
	 * @return int the index within the flattened array
280
	 */
281 116
	public function add($item, $priority = null)
282
	{
283 116
		if ($this->getReadOnly()) {
284 1
			throw new TInvalidOperationException('list_readonly', get_class($this));
285
		}
286
287 116
		return $this->insertAtIndexInPriority($item, false, $priority, true);
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type integer expected by parameter $index of Prado\Collections\TPrior...sertAtIndexInPriority(). ( Ignorable by Annotation )

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

287
		return $this->insertAtIndexInPriority($item, /** @scrutinizer ignore-type */ false, $priority, true);
Loading history...
288
	}
289
290
	/**
291
	 * Inserts an item at an index.  It reads the priority of the item at index within the flattened list
292
	 * and then inserts the item at that priority-index.
293
	 * @param int $index the specified position in the flattened list.
294
	 * @param mixed $item new item to add
295
	 * @throws TInvalidDataValueException If the index specified exceeds the bound
296
	 * @throws TInvalidOperationException if the list is read-only
297
	 */
298 3
	public function insertAt($index, $item)
299
	{
300 3
		if ($this->getReadOnly()) {
301 1
			throw new TInvalidOperationException('list_readonly', get_class($this));
302
		}
303
304 2
		if (($priority = $this->priorityAt($index, true)) !== false) {
0 ignored issues
show
introduced by
The condition $priority = $this->prior...$index, true) !== false is always true.
Loading history...
305 2
			$this->insertAtIndexInPriority($item, $priority[1], $priority[0]);
306
		} else {
307
			throw new TInvalidDataValueException('list_index_invalid', $index);
308
		}
309 2
	}
310
311
	/**
312
	 * Inserts an item at the specified index within a priority.  Override and call this method to
313
	 * insert your own functionality.
314
	 * @param mixed $item item to add within the list.
315
	 * @param int $index index within the priority to add the item, defaults to false which appends the item at the priority
316
	 * @param null|numeric $priority priority of the item.  defaults to null, which sets it to the default priority
317
	 * @param bool $preserveCache preserveCache specifies if this is a special quick function or not. This defaults to false.
318
	 * @throws TInvalidDataValueException If the index specified exceeds the bound
319
	 * @throws TInvalidOperationException if the list is read-only
320
	 */
321 116
	public function insertAtIndexInPriority($item, $index = false, $priority = null, $preserveCache = false)
322
	{
323 116
		if ($this->getReadOnly()) {
324 1
			throw new TInvalidOperationException('list_readonly', get_class($this));
325
		}
326
327 116
		if ($priority === null) {
328 116
			$priority = $this->getDefaultPriority();
329
		}
330 116
		$priority = (string) round(TPropertyValue::ensureFloat($priority), $this->_p);
331
332 116
		if ($preserveCache) {
333 116
			$this->sortPriorities();
334 116
			$cc = 0;
335 116
			foreach ($this->_d as $prioritykey => $items) {
336 89
				if ($prioritykey >= $priority) {
337 89
					break;
338
				} else {
339 65
					$cc += count($items);
340
				}
341
			}
342
343 116
			if ($index === false && isset($this->_d[$priority])) {
344 89
				$c = count($this->_d[$priority]);
345 89
				$c += $cc;
346 89
				$this->_d[$priority][] = $item;
347 106
			} elseif (isset($this->_d[$priority])) {
348 1
				$c = $index + $cc;
349 1
				array_splice($this->_d[$priority], $index, 0, [$item]);
0 ignored issues
show
Bug introduced by
It seems like $index can also be of type false; however, parameter $offset of array_splice() does only seem to accept integer, 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
				array_splice($this->_d[$priority], /** @scrutinizer ignore-type */ $index, 0, [$item]);
Loading history...
350
			} else {
351 106
				$c = $cc;
352 106
				$this->_o = false;
353 106
				$this->_d[$priority] = [$item];
354
			}
355
356 116
			if ($this->_fd && is_array($this->_fd)) { // if there is a flattened array cache
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->_fd of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
357 116
				array_splice($this->_fd, $c, 0, [$item]);
358
			}
359
		} else {
360 20
			$c = null;
361 20
			if ($index === false && isset($this->_d[$priority])) {
362 2
				$cc = count($this->_d[$priority]);
363 2
				$this->_d[$priority][] = $item;
364 20
			} elseif (isset($this->_d[$priority])) {
365 18
				$cc = $index;
366 18
				array_splice($this->_d[$priority], $index, 0, [$item]);
367
			} else {
368 17
				$cc = 0;
369 17
				$this->_o = false;
370 17
				$this->_d[$priority] = [$item];
371
			}
372 20
			if ($this->_fd && is_array($this->_fd) && count($this->_d) == 1) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->_fd of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
373 1
				array_splice($this->_fd, $cc, 0, [$item]);
374
			} else {
375 20
				$this->_fd = null;
376
			}
377
		}
378
379 116
		$this->_c++;
380
381 116
		return $c;
382
	}
383
384
385
	/**
386
	 * Removes an item from the priority list.
387
	 * The list will search for the item.  The first matching item found will be removed from the list.
388
	 * @param mixed $item item the item to be removed.
389
	 * @param null|bool|float $priority priority of item to remove. without this parameter it defaults to false.
390
	 * A value of false means any priority. null will be filled in with the default priority.
391
	 * @throws TInvalidDataValueException If the item does not exist
392
	 * @return int index within the flattened list at which the item is being removed
393
	 */
394 41
	public function remove($item, $priority = false)
395
	{
396 41
		if ($this->getReadOnly()) {
397 1
			throw new TInvalidOperationException('list_readonly', get_class($this));
398
		}
399
400 40
		if (($p = $this->priorityOf($item, true)) !== false) {
0 ignored issues
show
introduced by
The condition $p = $this->priorityOf($item, true) !== false is always true.
Loading history...
401 40
			if ($priority !== false) {
402 1
				if ($priority === null) {
0 ignored issues
show
introduced by
The condition $priority === null is always false.
Loading history...
403 1
					$priority = $this->getDefaultPriority();
404
				}
405 1
				$priority = (string) round(TPropertyValue::ensureFloat($priority), $this->_p);
406
407 1
				if ($p[0] != $priority) {
408 1
					throw new TInvalidDataValueException('list_item_inexistent');
409
				}
410
			}
411 40
			$this->removeAtIndexInPriority($p[1], $p[0]);
412 40
			return $p[2];
413
		} else {
414 2
			throw new TInvalidDataValueException('list_item_inexistent');
415
		}
416
	}
417
418
	/**
419
	 * Removes an item at the specified index in the flattened list.
420
	 * @param int $index index of the item to be removed.
421
	 * @throws TInvalidDataValueException If the index specified exceeds the bound
422
	 * @throws TInvalidOperationException if the list is read-only
423
	 * @return mixed the removed item.
424
	 */
425 5
	public function removeAt($index)
426
	{
427 5
		if ($this->getReadOnly()) {
428 1
			throw new TInvalidOperationException('list_readonly', get_class($this));
429
		}
430
431 4
		if (($priority = $this->priorityAt($index, true)) !== false) {
0 ignored issues
show
introduced by
The condition $priority = $this->prior...$index, true) !== false is always true.
Loading history...
432 4
			return $this->removeAtIndexInPriority($priority[1], $priority[0]);
433
		}
434
		throw new TInvalidDataValueException('list_index_invalid', $index);
435
	}
436
437
	/**
438
	 * Removes the item at a specific index within a priority.  Override
439
	 * and call this method to insert your own functionality.
440
	 * @param int $index index of item to remove within the priority.
441
	 * @param null|numeric $priority priority of the item to remove, defaults to null, or left blank, it is then set to the default priority
442
	 * @throws TInvalidDataValueException If the item does not exist
443
	 * @return mixed the removed item.
444
	 */
445 51
	public function removeAtIndexInPriority($index, $priority = null)
446
	{
447 51
		if ($this->getReadOnly()) {
448 1
			throw new TInvalidOperationException('list_readonly', get_class($this));
449
		}
450
451 50
		if ($priority === null) {
452
			$priority = $this->getDefaultPriority();
453
		}
454 50
		$priority = (string) round(TPropertyValue::ensureFloat($priority), $this->_p);
455
456 50
		if (!isset($this->_d[$priority]) || $index < 0 || $index >= count($this->_d[$priority])) {
457 1
			throw new TInvalidDataValueException('list_item_inexistent');
458
		}
459
460
		// $value is an array of elements removed, only one
461 49
		$value = array_splice($this->_d[$priority], $index, 1);
462 49
		$value = $value[0];
463
464 49
		if (!count($this->_d[$priority])) {
465 30
			unset($this->_d[$priority]);
466
		}
467
468 49
		$this->_c--;
469 49
		$this->_fd = null;
470 49
		return $value;
471
	}
472
473
	/**
474
	 * Removes all items in the priority list by calling removeAtIndexInPriority from the last item to the first.
475
	 */
476 7
	public function clear()
477
	{
478 7
		if ($this->getReadOnly()) {
479 1
			throw new TInvalidOperationException('list_readonly', get_class($this));
480
		}
481
482 6
		foreach ($this->_d as $priority => $items) {
483 6
			for ($index = count($items) - 1;$index >= 0;$index--) {
484 6
				$this->removeAtIndexInPriority($index, $priority);
485
			}
486 6
			unset($this->_d[$priority]);
487
		}
488 6
	}
489
490
	/**
491
	 * @param mixed $item item
492
	 * @return bool whether the list contains the item
493
	 */
494 2
	public function contains($item)
495
	{
496 2
		return $this->indexOf($item) >= 0;
497
	}
498
499
	/**
500
	 * @param mixed $item item
501
	 * @return int the index of the item in the flattened list (0 based), -1 if not found.
502
	 */
503 13
	public function indexOf($item)
504
	{
505 13
		if (($index = array_search($item, $this->flattenPriorities(), true)) === false) {
506 11
			return -1;
507
		} else {
508 11
			return $index;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $index also could return the type string which is incompatible with the documented return type integer.
Loading history...
509
		}
510
	}
511
512
	/**
513
	 * Returns the priority of a particular item
514
	 * @param mixed $item the item to look for within the list
515
	 * @param bool $withindex this specifies if the full positional data of the item within the list is returned.
516
	 * 		This defaults to false, if no parameter is provided, so only provides the priority number of the item by default.
517
	 * @return array|numeric the priority of the item in the list, false if not found.
518
	 *   if withindex is true, an array is returned of [0 => $priority, 1 => $priorityIndex, 2 => flattenedIndex,
519
	 * 'priority' => $priority, 'index' => $priorityIndex, 'absindex' => flattenedIndex]
520
	 */
521 48
	public function priorityOf($item, $withindex = false)
522
	{
523 48
		$this->sortPriorities();
524
525 48
		$absindex = 0;
526 48
		foreach ($this->_d as $priority => $items) {
527 48
			if (($index = array_search($item, $items, true)) !== false) {
528 46
				$absindex += $index;
529 46
				return $withindex ? [$priority, $index, $absindex,
530 46
						'priority' => $priority, 'index' => $index, 'absindex' => $absindex] : $priority;
531
			} else {
532 11
				$absindex += count($items);
533
			}
534
		}
535
536 4
		return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type Prado\Collections\numeric|array.
Loading history...
537
	}
538
539
	/**
540
	 * Retutrns the priority of an item at a particular flattened index.
541
	 * @param int $index index of the item within the list
542
	 * @param bool $withindex this specifies if the full positional data of the item within the list is returned.
543
	 * 		This defaults to false, if no parameter is provided, so only provides the priority number of the item by default.
544
	 * @return array|numeric the priority of the item in the list, false if not found.
545
	 *   if withindex is true, an array is returned of [0 => $priority, 1 => $priorityIndex, 2 => flattenedIndex,
546
	 * 'priority' => $priority, 'index' => $priorityIndex, 'absindex' => flattenedIndex]
547
	 */
548 12
	public function priorityAt($index, $withindex = false)
549
	{
550 12
		if ($index < 0 || $index >= $this->getCount()) {
551 4
			throw new TInvalidDataValueException('list_index_invalid', $index);
552
		}
553
554 12
		$absindex = $index;
555 12
		$this->sortPriorities();
556 12
		foreach ($this->_d as $priority => $items) {
557 12
			if ($index >= ($c = count($items))) {
558 7
				$index -= $c;
559
			} else {
560 12
				return $withindex ? [$priority, $index, $absindex,
561 12
						'priority' => $priority, 'index' => $index, 'absindex' => $absindex] : $priority;
562
			}
563
		}
564
		return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type Prado\Collections\numeric|array.
Loading history...
565
	}
566
567
	/**
568
	 * This inserts an item before another item within the list.  It uses the same priority as the
569
	 * found index item and places the new item before it.
570
	 * @param mixed $indexitem the item to index
571
	 * @param mixed $item the item to add before indexitem
572
	 * @throws TInvalidDataValueException If the item does not exist
573
	 * @return int where the item has been inserted in the flattened list
574
	 */
575 3
	public function insertBefore($indexitem, $item)
576
	{
577 3
		if ($this->getReadOnly()) {
578 1
			throw new TInvalidOperationException('list_readonly', get_class($this));
579
		}
580
581 2
		if (($priority = $this->priorityOf($indexitem, true)) === false) {
0 ignored issues
show
introduced by
The condition $priority = $this->prior...exitem, true) === false is always false.
Loading history...
582 1
			throw new TInvalidDataValueException('list_item_inexistent');
583
		}
584
585 1
		$this->insertAtIndexInPriority($item, $priority[1], $priority[0]);
586
587 1
		return $priority[2];
588
	}
589
590
	/**
591
	 * This inserts an item after another item within the list.  It uses the same priority as the
592
	 * found index item and places the new item after it.
593
	 * @param mixed $indexitem the item to index
594
	 * @param mixed $item the item to add after indexitem
595
	 * @throws TInvalidDataValueException If the item does not exist
596
	 * @return int where the item has been inserted in the flattened list
597
	 */
598 3
	public function insertAfter($indexitem, $item)
599
	{
600 3
		if ($this->getReadOnly()) {
601 1
			throw new TInvalidOperationException('list_readonly', get_class($this));
602
		}
603
604 2
		if (($priority = $this->priorityOf($indexitem, true)) === false) {
0 ignored issues
show
introduced by
The condition $priority = $this->prior...exitem, true) === false is always false.
Loading history...
605 1
			throw new TInvalidDataValueException('list_item_inexistent');
606
		}
607
608 1
		$this->insertAtIndexInPriority($item, $priority[1] + 1, $priority[0]);
609
610 1
		return $priority[2] + 1;
611
	}
612
613
	/**
614
	 * @return array the priority list of items in array
615
	 */
616 70
	public function toArray()
617
	{
618 70
		return $this->flattenPriorities();
619
	}
620
621
	/**
622
	 * @return array the array of priorities keys with values of arrays of items.  The priorities are sorted so important priorities, lower numerics, are first.
623
	 */
624 1
	public function toPriorityArray()
625
	{
626 1
		$this->sortPriorities();
627 1
		return $this->_d;
628
	}
629
630
	/**
631
	 * Combines the map elements which have a priority below the parameter value
632
	 * @param numeric $priority the cut-off priority.  All items of priority less than this are returned.
633
	 * @param bool $inclusive whether or not the input cut-off priority is inclusive.  Default: false, not inclusive.
634
	 * @return array the array of priorities keys with values of arrays of items that are below a specified priority.
635
	 *  The priorities are sorted so important priorities, lower numerics, are first.
636
	 */
637 2
	public function toArrayBelowPriority($priority, $inclusive = false)
638
	{
639 2
		$this->sortPriorities();
640 2
		$items = [];
641 2
		foreach ($this->_d as $itemspriority => $itemsatpriority) {
642 2
			if ((!$inclusive && $itemspriority >= $priority) || $itemspriority > $priority) {
643 2
				break;
644
			}
645 2
			$items = array_merge($items, $itemsatpriority);
646
		}
647 2
		return $items;
648
	}
649
650
	/**
651
	 * Combines the map elements which have a priority above the parameter value
652
	 * @param numeric $priority the cut-off priority.  All items of priority greater than this are returned.
653
	 * @param bool $inclusive whether or not the input cut-off priority is inclusive.  Default: true, inclusive.
654
	 * @return array the array of priorities keys with values of arrays of items that are above a specified priority.
655
	 *  The priorities are sorted so important priorities, lower numerics, are first.
656
	 */
657 2
	public function toArrayAbovePriority($priority, $inclusive = true)
658
	{
659 2
		$this->sortPriorities();
660 2
		$items = [];
661 2
		foreach ($this->_d as $itemspriority => $itemsatpriority) {
662 2
			if ((!$inclusive && $itemspriority <= $priority) || $itemspriority < $priority) {
663 2
				continue;
664
			}
665 2
			$items = array_merge($items, $itemsatpriority);
666
		}
667 2
		return $items;
668
	}
669
670
671
	/**
672
	 * Copies iterable data into the priority list.
673
	 * Note, existing data in the map will be cleared first.
674
	 * @param mixed $data the data to be copied from, must be an array or object implementing Traversable
675
	 * @throws TInvalidDataTypeException If data is neither an array nor an iterator.
676
	 */
677 27
	public function copyFrom($data)
678
	{
679 27
		if ($data instanceof TPriorityList) {
680 14
			if ($this->getCount() > 0) {
681
				$this->clear();
682
			}
683 14
			foreach ($data->getPriorities() as $priority) {
684 14
				foreach ($data->itemsAtPriority($priority) as $index => $item) {
685 14
					$this->insertAtIndexInPriority($item, $index, $priority);
686
				}
687
			}
688 15
		} elseif (is_array($data) || $data instanceof \Traversable) {
689 15
			if ($this->getCount() > 0) {
690 1
				$this->clear();
691
			}
692 15
			foreach ($data as $key => $item) {
693 15
				$this->add($item);
694
			}
695 1
		} elseif ($data !== null) {
696 1
			throw new TInvalidDataTypeException('map_data_not_iterable');
697
		}
698 27
	}
699
700
	/**
701
	 * Merges iterable data into the priority list.
702
	 * New data will be appended to the end of the existing data.  If another TPriorityList is merged,
703
	 * the incoming parameter items will be appended at the priorities they are present.  These items will be added
704
	 * to the end of the existing items with equal priorities, if there are any.
705
	 * @param mixed $data the data to be merged with, must be an array or object implementing Traversable
706
	 * @throws TInvalidDataTypeException If data is neither an array nor an iterator.
707
	 */
708 2
	public function mergeWith($data)
709
	{
710 2
		if ($data instanceof TPriorityList) {
711 1
			foreach ($data->getPriorities() as $priority) {
712 1
				foreach ($data->itemsAtPriority($priority) as $index => $item) {
713 1
					$this->insertAtIndexInPriority($item, false, $priority);
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type integer expected by parameter $index of Prado\Collections\TPrior...sertAtIndexInPriority(). ( Ignorable by Annotation )

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

713
					$this->insertAtIndexInPriority($item, /** @scrutinizer ignore-type */ false, $priority);
Loading history...
714
				}
715
			}
716 1
		} elseif (is_array($data) || $data instanceof \Traversable) {
717 1
			foreach ($data as $priority => $item) {
718 1
				$this->add($item);
719
			}
720 1
		} elseif ($data !== null) {
721 1
			throw new TInvalidDataTypeException('map_data_not_iterable');
722
		}
723 2
	}
724
725
	/**
726
	 * Returns whether there is an element at the specified offset.
727
	 * This method is required by the interface \ArrayAccess.
728
	 * @param mixed $offset the offset to check on
729
	 * @return bool
730
	 */
731 2
	public function offsetExists($offset)
732
	{
733 2
		return ($offset >= 0 && $offset < $this->getCount());
734
	}
735
736
	/**
737
	 * Returns the element at the specified offset.
738
	 * This method is required by the interface \ArrayAccess.
739
	 * @param int $offset the offset to retrieve element.
740
	 * @return mixed the element at the offset, null if no element is found at the offset
741
	 */
742 6
	public function offsetGet($offset)
743
	{
744 6
		return $this->itemAt($offset);
745
	}
746
747
	/**
748
	 * Sets the element at the specified offset. This method is required by the interface \ArrayAccess.
749
	 * Setting elements in a priority list is not straight forword when appending and setting at the
750
	 * end boundary.  When appending without an offset (a null offset), the item will be added at
751
	 * the default priority.  The item may not be the last item in the list.  When appending with an
752
	 * offset equal to the count of the list, the item will get be appended with the last items priority.
753
	 *
754
	 * All together, when setting the location of an item, the item stays in that location, but appending
755
	 * an item into a priority list doesn't mean the item is at the end of the list.
756
	 * @param int $offset the offset to set element
757
	 * @param mixed $item the element value
758
	 */
759 15
	public function offsetSet($offset, $item)
760
	{
761 15
		if ($offset === null) {
0 ignored issues
show
introduced by
The condition $offset === null is always false.
Loading history...
762 12
			return $this->add($item);
763
		}
764 3
		if ($offset === $this->getCount()) {
765 1
			$priority = $this->priorityAt($offset - 1, true);
766 1
			$priority[1]++;
767
		} else {
768 2
			$priority = $this->priorityAt($offset, true);
769 2
			$this->removeAtIndexInPriority($priority[1], $priority[0]);
770
		}
771 3
		$this->insertAtIndexInPriority($item, $priority[1], $priority[0]);
772 3
	}
773
774
	/**
775
	 * Unsets the element at the specified offset.
776
	 * This method is required by the interface \ArrayAccess.
777
	 * @param mixed $offset the offset to unset element
778
	 */
779 2
	public function offsetUnset($offset)
780
	{
781 2
		$this->removeAt($offset);
782 2
	}
783
}
784