Skip to content

Commit 663a8f8

Browse files
authored
Merge pull request #8 from geekcell/feat/support
Additional functionality for supporting classes.
2 parents 22de83f + 87deb1d commit 663a8f8

File tree

13 files changed

+688
-17
lines changed

13 files changed

+688
-17
lines changed

src/Domain/ChainRepository.php

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace GeekCell\Ddd\Domain;
6+
7+
use Assert;
8+
use GeekCell\Ddd\Contracts\Domain\Paginator;
9+
use GeekCell\Ddd\Contracts\Domain\Repository as RepositoryInterface;
10+
use Traversable;
11+
12+
abstract class ChainRepository implements RepositoryInterface
13+
{
14+
/**
15+
* @var array<class-string<RepositoryInterface>, RepositoryInterface>
16+
*/
17+
private array $repositories;
18+
19+
/**
20+
* @var class-string<RepositoryInterface>
21+
*/
22+
private string $primaryRepositoryKey;
23+
24+
/**
25+
* ChainRepository constructor.
26+
*/
27+
public function __construct(RepositoryInterface ...$repositories)
28+
{
29+
foreach ($repositories as $repository) {
30+
$this->repositories[get_class($repository)] = $repository;
31+
}
32+
33+
// Select the first repository as primary by default
34+
$firstRepository = reset($repositories);
35+
$this->selectPrimary($firstRepository);
36+
}
37+
38+
/**
39+
* Selects the primary repository, which will be used for collection,
40+
* pagination and count.
41+
*
42+
* @param RepositoryInterface $repository
43+
*
44+
* @throws Assert\AssertionFailedException
45+
*/
46+
protected function selectPrimary(RepositoryInterface $repository): void
47+
{
48+
$className = get_class($repository);
49+
Assert\Assertion::keyExists($this->repositories, $className);
50+
51+
$this->primaryRepositoryKey = $className;
52+
}
53+
54+
/**
55+
* Returns the currently selected primary repository.
56+
*
57+
* @return RepositoryInterface
58+
*/
59+
protected function getPrimary(): RepositoryInterface
60+
{
61+
return $this->repositories[$this->primaryRepositoryKey];
62+
}
63+
64+
/**
65+
* Delegate operation to the currently selected primary repository.
66+
* For more advanced scenarios, this method can be overridden.
67+
*
68+
* @inheritDoc
69+
*/
70+
public function collect(): Collection
71+
{
72+
return $this->getPrimary()->collect();
73+
}
74+
75+
/**
76+
* Delegate operation to the currently selected primary repository.
77+
* For more advanced scenarios, this method can be overridden.
78+
*
79+
* @inheritDoc
80+
*/
81+
public function paginate(int $itemsPerPage, int $currentPage = 1): Paginator
82+
{
83+
return $this->getPrimary()->paginate($itemsPerPage, $currentPage);
84+
}
85+
86+
/**
87+
* Delegate operation to the currently selected primary repository.
88+
* For more advanced scenarios, this method can be overridden.
89+
*
90+
* @inheritDoc
91+
*/
92+
public function count(): int
93+
{
94+
return $this->getPrimary()->count();
95+
}
96+
97+
/**
98+
* Delegate operation to the currently selected primary repository.
99+
* For more advanced scenarios, this method can be overridden.
100+
*
101+
* @inheritDoc
102+
*/
103+
public function getIterator(): Traversable
104+
{
105+
return $this->getPrimary()->getIterator();
106+
}
107+
}

src/Domain/Collection.php

+130-4
Original file line numberDiff line numberDiff line change
@@ -4,36 +4,154 @@
44

55
namespace GeekCell\Ddd\Domain;
66

7+
use ArrayAccess;
78
use ArrayIterator;
89
use Assert;
910
use Countable;
1011
use IteratorAggregate;
1112
use Traversable;
1213

13-
class Collection implements Countable, IteratorAggregate
14+
class Collection implements ArrayAccess, Countable, IteratorAggregate
1415
{
1516
/**
1617
* @template T of object
1718
* @extends IteratorAggregate<T>
1819
*
1920
* @param T[] $items
2021
* @param class-string<T> $itemType
22+
*
23+
* @throws Assert\AssertionFailedException
2124
*/
2225
final public function __construct(
2326
private readonly array $items = [],
24-
?string $itemType = null,
27+
private ?string $itemType = null,
2528
) {
2629
if ($itemType !== null) {
2730
Assert\Assertion::allIsInstanceOf($items, $itemType);
2831
}
2932
}
3033

34+
/**
35+
* Add one or more items to the collection. It **does not** modify the
36+
* current collection, but returns a new one.
37+
*
38+
* @param mixed $item One or more items to add to the collection.
39+
* @return static
40+
*
41+
* @throws Assert\AssertionFailedException
42+
*/
43+
public function add(mixed $item): static
44+
{
45+
if (!is_array($item)) {
46+
$item = [$item];
47+
}
48+
49+
if ($this->itemType !== null) {
50+
Assert\Assertion::allIsInstanceOf($item, $this->itemType);
51+
}
52+
53+
return new static([...$this->items, ...$item], $this->itemType);
54+
}
55+
56+
/**
57+
* Filter the collection using the given callback. It **does not** modify
58+
* the current collection, but returns a new one.
59+
*
60+
* @param callable $callback The callback to use for filtering.
61+
* @return static
62+
*/
63+
public function filter(callable $callback): static
64+
{
65+
return new static(
66+
array_filter($this->items, $callback),
67+
$this->itemType,
68+
);
69+
}
70+
71+
/**
72+
* Map the collection using the given callback. It **does not** modify
73+
* the current collection, but returns a new one.
74+
*
75+
* @param callable $callback The callback to use for mapping.
76+
* @param bool $inferTypes Whether to infer the type of the items in the
77+
* collection based on the first item in the
78+
* mapping result. Defaults to `true`.
79+
*
80+
* @return static
81+
*/
82+
public function map(callable $callback, bool $inferTypes = true): static
83+
{
84+
$mapResult = array_map($callback, $this->items);
85+
$firstItem = reset($mapResult);
86+
87+
if ($firstItem === false || !is_object($firstItem)) {
88+
return new static($mapResult);
89+
}
90+
91+
if ($inferTypes && $this->itemType !== null) {
92+
return new static($mapResult, get_class($firstItem));
93+
}
94+
95+
return new static($mapResult);
96+
}
97+
98+
/**
99+
* Reduce the collection using the given callback.
100+
*
101+
* @param callable $callback The callback to use for reducing.
102+
* @param mixed $initial The initial value to use for reducing.
103+
*
104+
* @return mixed
105+
*/
106+
public function reduce(callable $callback, mixed $initial = null): mixed
107+
{
108+
return array_reduce($this->items, $callback, $initial);
109+
}
110+
31111
/**
32112
* @inheritDoc
33113
*/
34-
public function getIterator(): Traversable
114+
public function offsetExists(mixed $offset): bool
35115
{
36-
return new ArrayIterator($this->items);
116+
if (!is_int($offset)) {
117+
return false;
118+
}
119+
120+
return isset($this->items[$offset]);
121+
}
122+
123+
/**
124+
* @inheritDoc
125+
*/
126+
public function offsetGet(mixed $offset): mixed
127+
{
128+
if (!$this->offsetExists($offset)) {
129+
return null;
130+
}
131+
132+
return $this->items[$offset];
133+
}
134+
135+
/**
136+
* This method is not supported since it would break the immutability of the
137+
* collection.
138+
*
139+
* @inheritDoc
140+
*/
141+
public function offsetSet(mixed $offset, mixed $value): void
142+
{
143+
// Unsupported since it would break the immutability of the collection.
144+
}
145+
146+
/**
147+
* This method is not supported since it would break the immutability of the
148+
* collection.
149+
*
150+
* @inheritDoc
151+
*/
152+
public function offsetUnset(mixed $offset): void
153+
{
154+
// Unsupported since it would break the immutability of the collection.
37155
}
38156

39157
/**
@@ -43,4 +161,12 @@ public function count(): int
43161
{
44162
return count($this->items);
45163
}
164+
165+
/**
166+
* @inheritDoc
167+
*/
168+
public function getIterator(): Traversable
169+
{
170+
return new ArrayIterator($this->items);
171+
}
46172
}

src/Infrastructure/InMemory/CommandBus.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
use GeekCell\Ddd\Contracts\Application\Command;
88
use GeekCell\Ddd\Contracts\Application\CommandBus as CommandBusInterface;
99
use GeekCell\Ddd\Contracts\Application\CommandHandler;
10-
use GeekCell\Ddd\Support\Attributes\For\Command as ForCommand;
10+
use GeekCell\Ddd\Support\Attributes\ForType\Command as ForCommand;
1111

1212
class CommandBus extends AbstractBus implements CommandBusInterface
1313
{

src/Infrastructure/InMemory/Paginator.php

+53-1
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44

55
namespace GeekCell\Ddd\Infrastructure\InMemory;
66

7+
use ArrayAccess;
78
use EmptyIterator;
89
use GeekCell\Ddd\Contracts\Domain\Paginator as PaginatorInterface;
910
use GeekCell\Ddd\Domain\Collection;
1011
use LimitIterator;
1112
use Traversable;
1213

13-
class Paginator implements PaginatorInterface
14+
class Paginator implements PaginatorInterface, ArrayAccess
1415
{
1516
public function __construct(
1617
private readonly Collection $collection,
@@ -52,6 +53,57 @@ public function getTotalItems(): int
5253
return count($this->collection);
5354
}
5455

56+
/**
57+
* @inheritDoc
58+
*/
59+
public function offsetExists(mixed $offset): bool
60+
{
61+
if (!is_int($offset)) {
62+
return false;
63+
}
64+
65+
return $offset >= 0 && $offset < $this->count();
66+
}
67+
68+
/**
69+
* @inheritDoc
70+
*/
71+
public function offsetGet(mixed $offset): mixed
72+
{
73+
if (!$this->offsetExists($offset)) {
74+
return null;
75+
}
76+
77+
$realOffset = $offset + $this->getCurrentPage();
78+
foreach ($this->getIterator() as $index => $item) {
79+
if ($index === $realOffset) {
80+
return $item;
81+
}
82+
}
83+
84+
return null;
85+
}
86+
87+
/**
88+
* This method is not supported since it is not appropriate for a paginator.
89+
*
90+
* @inheritDoc
91+
*/
92+
public function offsetSet(mixed $offset, mixed $value): void
93+
{
94+
// Unsupported since it is not appropriate for a paginator.
95+
}
96+
97+
/**
98+
* This method is not supported since it is not appropriate for a paginator.
99+
*
100+
* @inheritDoc
101+
*/
102+
public function offsetUnset(mixed $offset): void
103+
{
104+
// Unsupported since it is not appropriate for a paginator.
105+
}
106+
55107
/**
56108
* @inheritDoc
57109
*/

src/Infrastructure/InMemory/QueryBus.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
use GeekCell\Ddd\Contracts\Application\Query;
88
use GeekCell\Ddd\Contracts\Application\QueryBus as QueryBusInterface;
99
use GeekCell\Ddd\Contracts\Application\QueryHandler;
10-
use GeekCell\Ddd\Support\Attributes\For\Query as ForQuery;
10+
use GeekCell\Ddd\Support\Attributes\ForType\Query as ForQuery;
1111

1212
final class QueryBus extends AbstractBus implements QueryBusInterface
1313
{

src/Support/Attributes/For/Command.php src/Support/Attributes/ForType/Command.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
declare(strict_types=1);
44

5-
namespace GeekCell\Ddd\Support\Attributes\For;
5+
namespace GeekCell\Ddd\Support\Attributes\ForType;
66

77
use Attribute;
88
use GeekCell\Ddd\Contracts\Application\Command as CommandInterface;

src/Support/Attributes/For/Query.php src/Support/Attributes/ForType/Query.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
declare(strict_types=1);
44

5-
namespace GeekCell\Ddd\Support\Attributes\For;
5+
namespace GeekCell\Ddd\Support\Attributes\ForType;
66

77
use Attribute;
88
use GeekCell\Ddd\Contracts\Application\Query as QueryInterface;

0 commit comments

Comments
 (0)