Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
0d95983
breaking changes: added validation to UniqueId VO. Delete deprecated …
Jan 29, 2025
f9f67cd
feat: throw exception on invalid date time arguments
calmohallag Feb 11, 2025
1220b70
feat: ensure date value object has zero time
calmohallag Feb 11, 2025
fe6d709
Merge pull request #48 from PcComponentes/master
calmohallag Feb 11, 2025
c01092e
merge
calmohallag Feb 11, 2025
fc827a7
merge
calmohallag Feb 11, 2025
b12fe11
Merge pull request #51 from PcComponentes/master
calmohallag Feb 11, 2025
41f4aa5
chore: merge
calmohallag Feb 11, 2025
77f48f6
Merge pull request #49 from PcComponentes/feature/implements-time-fun…
calmohallag Feb 11, 2025
99a946a
Merge pull request #53 from PcComponentes/master
calmohallag Feb 17, 2025
ddb480d
Merge pull request #55 from PcComponentes/master
calmohallag Mar 7, 2025
477edea
Merge pull request #57 from PcComponentes/master
calmohallag May 7, 2025
daaa07c
feat: add find function to CollectionValueObject
calmohallag Jul 29, 2025
06c6fd3
Merge pull request #58 from PcComponentes/feature/find-function-for-c…
calmohallag Jul 29, 2025
d67060b
refactor: array find function name
calmohallag Aug 11, 2025
d0b2e24
Merge pull request #59 from PcComponentes/feature/find-function-for-c…
calmohallag Aug 11, 2025
148f647
Improve CollectionValueObject iteration typing
calmohallag Mar 13, 2026
7c34fc4
Merge pull request #61 from PcComponentes/feature/typed-collection-va…
calmohallag Mar 16, 2026
f35a539
Merge pull request #63 from PcComponentes/master
calmohallag Mar 26, 2026
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