Skip to content

Commit e60a69f

Browse files
authored
Feature/Add PHPStan Extensions. (#13)
* PHPStan level 8 meet. * PHPStan level 7 meet. * PHPStan level 6 meet.
1 parent 1a7afad commit e60a69f

8 files changed

+97
-76
lines changed

composer.json

+6-3
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@
2424
"pestphp/pest": "^2.0",
2525
"pestphp/pest-plugin-mock": "^2.0",
2626
"pestphp/pest-plugin-faker": "^2.0",
27-
"phpstan/phpstan": "^1.0"
27+
"phpstan/phpstan": "^1.0",
28+
"phpstan/extension-installer": "^1.3",
29+
"phpstan/phpstan-mockery": "^1.1"
2830
},
2931
"autoload": {
3032
"psr-4": {
@@ -39,15 +41,16 @@
3941
"scripts": {
4042
"test": "vendor/bin/pest --configuration=phpunit.xml --coverage-clover=coverage.xml --log-junit=test.xml",
4143
"test-cov": "vendor/bin/pest --configuration=phpunit.xml --coverage-html=coverage",
42-
"analyse": "vendor/bin/phpstan analyse src --no-progress --level=5",
44+
"analyse": "vendor/bin/phpstan analyse src --no-progress --level=8",
4345
"check": [
4446
"@analyse",
4547
"@test"
4648
]
4749
},
4850
"config": {
4951
"allow-plugins": {
50-
"pestphp/pest-plugin": true
52+
"pestphp/pest-plugin": true,
53+
"phpstan/extension-installer": true
5154
}
5255
}
5356
}

src/IsModel.php

+20-9
Original file line numberDiff line numberDiff line change
@@ -26,26 +26,37 @@ trait IsModel
2626
* Initialize the Model. Just as the constructor will do.
2727
*
2828
* @param array<int|string, mixed> $source
29-
* @param callable|null $onFail
29+
* @param string|callable $onFail
3030
*
3131
* @return static
3232
*/
33-
protected function initialize(array $source, callable $onFail = null): static
33+
protected function initialize(array $source, string|callable $onFail = 'invariantHandler'): static
34+
{
35+
$this->hydrate($this->prepareAttributes($source));
36+
$this->check($onFail);
37+
38+
return $this;
39+
}
40+
41+
/**
42+
* Transform an indexed array into assoc array by combining the
43+
* given values with the list of attributes of the object.
44+
*
45+
* @param array<int|string, mixed> $source
46+
*
47+
* @return array<string, mixed>
48+
*/
49+
private function prepareAttributes(array $source): array
3450
{
3551
// check if the array is indexed or associative.
3652
$isIndexed = fn($source): bool => ([] !== $source) && array_keys($source) === range(0, count($source) - 1);
3753

38-
$source = $isIndexed($source)
54+
/** @var array<string, mixed> $source */
55+
return $isIndexed($source)
3956
// combine the attributes keys with the provided source values.
4057
? array_combine(array_slice(static::attributes(), 0, count($source)), $source)
4158
// return the already mapped array source.
4259
: $source;
43-
44-
45-
$this->hydrate($source);
46-
$this->check($onFail);
47-
48-
return $this;
4960
}
5061

5162
/**

src/Traits/HasAttributes.php

+6-18
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ final public function values(): array
4747
/**
4848
* Populate the object recursively.
4949
*
50-
* @param iterable $source
50+
* @param iterable<string, mixed> $source
5151
*/
5252
final protected function hydrate(iterable $source): void
5353
{
@@ -68,8 +68,8 @@ final protected function get(string $attribute): mixed
6868
if (in_array($attribute, static::attributes())) {
6969
$method = $this->getStringKey($attribute, 'get', 'Value');
7070

71-
return ($this->canCall($method))
72-
? call_user_func_array([$this, $method], [$this->{$attribute}])
71+
return method_exists($this, $method)
72+
? $this->{$method}($this->{$attribute})
7373
: $this->{$attribute};
7474
}
7575

@@ -87,8 +87,8 @@ final protected function set(string $attribute, mixed $value): void
8787
if (in_array($attribute, $this->attributes())) {
8888
$method = $this->getStringKey($attribute, 'set', 'Value');
8989

90-
$this->{$attribute} = ($this->canCall($method))
91-
? call_user_func_array([$this, $method], [$value])
90+
$this->{$attribute} = method_exists($this, $method)
91+
? $this->{$method}($value)
9292
: $value;
9393
}
9494
}
@@ -116,24 +116,12 @@ protected function getStringKey(string $id, string $prefix = '', string $suffix
116116
);
117117
}
118118

119-
/**
120-
* Check if the required method name is callable.
121-
*
122-
* @param string $method
123-
*
124-
* @return bool
125-
*/
126-
protected function canCall(string $method): bool
127-
{
128-
return method_exists($this, $method);
129-
}
130-
131119
/**
132120
* Dynamic method to access each attribute as method, i.e:
133121
* $user->name() will access the private attribute name.
134122
*
135123
* @param string $attribute
136-
* @param array $_
124+
* @param array<int, mixed> $_
137125
* @return mixed|null
138126
* @deprecated will be removed in version 3.0
139127
*/

src/Traits/HasImmutability.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ trait HasImmutability
2323
* Enforces the immutability by blocking any attempts of update any property.
2424
*
2525
* @param string $name
26-
* @param $_
26+
* @param mixed $_
2727
* @return void
2828
*/
2929
final public function __set(string $name, $_): void

src/Traits/HasInvariants.php

+40-31
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,12 @@ final public static function invariants(): array
2525
$invariants = [];
2626
foreach (get_class_methods(static::class) as $invariant) {
2727
if (str_starts_with($invariant, 'invariant') && !in_array($invariant, ['invariants', 'invariantHandler'])) {
28-
$invariants[$invariant] = str_replace(
29-
'invariant ',
30-
'',
31-
strtolower(
32-
preg_replace('/[A-Z]([A-Z](?![a-z]))*/', ' $0', $invariant)
33-
)
34-
);
28+
$invariantRuleName = preg_replace('/[A-Z]([A-Z](?![a-z]))*/', ' $0', $invariant);
29+
if (is_null($invariantRuleName)) {
30+
continue;
31+
}
32+
33+
$invariants[$invariant] = str_replace('invariant ', '', strtolower($invariantRuleName));
3534
}
3635
}
3736

@@ -55,48 +54,58 @@ final public static function invariants(): array
5554
* $onFail function must have the following signature:
5655
* fn(array<string, string>) => void
5756
*
58-
* @param callable|null $onFail
57+
* @param string|callable $onFail
5958
*
6059
* @return void
6160
*/
62-
private function check(callable $onFail = null): void
61+
private function check(string|callable $onFail = 'invariantHandler'): void
6362
{
64-
$handler = 'invariantHandler';
63+
$violations = $this->computeInvariantViolations();
64+
if (!empty($violations)) {
65+
call_user_func_array($this->computeInvariantHandler($onFail), [$violations]);
66+
}
67+
}
6568

69+
/**
70+
* Computes the list of invariant violations.
71+
*
72+
* @return array<string, string>
73+
*/
74+
private function computeInvariantViolations(): array
75+
{
6676
$violations = [];
6777
foreach (static::invariants() as $invariant => $rule) {
6878
try {
69-
if (!call_user_func_array([$this, $invariant], [])) {
79+
if (!$this->{$invariant}()) {
7080
$violations[$invariant] = $rule;
7181
}
7282
} catch (Exception $e) {
7383
$violations[$invariant] = $e->getMessage();
7484
}
7585
}
7686

77-
if (!empty($violations)) {
78-
if (is_null($onFail)) {
79-
$customizedHandler = function (array $violations) use ($handler): void {
80-
call_user_func_array([$this, $handler], [$violations]);
81-
};
82-
83-
$defaultHandler = function (array $violations): void {
84-
throw new InvariantViolation(
85-
sprintf(
86-
"Unable to create %s due %s",
87-
basename(str_replace('\\', '/', static::class)),
88-
implode(",", $violations),
87+
return $violations;
88+
}
8989

90-
)
91-
);
92-
};
90+
private function computeInvariantHandler(string|callable $handlerFn): callable
91+
{
92+
if (!is_string($handlerFn)) {
93+
return $handlerFn;
94+
}
9395

94-
$onFail = (method_exists($this, $handler))
95-
? $customizedHandler
96-
: $defaultHandler;
96+
return method_exists($this, $handlerFn)
97+
? function (array $violations) use ($handlerFn): void {
98+
$this->{$handlerFn}($violations);
9799
}
100+
: function (array $violations): void {
101+
throw new InvariantViolation(
102+
sprintf(
103+
"Unable to create %s due %s",
104+
basename(str_replace('\\', '/', static::class)),
105+
implode(",", $violations),
98106

99-
$onFail($violations);
100-
}
107+
)
108+
);
109+
};
101110
}
102111
}

src/TypedCollection.php

+9-6
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
*
1515
* @author Unay Santisteban <usantisteban@othercode.io>
1616
* @package ComplexHeart\Domain\Model\Domain
17+
*
18+
* @template TKey of array-key
19+
* @template-covariant TValue
20+
* @extends Collection<TKey, TValue>
1721
*/
1822
class TypedCollection extends Collection
1923
{
@@ -37,7 +41,7 @@ class TypedCollection extends Collection
3741
/**
3842
* TypedCollection constructor.
3943
*
40-
* @param array $items
44+
* @param array<TKey, TValue> $items
4145
*/
4246
public function __construct(array $items = [])
4347
{
@@ -138,7 +142,7 @@ public function push(...$values): static
138142
*
139143
* @throws InvariantViolation
140144
*/
141-
public function offsetSet(mixed $key, mixed $value): void
145+
public function offsetSet($key, $value): void
142146
{
143147
if ($this->keyType !== 'mixed') {
144148
$this->checkKeyType($key);
@@ -187,10 +191,9 @@ public function add(mixed $item): static
187191
/**
188192
* Get the values of a given key.
189193
*
190-
* @param string|array|int|null $value
194+
* @param string|int|array<array-key, string> $value
191195
* @param string|null $key
192-
*
193-
* @return Collection
196+
* @return Collection<TKey, TValue>
194197
*/
195198
public function pluck($value, $key = null): Collection
196199
{
@@ -200,7 +203,7 @@ public function pluck($value, $key = null): Collection
200203
/**
201204
* Get the keys of the collection items.
202205
*
203-
* @return Collection
206+
* @return Collection<TKey, TValue>
204207
*/
205208
public function keys(): Collection
206209
{

src/ValueObjects/ArrayValue.php

+12-5
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,17 @@
1818
*
1919
* @author Unay Santisteban <usantisteban@othercode.io>
2020
* @package ComplexHeart\Domain\Model\ValueObjects
21+
* @implements IteratorAggregate<int|string, mixed>
22+
* @implements ArrayAccess<int|string, mixed>
2123
*/
2224
abstract class ArrayValue extends Value implements IteratorAggregate, ArrayAccess, Serializable, Countable
2325
{
2426
/**
2527
* The value storage.
2628
*
27-
* @var array
29+
* @var array<int|string, mixed>
2830
*/
29-
protected array $value;
31+
protected array $value = [];
3032

3133
/**
3234
* Define the min amount of items for the array.
@@ -52,7 +54,7 @@ abstract class ArrayValue extends Value implements IteratorAggregate, ArrayAcces
5254
/**
5355
* ArrayValue constructor.
5456
*
55-
* @param array $value
57+
* @param array<int|string, mixed> $value
5658
*/
5759
public function __construct(array $value = [])
5860
{
@@ -125,7 +127,7 @@ protected function invariantMustHaveMaximumNumberOfElements(): bool
125127
/**
126128
* Retrieve an external iterator.
127129
*
128-
* @return Traversable
130+
* @return Traversable<int|string, mixed>
129131
*/
130132
public function getIterator(): Traversable
131133
{
@@ -202,6 +204,10 @@ public function unserialize(string $data): void
202204
$this->value = unserialize($data);
203205
}
204206

207+
/**
208+
* @param array<int|string, mixed> $data
209+
* @return void
210+
*/
205211
public function __unserialize(array $data): void
206212
{
207213
$this->initialize($data);
@@ -224,6 +230,7 @@ public function count(): int
224230
*/
225231
public function __toString(): string
226232
{
227-
return json_encode($this->value);
233+
$string = json_encode($this->value);
234+
return is_string($string) ? $string : "[]";
228235
}
229236
}

src/ValueObjects/EnumValue.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ abstract class EnumValue extends Value
2727
/**
2828
* Internal cache.
2929
*
30-
* @var array
30+
* @var array<string, array<string>>
3131
*/
3232
protected static array $cache = [];
3333

@@ -44,7 +44,7 @@ public function __construct(mixed $value)
4444
/**
4545
* Returns the cached constant data of the class.
4646
*
47-
* @return array
47+
* @return array<int|string, mixed>
4848
*/
4949
private static function cache(): array
5050
{
@@ -81,7 +81,7 @@ public static function isValid(mixed $value): bool
8181
/**
8282
* Return the available labels.
8383
*
84-
* @return string[]
84+
* @return array<int, int|string>
8585
*/
8686
public static function getLabels(): array
8787
{

0 commit comments

Comments
 (0)