Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM php:8.0-cli-alpine3.13
FROM php:8.4-cli-alpine3.22

RUN apk update && \
apk add --no-cache \
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
}
},
"require": {
"php": "^8.0",
"php": "^8.4",
"ext-json": "*",
"ramsey/uuid": "^4.2"
},
Expand Down
54 changes: 51 additions & 3 deletions src/Domain/Model/ValueObject/CollectionValueObject.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,22 @@

namespace PcComponentes\Ddd\Domain\Model\ValueObject;

class CollectionValueObject implements \Iterator, \Countable, ValueObject
/**
* Generic collection value object.
*
* @template TKey of array-key
* @template TValue
* @implements \IteratorAggregate<TKey, TValue>
*/
class CollectionValueObject implements \IteratorAggregate, \Countable, ValueObject
{
/**
* @var array<TKey, TValue>
* Typed collection items.
*/
private array $items;

/** @param array<TKey, TValue> $items */
final private function __construct(array $items)
{
$this->items = $items;
Expand All @@ -17,16 +29,28 @@ public static function from(array $items): static
return new static($items);
}

public function current(): mixed
/** @return \Traversable<TKey, TValue> */
public function getIterator(): \Traversable
{
return \current($this->items);
return new \ArrayIterator($this->items);
}

/** @return TValue|null */
public function current()
{
$key = $this->key();

return null === $key
? null
: $this->items[$key];
}

public function next(): void
{
\next($this->items);
}

/** @return TKey|null */
public function key(): string|int|null
{
return \key($this->items);
Expand All @@ -47,26 +71,45 @@ public function count(): int
return \count($this->items);
}

/** @param callable(TValue, TKey): void $func */
public function walk(callable $func): void
{
\array_walk($this->items, $func);
}

/**
* @param callable(TValue): bool $func
* @return TValue|null
*/
public function findOne(callable $func)
{
return \array_find($this->items, $func);
}

/** @param callable(TValue): bool $func */
public function filter(callable $func): static
{
return static::from(\array_values(\array_filter($this->items, $func)));
}

/** @param callable(TValue): TValue $func */
public function map(callable $func): static
{
return static::from(\array_map($func, $this->items));
}

/**
* @template TCarry
* @param callable(TCarry, TValue): TCarry $func
* @param TCarry $initial
* @return TCarry
*/
public function reduce(callable $func, $initial)
{
return \array_reduce($this->items, $func, $initial);
}

/** @param callable(TValue, TValue): int $func */
public function sort(callable $func): static
{
$items = $this->items;
Expand Down Expand Up @@ -99,21 +142,25 @@ public function equivalentTo(self $other): bool
return $a->equalTo($b);
}

/** @return array<TKey, TValue> */
final public function jsonSerialize(): array
{
return $this->items;
}

/** @return TValue|null */
public function first()
{
return $this->items[\array_key_first($this->items)] ?? null;
}

/** @return array<TKey, TValue> */
public function value(): array
{
return $this->items;
}

/** @param TValue $item */
protected function addItem($item): static
{
$items = $this->items;
Expand All @@ -122,6 +169,7 @@ protected function addItem($item): static
return static::from($items);
}

/** @param TValue $item */
protected function removeItem($item): static
{
return $this->filter(
Expand Down
9 changes: 0 additions & 9 deletions src/Domain/Model/ValueObject/DateTimeRangeValeObject.php

This file was deleted.

12 changes: 4 additions & 8 deletions src/Domain/Model/ValueObject/DateTimeValueObject.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,30 +40,26 @@ final public static function createFromFormat(
string $format,
string $datetime,
?\DateTimeZone $timezone = null
): static|false {
): static {
$datetime = parent::createFromFormat($format, $datetime, $timezone);

if (false === $datetime) {
return false;
throw new \InvalidArgumentException('Invalid date format');
}

$timeZone = new \DateTimeZone(self::TIME_ZONE);

return static::createFromInterface($datetime->setTimezone($timeZone));
}

final public static function fromFormat(string $format, string $str): static|false
final public static function fromFormat(string $format, string $str): static
{
return static::createFromFormat($format, $str, new \DateTimeZone(self::TIME_ZONE));
}

final public static function createFromTimestamp(float|int $timestamp): static
{
$dateTime = self::fromFormat('U.u', \number_format((float) $timestamp, 6, '.', ''));

\assert(false !== $dateTime, 'Unexpected error on create date time from timestamp');

return $dateTime;
return self::fromFormat('U.u', \number_format((float) $timestamp, 6, '.', ''));
}

final public static function fromTimestamp(int|float $timestamp): static
Expand Down
14 changes: 5 additions & 9 deletions src/Domain/Model/ValueObject/DateValueObject.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public static function from(string $str): static
{
$timeZone = new \DateTimeZone(self::TIME_ZONE);

return (new static($str, $timeZone))->setTimezone($timeZone);
return (new static($str, $timeZone))->setTimezone($timeZone)->setTime(0, 0, 0, 0);
}

final public static function now(): static
Expand All @@ -40,30 +40,26 @@ final public static function createFromFormat(
string $format,
string $datetime,
?\DateTimeZone $timezone = null
): static|false {
): static {
$datetime = parent::createFromFormat($format, $datetime, $timezone);

if (false === $datetime) {
return false;
throw new \InvalidArgumentException('Invalid date format');
}

$timeZone = new \DateTimeZone(self::TIME_ZONE);

return static::createFromInterface($datetime->setTimezone($timeZone));
}

final public static function fromFormat(string $format, string $str): static|false
final public static function fromFormat(string $format, string $str): static
{
return static::createFromFormat($format, $str, new \DateTimeZone(self::TIME_ZONE));
}

final public static function createFromTimestamp(float|int $timestamp): static
{
$dateTime = self::fromFormat('U.u', \number_format((float) $timestamp, 6, '.', ''));

\assert(false !== $dateTime, 'Unexpected error on create date time from timestamp');

return $dateTime;
return self::fromFormat('U.u', \number_format((float) $timestamp, 6, '.', ''));
}

final public static function fromTimestamp(int|float $timestamp): static
Expand Down
22 changes: 21 additions & 1 deletion src/Domain/Model/ValueObject/UniqueId.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,30 @@

class UniqueId extends StringValueObject
{
private const VALID_PATTERN = '/^[A-Z0-9]{10}$/';

public static function from(string $value): static
{
if (false === self::isValid($value)) {
throw new \InvalidArgumentException('Invalid UniqueId.');
}

return parent::from($value);
}

public static function create(): static
{
$value = \base_convert(\uniqid(), 16, 36);
$value = \str_pad($value, 10, '0', \STR_PAD_LEFT);
$value = \substr($value, -10);

return self::from(
\strtoupper(\base_convert(\uniqid(), 16, 36)),
\strtoupper($value),
);
}

public static function isValid(string $value): bool
{
return 1 === \preg_match(self::VALID_PATTERN, $value);
}
}
50 changes: 42 additions & 8 deletions tests/Domain/Model/ValueObject/CollectionValueObjectTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
namespace PcComponentes\Ddd\Tests\Domain\Model\ValueObject;

use PcComponentes\Ddd\Domain\Model\ValueObject\CollectionValueObject;
use PcComponentes\Ddd\Domain\Model\ValueObject\FloatValueObject;
use PHPUnit\Framework\TestCase;

class CollectionValueObjectTest extends TestCase
Expand Down Expand Up @@ -95,6 +96,19 @@ static function ($current) use (&$iterated) {
$this->assertEquals([1, 2, 3, 4], $iterated);
}

/** @test */
public function given_collection_when_iterate_with_foreach_then_keep_items_and_keys()
{
$collection = CollectionValueObject::from(['a' => 1, 'b' => 2]);
$iterated = [];

foreach ($collection as $key => $item) {
$iterated[$key] = $item;
}

$this->assertEquals(['a' => 1, 'b' => 2], $iterated);
}

/** @test */
public function given_two_identical_collections_when_ask_to_check_equality_then_return_true()
{
Expand Down Expand Up @@ -129,12 +143,12 @@ public function equivalentCollectionValues(): array
[['1', '1', '1', '4'], ['4', '1', '1', '1']],
[
[
FloatValueObjectTested::from(1.1),
FloatValueObjectTested::from(6.0),
FloatValueObject::from(1.1),
FloatValueObject::from(6.0),
],
[
FloatValueObjectTested::from(1.1),
FloatValueObjectTested::from(6.0),
FloatValueObject::from(1.1),
FloatValueObject::from(6.0),
],
],
[[$objet1, $objet2], [$objet1, $objet2]],
Expand Down Expand Up @@ -170,12 +184,12 @@ public function differentCollectionValues(): array
[['1', '1', '4', '4'], ['4', '1', '1', '1']],
[
[
FloatValueObjectTested::from(1.1),
FloatValueObjectTested::from(6.0),
FloatValueObject::from(1.1),
FloatValueObject::from(6.0),
],
[
FloatValueObjectTested::from(1.3),
FloatValueObjectTested::from(6.0),
FloatValueObject::from(1.3),
FloatValueObject::from(6.0),
],
],
];
Expand Down Expand Up @@ -215,6 +229,26 @@ public function given_collection_when_ask_to_remove_item_then_return_new_collect
$this->assertEquals([1, 2, 4], $newCollection->jsonSerialize());
}

/** @test */
public function given_an_empty_collection_when_ask_to_obtain_current_item_then_return_null()
{
$collection = CollectionValueObject::from([]);

$this->assertNull($collection->current());
}

/** @test */
public function given_a_collection_when_ask_to_obtain_current_item_then_return_expected_item()
{
$collection = CollectionValueObject::from([1, 2, 3, 4]);

$this->assertSame(1, $collection->current());

$collection->next();

$this->assertSame(2, $collection->current());
}

/** @test */
public function given_an_empty_collection_when_ask_to_obtain_first_item_then_return_null()
{
Expand Down
Loading