Skip to content

Commit edb931a

Browse files
authored
Release 0.2.0
2 parents f065a66 + 00b21ac commit edb931a

18 files changed

+212
-34
lines changed

.github/workflows/documentation.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: Documentation
2+
3+
on:
4+
pull_request:
5+
branches: [ main ]
6+
types: [ closed ]
7+
8+
jobs:
9+
publish:
10+
if: github.event.pull_request.merged == true
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Checkout source code
15+
uses: actions/checkout@v2
16+
17+
- name: Pusblish Wiki
18+
uses: SwiftDocOrg/github-wiki-publish-action@v1
19+
with:
20+
path: "wiki/"
21+
env:
22+
GH_PERSONAL_ACCESS_TOKEN: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}

.github/workflows/test.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
- name: Checkout source code
1818
uses: actions/checkout@v2
1919

20-
- name: Install PHP
20+
- name: Install PHP ${{ matrix.php-version }}
2121
uses: shivammathur/setup-php@v2
2222
with:
2323
php-version: ${{ matrix.php-version }}
@@ -36,7 +36,6 @@ jobs:
3636
run: composer validate
3737

3838
- name: Install dependencies
39-
if: steps.composer-cache.outputs.cache-hit != 'true'
4039
run: composer install --prefer-dist --no-progress --no-suggest
4140

4241
- name: Execute tests (Unit and Feature tests) via PHPUnit

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ Check this small example of the usage:
9090
$response = $queryBus->ask(new GetUserQuery('some-uuid-value'));
9191
```
9292

93+
Check the [wiki](https://github.com/othercodes/ComplexHeart/wiki) for more detailed examples.
94+
9395
## References
9496

9597
- [Pro Codely TV](https://pro.codely.tv/library/)

src/Domain/Bus/Event.php

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,15 @@ abstract class Event extends Message
2424
*/
2525
public function __construct(string $aggregateId, array $data = [])
2626
{
27-
parent::__construct(
28-
[
29-
'aggregateId' => $aggregateId,
30-
'data' => $data,
31-
]
32-
);
27+
parent::__construct(array_merge(['aggregateId' => $aggregateId], $data));
3328
}
3429

3530
/**
3631
* Return the Aggregate Id.
3732
*
3833
* @return string
3934
*/
40-
public function aggregateId()
35+
public function aggregateId(): string
4136
{
4237
return $this->fromPayload('aggregateId');
4338
}

src/Domain/Collection.php

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,18 @@ class Collection extends BaseCollection
3333
private int $perPage;
3434

3535
/**
36-
* The type of item in the collection.
36+
* The type of each items in the collection.
3737
*
3838
* @var string
3939
*/
40-
protected string $typeOf = 'mixed';
40+
protected string $valueType = 'mixed';
41+
42+
/**
43+
* The type of each keys in the collection.
44+
*
45+
* @var string
46+
*/
47+
protected string $keyType = 'mixed';
4148

4249
/**
4350
* Collection constructor.
@@ -85,24 +92,53 @@ protected function invariantAmountOfItemsMustBeLessOrEqualsThanTotalItems(): boo
8592
* @return bool
8693
* @throws InvariantViolation
8794
*/
88-
protected function invariantItemsMustBeOfSameType(): bool
95+
protected function invariantItemsMustMatchTheRequiredType(): bool
8996
{
90-
$primitives = ['integer', 'boolean', 'float', 'string', 'array', 'object', 'callable'];
91-
if ($this->typeOf !== 'mixed') {
92-
$check = in_array($this->typeOf, $primitives)
93-
? fn($value): bool => gettype($value) !== $this->typeOf
94-
: fn($value): bool => !($value instanceof $this->typeOf);
97+
if ($this->valueType !== 'mixed') {
98+
$primitives = ['integer', 'boolean', 'float', 'string', 'array', 'object', 'callable'];
99+
$check = in_array($this->valueType, $primitives)
100+
? fn($value): bool => gettype($value) !== $this->valueType
101+
: fn($value): bool => !($value instanceof $this->valueType);
95102

96103
foreach ($this->items as $index => $item) {
97104
if ($check($item)) {
98-
throw new InvariantViolation("All items must be type of {$this->typeOf}");
105+
throw new InvariantViolation("All items must be type of {$this->valueType}");
99106
}
100107
}
101108
}
102109

103110
return true;
104111
}
105112

113+
/**
114+
* Invariant: Check the collection keys to match the required type.
115+
*
116+
* Supported types:
117+
* - string
118+
* - integer
119+
*
120+
* @return bool
121+
* @throws InvariantViolation
122+
*/
123+
protected function invariantKeysMustMatchTheRequiredType(): bool
124+
{
125+
if ($this->keyType !== 'mixed') {
126+
$supported = ['string', 'integer'];
127+
if (!in_array($this->keyType, $supported)) {
128+
throw new InvariantViolation(
129+
"Unsupported key type, must be one of ".implode(', ', $supported)
130+
);
131+
}
132+
133+
foreach ($this->items as $index => $item) {
134+
if (gettype($index) !== $this->keyType) {
135+
throw new InvariantViolation("All keys must be type of {$this->keyType}");
136+
}
137+
}
138+
}
139+
return true;
140+
}
141+
106142
/**
107143
* Return the total amount of items on the data source.
108144
*

src/Domain/Contracts/Aggregate.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
namespace OtherCode\ComplexHeart\Domain\Contracts;
66

7-
use OtherCode\ComplexHeart\Domain\Bus\Event;
7+
use OtherCode\ComplexHeart\Domain\Contracts\Bus\EventBus;
88

99
/**
1010
* Interface Aggregate
@@ -15,9 +15,14 @@
1515
interface Aggregate extends Entity
1616
{
1717
/**
18-
* Pull the registered Domain Events.
18+
* Publish the registered Domain Events.
1919
*
20-
* @return Event[]
20+
* $aggregate = new Aggregate();
21+
* // do things and generate events
22+
* $aggregate->publishDomainEvents($eventBus);
23+
*
24+
* @param EventBus $eventBus
25+
* @return void
2126
*/
22-
public function pullDomainEvents(): array;
27+
public function publishDomainEvents(EventBus $eventBus): void;
2328
}

src/Domain/Contracts/Bus/QueryBus.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace OtherCode\ComplexHeart\Domain\Contracts\Bus;
66

77
use OtherCode\ComplexHeart\Domain\Bus\Query;
8+
use OtherCode\ComplexHeart\Domain\Bus\Response;
89

910
/**
1011
* Interface QueryBus
@@ -21,5 +22,5 @@ interface QueryBus
2122
*
2223
* @return mixed
2324
*/
24-
public function ask(Query $query);
25+
public function ask(Query $query): Response;
2526
}

src/Domain/Traits/HasDomainEvents.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace OtherCode\ComplexHeart\Domain\Traits;
66

77
use OtherCode\ComplexHeart\Domain\Bus\Event;
8+
use OtherCode\ComplexHeart\Domain\Contracts\Bus\EventBus;
89

910
/**
1011
* Trait HasDomainEvents
@@ -18,21 +19,20 @@ trait HasDomainEvents
1819
/**
1920
* List of registered domain events.
2021
*
21-
* @var array<Event>
22+
* @var Event[]
2223
*/
2324
private array $_domainEvents = [];
2425

2526
/**
26-
* Pull out all the registered domain events.
27+
* Publish the registered Domain Events.
2728
*
28-
* @return array<Event>
29+
* @param EventBus $eventBus
30+
* @return void
2931
*/
30-
final public function pullDomainEvents(): array
32+
final public function publishDomainEvents(EventBus $eventBus): void
3133
{
32-
$domainEvents = $this->_domainEvents;
34+
$eventBus->publish(...$this->_domainEvents);
3335
$this->_domainEvents = [];
34-
35-
return $domainEvents;
3636
}
3737

3838
/**

src/Domain/Traits/HasServiceBus.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,16 @@ final public function bind(ServiceBus $bus): void
3333
$this->_serviceBus = $bus;
3434
}
3535

36+
/**
37+
* Return true if the Service Bus is bound to the object, false otherwise.
38+
*
39+
* @return bool
40+
*/
41+
final public function isBound(): bool
42+
{
43+
return isset($this->_serviceBus);
44+
}
45+
3646
/**
3747
* Return the Command Bus implementation.
3848
*

tests/Domain/CollectionTest.php

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class CollectionTest extends MockeryTestCase
2020
public function testShouldSuccessfullyInstantiateACollection(): void
2121
{
2222
$c = new class (['foo', 'bar']) extends Collection {
23-
protected string $typeOf = 'string';
23+
protected string $valueType = 'string';
2424
};
2525

2626
$this->assertInstanceOf(Collection::class, $c);
@@ -31,7 +31,7 @@ public function testShouldFailWithWrongPrimitiveValueItemTypes(): void
3131
$this->expectException(InvariantViolation::class);
3232

3333
new class ([1, '2']) extends Collection {
34-
protected string $typeOf = 'integer';
34+
protected string $valueType = 'integer';
3535
};
3636
}
3737

@@ -40,7 +40,7 @@ public function testShouldFailWithWrongClassValueItemTypes(): void
4040
$this->expectException(InvariantViolation::class);
4141

4242
new class ([new stdClass(), '2']) extends Collection {
43-
protected string $typeOf = stdClass::class;
43+
protected string $valueType = stdClass::class;
4444
};
4545
}
4646

@@ -57,4 +57,22 @@ public function testShouldFailDueToAmountOfItemsIsGreaterThanTotalItems(): void
5757

5858
new Collection([0, 1, 2, 3, 4], 2, 5);
5959
}
60+
61+
public function testShouldFailDueToUnsupportedKeyType(): void
62+
{
63+
$this->expectException(InvariantViolation::class);
64+
65+
new class (['foo', 'bar']) extends Collection {
66+
protected string $keyType = 'boolean';
67+
};
68+
}
69+
70+
public function testShouldFailDueToWrongKeyType(): void
71+
{
72+
$this->expectException(InvariantViolation::class);
73+
74+
new class (['foo', 'bar']) extends Collection {
75+
protected string $keyType = 'string';
76+
};
77+
}
6078
}

tests/Domain/Traits/HasDomainEventTest.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44

55
namespace OtherCode\ComplexHeart\Tests\Domain\Traits;
66

7+
use Mockery;
78
use Mockery\Adapter\Phpunit\MockeryTestCase;
9+
use OtherCode\ComplexHeart\Domain\Bus\Event;
10+
use OtherCode\ComplexHeart\Domain\Contracts\Bus\EventBus;
811
use OtherCode\ComplexHeart\Domain\ValueObjects\UUIDValue;
912
use OtherCode\ComplexHeart\Tests\Sample\Order;
1013
use OtherCode\ComplexHeart\Tests\Sample\OrderLine;
@@ -20,14 +23,19 @@ class HasDomainEventTest extends MockeryTestCase
2023
{
2124
public function testShouldAddAndPullDomainEvent(): void
2225
{
26+
$eventBus = Mockery::mock(EventBus::class);
27+
$eventBus->shouldReceive('publish')
28+
->once()
29+
->with(Mockery::type(Event::class));
30+
2331
$o = Order::create(
2432
UUIDValue::random(),
2533
new OrderLine(UUIDValue::random(), new ProductName('PR 1')),
2634
new OrderLine(UUIDValue::random(), new ProductName('PR 2')),
2735
);
2836

2937
$this->assertCount(1, $o->getDomainEvents());
30-
$this->assertCount(1, $o->pullDomainEvents());
38+
$o->publishDomainEvents($eventBus);
3139
$this->assertCount(0, $o->getDomainEvents());
3240
}
3341
}

wiki/Domain-Modeling-Aggregates.md

Whitespace-only changes.

wiki/Domain-Modeling-Entities.md

Whitespace-only changes.

wiki/Domain-Modeling-Value-Objects.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
2+
```php
3+
use OtherCode\ComplexHeart\Domain\Contracts\ValueObject;
4+
use OtherCode\ComplexHeart\Domain\Traits\IsValueObject;
5+
6+
/**
7+
* Class Color
8+
* @method string value()
9+
*/
10+
final class Color implements ValueObject
11+
{
12+
use IsValueObject;
13+
14+
private string $value;
15+
16+
public function __construct(string $value)
17+
{
18+
$this->initialize(['value' => $value]);
19+
}
20+
21+
protected function invariantValueMustBeHexadecimal(): bool
22+
{
23+
return preg_match('/^#(?:[0-9a-fA-F]{3}){1,2}$/', $this->value) === 1;
24+
}
25+
26+
public function __toString(): string
27+
{
28+
return $this->value();
29+
}
30+
}
31+
32+
$red = new Color('#ff0000');
33+
$red->equals(new Color('#00ff00')); // false
34+
$red->value(); // #ff0000
35+
$magenta = new Color('ff00ff'); // Exception InvariantViolation: Value must be hexadecimal.
36+
```

wiki/Domain-Modeling.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
## How to model the domain
2+
3+
Complex Heart allows you to model your domain Aggregates, Entities, and Value Objects using a set of traits. Great, but
4+
why traits and not classes? Well, sometimes you have some kind of inheritance in your classes. Being forced to use a
5+
certain base class is too invasive and personally, I don't like it. By using a set of traits and interfaces you have all
6+
the functionality you need without compromising the essence of your own domain.
7+
8+
The available traits are:
9+
10+
- `HasAttributes` Provide some functionality to manage attributes.
11+
- `HasEquality` Provide functionality to handle equality between objects.
12+
- `HasInvariants` Allow invariant checking on instantiation (Guard Clause).
13+
- `HasIdentity` Define the Entity/Aggregate identity.
14+
- `HasDomainEvents` Provide domain event management.
15+
16+
On top of those base traits **Complex Heart** provide ready to use compositions:
17+
18+
- `IsModel` composed by `HasAttributes` and `HasInvariants`
19+
- `IsValueObject` composed by `IsModel` and `HasEquality`
20+
- `IsEntity` composed by `IsModel`, `HasIdentity`, `HasEquality`
21+
- `IsAggregate` composed by `IsEntity`, `HasDomainEvents`

wiki/Home.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

0 commit comments

Comments
 (0)