Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/Add PHPStan Extensions. #13

Merged
merged 1 commit into from
Jan 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
"pestphp/pest": "^2.0",
"pestphp/pest-plugin-mock": "^2.0",
"pestphp/pest-plugin-faker": "^2.0",
"phpstan/phpstan": "^1.0"
"phpstan/phpstan": "^1.0",
"phpstan/extension-installer": "^1.3",
"phpstan/phpstan-mockery": "^1.1"
},
"autoload": {
"psr-4": {
Expand All @@ -39,15 +41,16 @@
"scripts": {
"test": "vendor/bin/pest --configuration=phpunit.xml --coverage-clover=coverage.xml --log-junit=test.xml",
"test-cov": "vendor/bin/pest --configuration=phpunit.xml --coverage-html=coverage",
"analyse": "vendor/bin/phpstan analyse src --no-progress --level=5",
"analyse": "vendor/bin/phpstan analyse src --no-progress --level=8",
"check": [
"@analyse",
"@test"
]
},
"config": {
"allow-plugins": {
"pestphp/pest-plugin": true
"pestphp/pest-plugin": true,
"phpstan/extension-installer": true
}
}
}
29 changes: 20 additions & 9 deletions src/IsModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,26 +26,37 @@ trait IsModel
* Initialize the Model. Just as the constructor will do.
*
* @param array<int|string, mixed> $source
* @param callable|null $onFail
* @param string|callable $onFail
*
* @return static
*/
protected function initialize(array $source, callable $onFail = null): static
protected function initialize(array $source, string|callable $onFail = 'invariantHandler'): static
{
$this->hydrate($this->prepareAttributes($source));
$this->check($onFail);

return $this;
}

/**
* Transform an indexed array into assoc array by combining the
* given values with the list of attributes of the object.
*
* @param array<int|string, mixed> $source
*
* @return array<string, mixed>
*/
private function prepareAttributes(array $source): array
{
// check if the array is indexed or associative.
$isIndexed = fn($source): bool => ([] !== $source) && array_keys($source) === range(0, count($source) - 1);

$source = $isIndexed($source)
/** @var array<string, mixed> $source */
return $isIndexed($source)
// combine the attributes keys with the provided source values.
? array_combine(array_slice(static::attributes(), 0, count($source)), $source)
// return the already mapped array source.
: $source;


$this->hydrate($source);
$this->check($onFail);

return $this;
}

/**
Expand Down
24 changes: 6 additions & 18 deletions src/Traits/HasAttributes.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ final public function values(): array
/**
* Populate the object recursively.
*
* @param iterable $source
* @param iterable<string, mixed> $source
*/
final protected function hydrate(iterable $source): void
{
Expand All @@ -68,8 +68,8 @@ final protected function get(string $attribute): mixed
if (in_array($attribute, static::attributes())) {
$method = $this->getStringKey($attribute, 'get', 'Value');

return ($this->canCall($method))
? call_user_func_array([$this, $method], [$this->{$attribute}])
return method_exists($this, $method)
? $this->{$method}($this->{$attribute})
: $this->{$attribute};
}

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

$this->{$attribute} = ($this->canCall($method))
? call_user_func_array([$this, $method], [$value])
$this->{$attribute} = method_exists($this, $method)
? $this->{$method}($value)
: $value;
}
}
Expand Down Expand Up @@ -116,24 +116,12 @@ protected function getStringKey(string $id, string $prefix = '', string $suffix
);
}

/**
* Check if the required method name is callable.
*
* @param string $method
*
* @return bool
*/
protected function canCall(string $method): bool
{
return method_exists($this, $method);
}

/**
* Dynamic method to access each attribute as method, i.e:
* $user->name() will access the private attribute name.
*
* @param string $attribute
* @param array $_
* @param array<int, mixed> $_
* @return mixed|null
* @deprecated will be removed in version 3.0
*/
Expand Down
2 changes: 1 addition & 1 deletion src/Traits/HasImmutability.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ trait HasImmutability
* Enforces the immutability by blocking any attempts of update any property.
*
* @param string $name
* @param $_
* @param mixed $_
* @return void
*/
final public function __set(string $name, $_): void
Expand Down
71 changes: 40 additions & 31 deletions src/Traits/HasInvariants.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,12 @@ final public static function invariants(): array
$invariants = [];
foreach (get_class_methods(static::class) as $invariant) {
if (str_starts_with($invariant, 'invariant') && !in_array($invariant, ['invariants', 'invariantHandler'])) {
$invariants[$invariant] = str_replace(
'invariant ',
'',
strtolower(
preg_replace('/[A-Z]([A-Z](?![a-z]))*/', ' $0', $invariant)
)
);
$invariantRuleName = preg_replace('/[A-Z]([A-Z](?![a-z]))*/', ' $0', $invariant);
if (is_null($invariantRuleName)) {
continue;
}

$invariants[$invariant] = str_replace('invariant ', '', strtolower($invariantRuleName));
}
}

Expand All @@ -55,48 +54,58 @@ final public static function invariants(): array
* $onFail function must have the following signature:
* fn(array<string, string>) => void
*
* @param callable|null $onFail
* @param string|callable $onFail
*
* @return void
*/
private function check(callable $onFail = null): void
private function check(string|callable $onFail = 'invariantHandler'): void
{
$handler = 'invariantHandler';
$violations = $this->computeInvariantViolations();
if (!empty($violations)) {
call_user_func_array($this->computeInvariantHandler($onFail), [$violations]);
}
}

/**
* Computes the list of invariant violations.
*
* @return array<string, string>
*/
private function computeInvariantViolations(): array
{
$violations = [];
foreach (static::invariants() as $invariant => $rule) {
try {
if (!call_user_func_array([$this, $invariant], [])) {
if (!$this->{$invariant}()) {
$violations[$invariant] = $rule;
}
} catch (Exception $e) {
$violations[$invariant] = $e->getMessage();
}
}

if (!empty($violations)) {
if (is_null($onFail)) {
$customizedHandler = function (array $violations) use ($handler): void {
call_user_func_array([$this, $handler], [$violations]);
};

$defaultHandler = function (array $violations): void {
throw new InvariantViolation(
sprintf(
"Unable to create %s due %s",
basename(str_replace('\\', '/', static::class)),
implode(",", $violations),
return $violations;
}

)
);
};
private function computeInvariantHandler(string|callable $handlerFn): callable
{
if (!is_string($handlerFn)) {
return $handlerFn;
}

$onFail = (method_exists($this, $handler))
? $customizedHandler
: $defaultHandler;
return method_exists($this, $handlerFn)
? function (array $violations) use ($handlerFn): void {
$this->{$handlerFn}($violations);
}
: function (array $violations): void {
throw new InvariantViolation(
sprintf(
"Unable to create %s due %s",
basename(str_replace('\\', '/', static::class)),
implode(",", $violations),

$onFail($violations);
}
)
);
};
}
}
15 changes: 9 additions & 6 deletions src/TypedCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
*
* @author Unay Santisteban <[email protected]>
* @package ComplexHeart\Domain\Model\Domain
*
* @template TKey of array-key
* @template-covariant TValue
* @extends Collection<TKey, TValue>
*/
class TypedCollection extends Collection
{
Expand All @@ -37,7 +41,7 @@ class TypedCollection extends Collection
/**
* TypedCollection constructor.
*
* @param array $items
* @param array<TKey, TValue> $items
*/
public function __construct(array $items = [])
{
Expand Down Expand Up @@ -138,7 +142,7 @@ public function push(...$values): static
*
* @throws InvariantViolation
*/
public function offsetSet(mixed $key, mixed $value): void
public function offsetSet($key, $value): void
{
if ($this->keyType !== 'mixed') {
$this->checkKeyType($key);
Expand Down Expand Up @@ -187,10 +191,9 @@ public function add(mixed $item): static
/**
* Get the values of a given key.
*
* @param string|array|int|null $value
* @param string|int|array<array-key, string> $value
* @param string|null $key
*
* @return Collection
* @return Collection<TKey, TValue>
*/
public function pluck($value, $key = null): Collection
{
Expand All @@ -200,7 +203,7 @@ public function pluck($value, $key = null): Collection
/**
* Get the keys of the collection items.
*
* @return Collection
* @return Collection<TKey, TValue>
*/
public function keys(): Collection
{
Expand Down
17 changes: 12 additions & 5 deletions src/ValueObjects/ArrayValue.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,17 @@
*
* @author Unay Santisteban <[email protected]>
* @package ComplexHeart\Domain\Model\ValueObjects
* @implements IteratorAggregate<int|string, mixed>
* @implements ArrayAccess<int|string, mixed>
*/
abstract class ArrayValue extends Value implements IteratorAggregate, ArrayAccess, Serializable, Countable
{
/**
* The value storage.
*
* @var array
* @var array<int|string, mixed>
*/
protected array $value;
protected array $value = [];

/**
* Define the min amount of items for the array.
Expand All @@ -52,7 +54,7 @@ abstract class ArrayValue extends Value implements IteratorAggregate, ArrayAcces
/**
* ArrayValue constructor.
*
* @param array $value
* @param array<int|string, mixed> $value
*/
public function __construct(array $value = [])
{
Expand Down Expand Up @@ -125,7 +127,7 @@ protected function invariantMustHaveMaximumNumberOfElements(): bool
/**
* Retrieve an external iterator.
*
* @return Traversable
* @return Traversable<int|string, mixed>
*/
public function getIterator(): Traversable
{
Expand Down Expand Up @@ -202,6 +204,10 @@ public function unserialize(string $data): void
$this->value = unserialize($data);
}

/**
* @param array<int|string, mixed> $data
* @return void
*/
public function __unserialize(array $data): void
{
$this->initialize($data);
Expand All @@ -224,6 +230,7 @@ public function count(): int
*/
public function __toString(): string
{
return json_encode($this->value);
$string = json_encode($this->value);
return is_string($string) ? $string : "[]";
}
}
6 changes: 3 additions & 3 deletions src/ValueObjects/EnumValue.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ abstract class EnumValue extends Value
/**
* Internal cache.
*
* @var array
* @var array<string, array<string>>
*/
protected static array $cache = [];

Expand All @@ -44,7 +44,7 @@ public function __construct(mixed $value)
/**
* Returns the cached constant data of the class.
*
* @return array
* @return array<int|string, mixed>
*/
private static function cache(): array
{
Expand Down Expand Up @@ -81,7 +81,7 @@ public static function isValid(mixed $value): bool
/**
* Return the available labels.
*
* @return string[]
* @return array<int, int|string>
*/
public static function getLabels(): array
{
Expand Down
Loading