Skip to content

Commit 5f1f90a

Browse files
authored
Feature/Improved TypedCollection. (#3) (#5)
1 parent ec01db3 commit 5f1f90a

File tree

3 files changed

+290
-42
lines changed

3 files changed

+290
-42
lines changed

src/Traits/HasTypeCheck.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace ComplexHeart\Domain\Model\Traits;
6+
7+
/**
8+
* Trait HasTypeValidation
9+
*
10+
* @author Unay Santisteban <usantisteban@othercode.es>
11+
* @package ComplexHeart\Domain\Model\Traits
12+
*/
13+
trait HasTypeCheck
14+
{
15+
/**
16+
* Assert that the given value type match the required validType.
17+
*
18+
* @param mixed $value
19+
* @param string $validType
20+
*
21+
* @return bool
22+
*/
23+
protected function isValueTypeValid($value, string $validType): bool
24+
{
25+
if ($validType === 'mixed') {
26+
return true;
27+
}
28+
29+
$primitives = ['integer', 'boolean', 'float', 'string', 'array', 'object', 'callable'];
30+
$validation = in_array($validType, $primitives)
31+
? fn($value): bool => gettype($value) === $validType
32+
: fn($value): bool => $value instanceof $validType;
33+
34+
return $validation($value);
35+
}
36+
37+
/**
38+
* Assert that the given value type NOT match the required validType.
39+
*
40+
* @param mixed $value
41+
* @param string $validType
42+
*
43+
* @return bool
44+
*/
45+
protected function isValueTypeNotValid($value, string $validType): bool
46+
{
47+
return !$this->isValueTypeValid($value, $validType);
48+
}
49+
}

src/TypedCollection.php

Lines changed: 140 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44

55
namespace ComplexHeart\Domain\Model;
66

7-
use Illuminate\Support\Collection;
87
use ComplexHeart\Domain\Model\Exceptions\InvariantViolation;
8+
use ComplexHeart\Domain\Model\Traits\HasTypeCheck;
99
use ComplexHeart\Domain\Model\Traits\HasInvariants;
10+
use Illuminate\Support\Collection;
1011

1112
/**
1213
* Class TypedCollection
@@ -17,6 +18,7 @@
1718
class TypedCollection extends Collection
1819
{
1920
use HasInvariants;
21+
use HasTypeCheck;
2022

2123
/**
2224
* The type of each key in the collection.
@@ -44,58 +46,165 @@ public function __construct(array $items = [])
4446
}
4547

4648
/**
47-
* Invariant: All items must be of the same type.
49+
* Assert that the key type is compliant with the collection definition.
4850
*
49-
* - If $typeOf is primitive check the type with gettype().
50-
* - If $typeOf is a class, check if the item is an instance of it.
51+
* @param mixed $key
5152
*
52-
* @return bool
5353
* @throws InvariantViolation
5454
*/
55-
protected function invariantItemsMustMatchTheRequiredType(): bool
55+
protected function checkKeyType($key): void
5656
{
57-
if ($this->valueType !== 'mixed') {
58-
$primitives = ['integer', 'boolean', 'float', 'string', 'array', 'object', 'callable'];
59-
$check = in_array($this->valueType, $primitives)
60-
? fn($value): bool => gettype($value) !== $this->valueType
61-
: fn($value): bool => !($value instanceof $this->valueType);
62-
63-
foreach ($this->items as $item) {
64-
if ($check($item)) {
65-
throw new InvariantViolation("All items must be type of {$this->valueType}");
66-
}
67-
}
57+
$supported = ['string', 'integer'];
58+
if (!in_array($this->keyType, $supported)) {
59+
throw new InvariantViolation(
60+
"Unsupported key type $this->keyType, must be one of ".implode(', ', $supported)
61+
);
6862
}
6963

70-
return true;
64+
if ($this->isValueTypeNotValid($key, $this->keyType)) {
65+
throw new InvariantViolation("All keys in the collection must be type of $this->keyType");
66+
}
7167
}
7268

7369
/**
74-
* Invariant: Check the collection keys to match the required type.
70+
* Assert that the item type is compliant with the collection definition.
71+
*
72+
* @param mixed $item
7573
*
76-
* Supported types:
74+
* @throws InvariantViolation
75+
*/
76+
protected function checkValueType($item): void
77+
{
78+
if ($this->isValueTypeNotValid($item, $this->valueType)) {
79+
throw new InvariantViolation("All items in the collection must be type of $this->valueType");
80+
}
81+
}
82+
83+
/**
84+
* Check the keys and values of the collection to match the required type.
85+
*
86+
* Supported types for keys:
7787
* - string
7888
* - integer
7989
*
90+
* Values can have any type:
91+
* - If $type is primitive check the type with gettype().
92+
* - If $type is a class, check if the item is an instance of it.
93+
*
8094
* @return bool
8195
* @throws InvariantViolation
8296
*/
83-
protected function invariantKeysMustMatchTheRequiredType(): bool
97+
protected function invariantKeysAndValuesMustMatchTheRequiredType(): bool
8498
{
85-
if ($this->keyType !== 'mixed') {
86-
$supported = ['string', 'integer'];
87-
if (!in_array($this->keyType, $supported)) {
88-
throw new InvariantViolation(
89-
"Unsupported key type, must be one of ".implode(', ', $supported)
90-
);
99+
if ($this->keyType === 'mixed' && $this->valueType === 'mixed') {
100+
return true;
101+
}
102+
103+
foreach ($this->items as $key => $item) {
104+
if ($this->keyType !== 'mixed') {
105+
$this->checkKeyType($key);
91106
}
92107

93-
foreach ($this->items as $index => $item) {
94-
if (gettype($index) !== $this->keyType) {
95-
throw new InvariantViolation("All keys must be type of {$this->keyType}");
96-
}
108+
if ($this->valueType !== 'mixed') {
109+
$this->checkValueType($item);
97110
}
98111
}
112+
99113
return true;
100114
}
115+
116+
/**
117+
* Push one or more items onto the end of the collection.
118+
*
119+
* @param mixed $values [optional]
120+
*
121+
* @return static
122+
* @throws InvariantViolation
123+
*/
124+
public function push(...$values)
125+
{
126+
foreach ($values as $value) {
127+
$this->checkValueType($value);
128+
}
129+
130+
return parent::push(...$values);
131+
}
132+
133+
/**
134+
* Offset to set.
135+
*
136+
* @param mixed $key
137+
* @param mixed $value
138+
*
139+
* @throws InvariantViolation
140+
*/
141+
#[\ReturnTypeWillChange]
142+
public function offsetSet($key, $value)
143+
{
144+
if ($this->keyType !== 'mixed') {
145+
$this->checkKeyType($key);
146+
}
147+
148+
$this->checkValueType($value);
149+
150+
parent::offsetSet($key, $value);
151+
}
152+
153+
/**
154+
* Push an item onto the beginning of the collection.
155+
*
156+
* @param mixed $value
157+
* @param null $key
158+
*
159+
* @return static
160+
* @throws InvariantViolation
161+
*/
162+
public function prepend($value, $key = null)
163+
{
164+
if ($this->keyType !== 'mixed') {
165+
$this->checkKeyType($key);
166+
}
167+
168+
$this->checkValueType($value);
169+
170+
return parent::prepend($value, $key);
171+
}
172+
173+
/**
174+
* Add an item to the collection.
175+
*
176+
* @param mixed $item
177+
*
178+
* @return static
179+
* @throws InvariantViolation
180+
*/
181+
public function add($item)
182+
{
183+
$this->checkValueType($item);
184+
185+
return parent::add($item);
186+
}
187+
188+
/**
189+
* Get the values of a given key.
190+
*
191+
* @param string|array|int|null $value
192+
* @param string|null $key
193+
*
194+
* @return Collection
195+
*/
196+
public function pluck($value, $key = null)
197+
{
198+
return $this->toBase()->pluck($value, $key);
199+
}
200+
201+
/**
202+
* Get the keys of the collection items.
203+
*
204+
* @return Collection
205+
*/
206+
public function keys(): Collection
207+
{
208+
return $this->toBase()->keys();
209+
}
101210
}

0 commit comments

Comments
 (0)