Completed
Push — master ( 219e8a...bb5999 )
by Jakub
02:19
created

TCollection::getItems()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 7
nc 2
nop 1
dl 0
loc 11
ccs 7
cts 7
cp 1
crap 4
rs 9.2
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
  protected $items = [];
14
  /** @var string Type of items in the collection */
15
  protected $class;
16
  /** @var string|NULL */
17
  protected $uniqueProperty = NULL;
18
  /** @var int */
19
  protected $maxSize = 0;
20
  /** @var bool */
21
  protected $locked = false;
22
  /** @var callable[] */
23
  protected $checkers = [];
24
  
25
  public function __construct() {
26 1
    $this->addChecker([$this, "checkLock"]);
27 1
    $this->addChecker([$this, "checkType"]);
28 1
    $this->addChecker([$this, "checkUniqueness"]);
29 1
    $this->addChecker([$this, "checkSize"]);
30 1
  }
31
  
32
  public function isLocked(): bool {
33 1
    return $this->locked;
34
  }
35
  
36
  public function lock(): void {
37 1
    $this->locked = true;
38 1
  }
39
  
40
  public function count(): int {
41 1
    return count($this->items);
42
  }
43
  
44
  public function getIterator(): \ArrayIterator {
45 1
    return new \ArrayIterator($this->items);
46
  }
47
  
48
  /**
49
   * @param int $index
50
   */
51
  public function offsetExists($index): bool {
52 1
    return $index >= 0 AND $index < count($this->items);
53
  }
54
  
55
  /**
56
   * @param int|NULL $index
57
   * @throws \OutOfRangeException
58
   */
59
  public function offsetGet($index) {
60 1
    if($index < 0 OR $index >= count($this->items)) {
61 1
      throw new \OutOfRangeException("Offset invalid or out of range.");
62
    }
63 1
    return $this->items[$index];
64
  }
65
  
66
  public function addChecker(callable $checker): void {
67 1
    $this->checkers[] = $checker;
68 1
  }
69
  
70
  /**
71
   * @param object $newItem
72
   */
73
  protected function checkLock($newItem, self $collection): void {
74 1
    if($collection->locked) {
75 1
      throw new \RuntimeException("Cannot add items to locked collection.");
76
    }
77 1
  }
78
  
79
  /**
80
   * @param object $newItem
81
   */
82
  protected function checkType($newItem, self $collection): void {
83 1
    if(!$newItem instanceof $collection->class) {
84 1
      throw new \InvalidArgumentException("Argument must be of $this->class type.");
85
    }
86 1
  }
87
  
88
  /**
89
   * @param object $newItem
90
   */
91
  protected function checkUniqueness($newItem, self $collection): void {
92 1
    $uniqueProperty = $collection->uniqueProperty;
93 1
    if(is_null($uniqueProperty)) {
94 1
      return;
95
    }
96 1
    foreach($collection->items as $item) {
97 1
      if($newItem->$uniqueProperty === $item->$uniqueProperty) {
98 1
        throw new \RuntimeException("Duplicate $uniqueProperty {$item->$uniqueProperty}.");
99
      }
100
    }
101 1
  }
102
  
103
  /**
104
   * @param object $newItem
105
   */
106
  protected function checkSize($newItem, self $collection): void {
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
   * @todo make it possible to use different comparing rules
181
   */
182
  public function getItems(array $filter = []): array {
183 1
    if(count($filter) === 0) {
184 1
      return $this->items;
185
    }
186 1
    return array_values(array_filter($this->items, function($item) use($filter) {
187 1
      foreach($filter as $key => $value) {
188 1
        if($item->$key !== $value) {
189 1
          return false;
190
        }
191
      }
192 1
      return true;
193 1
    }));
194
  }
195
}
196
?>