Passed
Push — master ( b55437...59ccbb )
by Jakub
01:42
created

TCollection::getItem()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 6
ccs 4
cts 4
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
4
namespace Nexendrie\Utils;
5
6
/**
7
 * TCollection
8
 * Target class has to implement \ArrayAccess, \Countable, \IteratorAggregate interfaces
9
 *
10
 * @author Jakub Konečný
11
 */
12 1
trait TCollection {
13
  /** @var object[] */
14
  protected $items = [];
15
  /** @var string Type of items in the collection */
16
  protected $class;
17
  /** @var string|NULL */
18
  protected $uniqueProperty = null;
19
  /** @var int */
20
  protected $maxSize = 0;
21
  /** @var bool */
22
  protected $locked = false;
23
  /** @var callable[] */
24
  protected $checkers = [];
25
  
26
  public function __construct() {
27 1
    $this->addChecker([$this, "checkLock"]);
28 1
    $this->addChecker([$this, "checkType"]);
29 1
    $this->addChecker([$this, "checkUniqueness"]);
30 1
    $this->addChecker([$this, "checkSize"]);
31 1
  }
32
  
33
  public function isLocked(): bool {
34 1
    return $this->locked;
35
  }
36
  
37
  public function lock(): void {
38 1
    $this->locked = true;
39 1
  }
40
  
41
  public function count(): int {
42 1
    return count($this->items);
43
  }
44
  
45
  public function getIterator(): \ArrayIterator {
46 1
    return new \ArrayIterator($this->items);
47
  }
48
  
49
  /**
50
   * @param int $index
51
   */
52
  public function offsetExists($index): bool {
53 1
    return $index >= 0 AND $index < count($this->items);
54
  }
55
  
56
  /**
57
   * @param int|NULL $index
58
   * @return object
59
   * @throws \OutOfRangeException
60
   */
61
  public function offsetGet($index) {
62 1
    if($index < 0 OR $index >= count($this->items)) {
63 1
      throw new \OutOfRangeException("Offset invalid or out of range.");
64
    }
65 1
    return $this->items[$index];
66
  }
67
  
68
  public function addChecker(callable $checker): void {
69 1
    $this->checkers[] = $checker;
70 1
  }
71
  
72
  /**
73
   * @param object $newItem
74
   */
75
  protected function checkLock($newItem, self $collection): void {
1 ignored issue
show
introduced by
The method parameter $newItem is never used
Loading history...
76 1
    if($collection->locked) {
77 1
      throw new \RuntimeException("Cannot add items to locked collection.");
78
    }
79 1
  }
80
  
81
  /**
82
   * @param object $newItem
83
   */
84
  protected function checkType($newItem, self $collection): void {
85 1
    if(!$newItem instanceof $collection->class) {
86 1
      throw new \InvalidArgumentException("Argument must be of $this->class type.");
87
    }
88 1
  }
89
  
90
  /**
91
   * @param object $newItem
92
   */
93
  protected function checkUniqueness($newItem, self $collection): void {
94 1
    $uniqueProperty = $collection->uniqueProperty;
95 1
    if(is_null($uniqueProperty)) {
96 1
      return;
97
    }
98 1
    if($this->hasItems([$uniqueProperty => $newItem->$uniqueProperty])) {
99 1
      throw new \RuntimeException("Duplicate $uniqueProperty {$newItem->$uniqueProperty}.");
100
    }
101 1
  }
102
  
103
  /**
104
   * @param object $newItem
105
   */
106
  protected function checkSize($newItem, self $collection): void {
1 ignored issue
show
introduced by
The method parameter $newItem is never used
Loading history...
107 1
    if($collection->maxSize < 1) {
108 1
      return;
109
    }
110 1
    if($collection->count() + 1 > $collection->maxSize) {
111 1
      throw new \RuntimeException("Collection reached its max size. Cannot add more items.");
112
    }
113 1
  }
114
  
115
  /**
116
   * @param object $item
117
   */
118
  protected function performChecks($item): void {
119 1
    foreach($this->checkers as $checker) {
120 1
      call_user_func($checker, $item, $this);
121
    }
122 1
  }
123
  
124
  /**
125
   * @param int|NULL $index
126
   * @param object $item
127
   * @throws \OutOfRangeException
128
   * @throws \InvalidArgumentException
129
   * @throws \RuntimeException
130
   */
131
  public function offsetSet($index, $item): void {
132 1
    $this->performChecks($item);
133 1
    if($index === null) {
134 1
      $this->items[] = & $item;
135 1
    } elseif($index < 0 OR $index >= count($this->items)) {
136 1
      throw new \OutOfRangeException("Offset invalid or out of range.");
137
    } else {
138 1
      $this->items[$index] = & $item;
139
    }
140 1
  }
141
  
142
  /**
143
   * @param int $index
144
   * @throws \OutOfRangeException
145
   */
146
  public function offsetUnset($index): void {
147 1
    if($this->locked) {
148 1
      throw new \RuntimeException("Cannot remove items from locked collection.");
149 1
    } elseif($index < 0 OR $index >= count($this->items)) {
150 1
      throw new \OutOfRangeException("Offset invalid or out of range.");
151
    }
152 1
    array_splice($this->items, $index, 1);
153 1
  }
154
  
155
  public function toArray(): array {
156 1
    return $this->items;
157
  }
158
  
159
  /**
160
   * Create new collection from array
161
   */
162
  public static function fromArray(array $items, ...$args): self {
163 1
    $collection = new static(...$args);
1 ignored issue
show
Unused Code introduced by
The call to Nexendrie\Utils\TCollection::__construct() has too many arguments starting with $args. ( Ignorable by Annotation )

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

163
    $collection = /** @scrutinizer ignore-call */ new static(...$args);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
164 1
    foreach($items as $item) {
165 1
      $collection[] = $item;
166
    }
167 1
    return $collection;
168
  }
169
  
170
  /**
171
   * Check if the collection has at least 1 item matching the filter
172
   */
173
  public function hasItems(array $filter = []): bool {
174 1
    return (count($this->getItems($filter)) > 0);
175
  }
176
  
177
  /**
178
   * Get all items matching the filter
179
   */
180
  public function getItems(array $filter = []): array {
181 1
    return Filter::applyFilter($this->items, $filter);
182
  }
183
184
  /**
185
   * Get first item matching the filter
186
   *
187
   * @return object|null
188
   */
189
  public function getItem(array $filter) {
190 1
    $items = $this->getItems($filter);
191 1
    if(count($items) === 0) {
192 1
      return null;
193
    }
194 1
    return $items[0];
195
  }
196
}
197
?>