Skip to content
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
49 changes: 49 additions & 0 deletions src/Traits/HasTypeCheck.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

namespace ComplexHeart\Domain\Model\Traits;

/**
* Trait HasTypeValidation
*
* @author Unay Santisteban <usantisteban@othercode.es>
* @package ComplexHeart\Domain\Model\Traits
*/
trait HasTypeCheck
{
/**
* Assert that the given value type match the required validType.
*
* @param mixed $value
* @param string $validType
*
* @return bool
*/
protected function isValueTypeValid($value, string $validType): bool
{
if ($validType === 'mixed') {
return true;
}

$primitives = ['integer', 'boolean', 'float', 'string', 'array', 'object', 'callable'];
$validation = in_array($validType, $primitives)
? fn($value): bool => gettype($value) === $validType
: fn($value): bool => $value instanceof $validType;

return $validation($value);
}

/**
* Assert that the given value type NOT match the required validType.
*
* @param mixed $value
* @param string $validType
*
* @return bool
*/
protected function isValueTypeNotValid($value, string $validType): bool
{
return !$this->isValueTypeValid($value, $validType);
}
}
170 changes: 139 additions & 31 deletions src/TypedCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@

namespace ComplexHeart\Domain\Model;

use Illuminate\Support\Collection;
use ComplexHeart\Domain\Model\Exceptions\InvariantViolation;
use ComplexHeart\Domain\Model\Traits\HasTypeCheck;
use ComplexHeart\Domain\Model\Traits\HasInvariants;
use Illuminate\Support\Collection;

/**
* Class TypedCollection
Expand All @@ -17,6 +18,7 @@
class TypedCollection extends Collection
{
use HasInvariants;
use HasTypeCheck;

/**
* The type of each key in the collection.
Expand Down Expand Up @@ -44,58 +46,164 @@ public function __construct(array $items = [])
}

/**
* Invariant: All items must be of the same type.
* Assert that the key type is compliant with the collection definition.
*
* - If $typeOf is primitive check the type with gettype().
* - If $typeOf is a class, check if the item is an instance of it.
* @param mixed $key
*
* @return bool
* @throws InvariantViolation
*/
protected function invariantItemsMustMatchTheRequiredType(): bool
protected function checkKeyType($key): void
{
if ($this->valueType !== 'mixed') {
$primitives = ['integer', 'boolean', 'float', 'string', 'array', 'object', 'callable'];
$check = in_array($this->valueType, $primitives)
? fn($value): bool => gettype($value) !== $this->valueType
: fn($value): bool => !($value instanceof $this->valueType);

foreach ($this->items as $item) {
if ($check($item)) {
throw new InvariantViolation("All items must be type of {$this->valueType}");
}
}
$supported = ['string', 'integer'];
if (!in_array($this->keyType, $supported)) {
throw new InvariantViolation(
"Unsupported key type $this->keyType, must be one of ".implode(', ', $supported)
);
}

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

/**
* Invariant: Check the collection keys to match the required type.
* Assert that the item type is compliant with the collection definition.
*
* @param mixed $item
*
* Supported types:
* @throws InvariantViolation
*/
protected function checkValueType($item): void
{
if ($this->isValueTypeNotValid($item, $this->valueType)) {
throw new InvariantViolation("All items in the collection must be type of $this->valueType");
}
}

/**
* Check the keys and values of the collection to match the required type.
*
* Supported types for keys:
* - string
* - integer
*
* Values can have any type:
* - If $type is primitive check the type with gettype().
* - If $type is a class, check if the item is an instance of it.
*
* @return bool
* @throws InvariantViolation
*/
protected function invariantKeysMustMatchTheRequiredType(): bool
protected function invariantKeysAndValuesMustMatchTheRequiredType(): bool
{
if ($this->keyType !== 'mixed') {
$supported = ['string', 'integer'];
if (!in_array($this->keyType, $supported)) {
throw new InvariantViolation(
"Unsupported key type, must be one of ".implode(', ', $supported)
);
if ($this->keyType === 'mixed' && $this->valueType === 'mixed') {
return true;
}

foreach ($this->items as $key => $item) {
if ($this->keyType !== 'mixed') {
$this->checkKeyType($key);
}

foreach ($this->items as $index => $item) {
if (gettype($index) !== $this->keyType) {
throw new InvariantViolation("All keys must be type of {$this->keyType}");
}
if ($this->valueType !== 'mixed') {
$this->checkValueType($item);
}
}

return true;
}

/**
* Push one or more items onto the end of the collection.
*
* @param mixed $values [optional]
*
* @return static
* @throws InvariantViolation
*/
public function push(...$values)
{
foreach ($values as $value) {
$this->checkValueType($value);
}

return parent::push(...$values);
}

/**
* Offset to set.
*
* @param mixed $key
* @param mixed $value
*
* @throws InvariantViolation
*/
public function offsetSet($key, $value)
{
if ($this->keyType !== 'mixed') {
$this->checkKeyType($key);
}

$this->checkValueType($value);

parent::offsetSet($key, $value);
}

/**
* Push an item onto the beginning of the collection.
*
* @param mixed $value
* @param null $key
*
* @return static
* @throws InvariantViolation
*/
public function prepend($value, $key = null)
{
if ($this->keyType !== 'mixed') {
$this->checkKeyType($key);
}

$this->checkValueType($value);

return parent::prepend($value, $key);
}

/**
* Add an item to the collection.
*
* @param mixed $item
*
* @return static
* @throws InvariantViolation
*/
public function add($item)
{
$this->checkValueType($item);

return parent::add($item);
}

/**
* Get the values of a given key.
*
* @param string|array|int|null $value
* @param string|null $key
*
* @return Collection
*/
public function pluck($value, $key = null)
{
return $this->toBase()->pluck($value, $key);
}

/**
* Get the keys of the collection items.
*
* @return Collection
*/
public function keys(): Collection
{
return $this->toBase()->keys();
}
}
Loading