Skip to content

Commit 521e586

Browse files
authored
refactor!: Use geekcell/ddd as core library.
* refactor!: Use `geekcell/ddd` as core library. * feat: Add Doctrine paginator and repository. * refactor!: Adjust paginator and repository for new interfaces. * doc: Update README.md * refactor!: Simplify repository constructor. * feat: Add custom `Id` and `Uuid` Doctrine types. * chore: Finalize pull request for merge. Closes #5
1 parent bbf49e6 commit 521e586

28 files changed

+3593
-1482
lines changed

CHANGELOG.md

Lines changed: 0 additions & 29 deletions
This file was deleted.

README.md

Lines changed: 90 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Symfony Bundle for DDD
22

3-
Various additions for [domain driven development](https://martinfowler.com/tags/domain%20driven%20design.html) inside Symfony.
3+
This Symfony bundle augments [geekcell/php-ddd](https://github.com/geekcell/php-ddd) with framework-specific implementations to enable seamless [domain driven design](https://martinfowler.com/tags/domain%20driven%20design.html) in a familiar environment.
44

55
## Installation
66

@@ -10,48 +10,128 @@ To use this bundle, require it in Composer
1010
composer require geekcell/ddd-bundle
1111
```
1212

13+
## Quickstart
14+
15+
- [Aggregate Root](#aggregate-root)
16+
- [Repositories](#repositories)
17+
- [Command & Query Bus](#command--query-bus)
18+
- [Supporting Tools](#supporting-tools)
19+
1320
## Aggregate Root
1421

1522
Extend from `AggregateRoot` to record and commit domain events. Domain events must implement the (marker) interface `DomainEvent`. Events will be dispatched via the currently configured Symfony event dispatcher.
1623

1724
### Example Usage
1825

1926
```php
20-
use GeekCell\DDDBundle\Domain\Event\DomainEvent;
21-
use GeekCell\DDDBundle\Domain\Model\AggregateRoot;
27+
use GeekCell\Ddd\Contracts\Domain\Event as DomainEvent;
28+
use GeekCell\DddBundle\Domain\AggregateRoot;
2229

2330
class OrderPlacedEvent implements DomainEvent
2431
{
25-
...
32+
public function __construct(
33+
private readonly Order $order,
34+
) {
35+
}
36+
37+
// Getters etc.
2638
}
2739

2840
class Order extends AggregateRoot
2941
{
3042
public function save(): void
3143
{
32-
$this->record(new OrderPlacedEvent());
44+
$this->record(new OrderPlacedEvent($this));
3345
}
3446

35-
...
47+
// ...
3648
}
3749

38-
$order = new Order();
50+
$order = new Order( /* ... */ );
3951
$order->save();
40-
$order->commit(); // <- Events will be dispatched
52+
$order->commit(); // All recorded events will be dispatched and released
4153
```
4254

4355
_Hint: If you want to dispatch an event directly, use `AggregateRoot::dispatch()` instead of `AggregateRoot::record()`._
4456

4557
If you cannot (or don't want to) extend from `AggregateRoot`, you can alternative use `DispatchableTrait` to add dispatching capabilities to any class. The former is however the recommended way.
4658

59+
## Repositories
60+
61+
_coming soon..._
62+
63+
## Command & Query Bus
64+
65+
You can use `CommandBus` and `QueryBus` as services to implement [CQRS](https://martinfowler.com/bliki/CQRS.html). Internally, both buses will use the [Symfony messenger](https://symfony.com/doc/current/messenger.html) as "backend".
66+
67+
## Example Usage
68+
69+
```php
70+
// src/Application/Query/TopRatedBookQuery.php
71+
use GeekCell\Ddd\Contracts\Application\Query;
72+
73+
class TopRatedBooksQuery implements Query
74+
{
75+
public function __construct(
76+
private readonly string $category,
77+
private readonly int $sinceDays,
78+
private readonly int $limit = 10,
79+
) {
80+
}
81+
82+
// Getters etc.
83+
}
84+
85+
// src/Application/Query/TopRatedBookQueryHandler.php
86+
use GeekCell\Ddd\Contracts\Application\QueryHandler;
87+
88+
#[AsMessageHandler]
89+
class TopRatedBookQueryHandler implements QueryHandler
90+
{
91+
public function __construct(
92+
private readonly BookRepository $repository,
93+
) {
94+
}
95+
96+
public function __invoke(TopRatedBookQuery $query)
97+
{
98+
$books = $this->repository
99+
->findTopRated($query->getCategory(), $query->getSinceDays())
100+
->paginate($query->getLimit());
101+
102+
return $books;
103+
}
104+
}
105+
106+
// src/Infrastructure/Http/Controller/BookController.php
107+
use GeekCell\Ddd\Contracts\Application\QueryBus;
108+
109+
class BookController extends AbstractController
110+
{
111+
public function __construct(
112+
private readonly QueryBus $queryBus,
113+
) {
114+
}
115+
116+
#[Route('/books/top-rated')]
117+
public function getTopRated(Request $request)
118+
{
119+
$query = new TopRatedBooksQuery( /* extract from request */ );
120+
$topRatedBooks = $this->queryBus->dispatch($query);
121+
122+
// ...
123+
}
124+
}
125+
```
126+
47127
## Supporting Tools
48128

49129
### Facades
50130

51131
Facades are heavily inspired by [Laravel's Facades](https://laravel.com/docs/facades) and are more or less singletons on steroids. They are basically a shortcut to services inside the DIC.
52132

53133
```php
54-
use GeekCell\DDDBundle\Support\Facades\EventDispatcher;
134+
use GeekCell\DddBundle\Support\Facades\EventDispatcher;
55135

56136
EventDispatcher::dispatch($someEvent);
57137

@@ -61,7 +141,7 @@ EventDispatcher::dispatch($someEvent);
61141
You can create your own facades by extending from `Facade` and implementing the `Facade::getFacadeAccessor()` method to return the DIC service alias.
62142

63143
```php
64-
use GeekCell\DDDBundle\Support\Facades\Facade;
144+
use GeekCell\DddBundle\Support\Facades\Facade;
65145

66146
class Logger extends Facade
67147
{

composer.json

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
{
22
"name": "geekcell/ddd-bundle",
3+
"description": "A bundle for pragmatic domain driven design in Symfony.",
34
"type": "symfony-bundle",
4-
"version": "1.0.3",
5+
"version": "1.1.0",
56
"license": "MIT",
67
"authors": [
78
{
@@ -10,28 +11,32 @@
1011
}
1112
],
1213
"require": {
13-
"symfony/http-kernel": "^6.0",
14-
"symfony/event-dispatcher": "^6.0",
15-
"symfony/dependency-injection": "^6.0",
14+
"beberlei/assert": "^3.3",
15+
"geekcell/ddd": "^1.0.0",
1616
"symfony/config": "^6.0",
17-
"beberlei/assert": "^3.3"
17+
"symfony/dependency-injection": "^6.0",
18+
"symfony/event-dispatcher": "^6.0",
19+
"symfony/http-kernel": "^6.0",
20+
"symfony/messenger": "^6.0",
21+
"doctrine/orm": "^2.12",
22+
"symfony/string": "^6.2"
1823
},
1924
"autoload": {
2025
"psr-4": {
21-
"GeekCell\\DDDBundle\\": "src"
26+
"GeekCell\\DddBundle\\": "src"
2227
}
2328
},
2429
"autoload-dev": {
2530
"psr-4": {
26-
"GeekCell\\DDDBundle\\Tests\\": "tests/"
31+
"GeekCell\\DddBundle\\Tests\\": "tests/"
2732
}
2833
},
2934
"require-dev": {
35+
"friendsofphp/php-cs-fixer": "^3.13",
3036
"mockery/mockery": "^1.5",
31-
"phpunit/phpunit": "^9.5",
3237
"phpstan/phpstan": "^1.9",
33-
"friendsofphp/php-cs-fixer": "^3.13",
34-
"phpstan/phpstan-mockery": "^1.1"
38+
"phpstan/phpstan-mockery": "^1.1",
39+
"phpunit/phpunit": "^9.5"
3540
},
3641
"scripts": {
3742
"gc:tests": "phpunit --testdox --colors=always",

0 commit comments

Comments
 (0)