Skip to content

Commit 0f34e3f

Browse files
authored
Merge pull request #241 from patchlevel/3.6.x-merge-up-into-3.7.x_zgsYFz7r
Merge release 3.6.2 into 3.7.x
2 parents fb62400 + 398d4e6 commit 0f34e3f

File tree

2 files changed

+80
-50
lines changed

2 files changed

+80
-50
lines changed

docs/pages/getting_started.md

Lines changed: 80 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -21,30 +21,30 @@ namespace App\Hotel\Domain\Event;
2121

2222
use Patchlevel\EventSourcing\Aggregate\Uuid;
2323
use Patchlevel\EventSourcing\Attribute\Event;
24-
use Patchlevel\EventSourcing\Serializer\Normalizer\IdNormalizer;
2524

2625
#[Event('hotel.created')]
2726
final class HotelCreated
2827
{
2928
public function __construct(
30-
#[IdNormalizer]
31-
public readonly Uuid $id,
29+
public readonly Uuid $hotelId,
3230
public readonly string $hotelName,
3331
) {
3432
}
3533
}
3634
```
37-
A guest can check in by `name`:
35+
A guest can check in by `guestName`:
3836

3937
```php
4038
namespace App\Hotel\Domain\Event;
4139

40+
use Patchlevel\EventSourcing\Aggregate\Uuid;
4241
use Patchlevel\EventSourcing\Attribute\Event;
4342

4443
#[Event('hotel.guest_is_checked_in')]
4544
final class GuestIsCheckedIn
4645
{
4746
public function __construct(
47+
public readonly Uuid $hotelId,
4848
public readonly string $guestName,
4949
) {
5050
}
@@ -55,12 +55,14 @@ And also check out again:
5555
```php
5656
namespace App\Hotel\Domain\Event;
5757

58+
use Patchlevel\EventSourcing\Aggregate\Uuid;
5859
use Patchlevel\EventSourcing\Attribute\Event;
5960

6061
#[Event('hotel.guest_is_checked_out')]
6162
final class GuestIsCheckedOut
6263
{
6364
public function __construct(
65+
public readonly Uuid $hotelId,
6466
public readonly string $guestName,
6567
) {
6668
}
@@ -128,7 +130,7 @@ final class Hotel extends BasicAggregateRoot
128130
throw new GuestHasAlreadyCheckedIn($guestName);
129131
}
130132

131-
$this->recordThat(new GuestIsCheckedIn($guestName));
133+
$this->recordThat(new GuestIsCheckedIn($this->id, $guestName));
132134
}
133135

134136
public function checkOut(string $guestName): void
@@ -137,7 +139,7 @@ final class Hotel extends BasicAggregateRoot
137139
throw new IsNotAGuest($guestName);
138140
}
139141

140-
$this->recordThat(new GuestIsCheckedOut($guestName));
142+
$this->recordThat(new GuestIsCheckedOut($this->id, $guestName));
141143
}
142144

143145
#[Apply]
@@ -172,74 +174,97 @@ final class Hotel extends BasicAggregateRoot
172174

173175
## Define projections
174176

175-
So that we can see all the hotels on our website and also see how many guests are currently visiting the hotels,
176-
we need a projection for it. To create a projection we need a projector.
177+
Now we want to see which guests are currently checked in at a hotel or when a guest checked in and out.
178+
For this we need a projection and to create a projection we need a projector.
177179
Each projector is then responsible for a specific projection.
178180

179181
```php
180182
namespace App\Hotel\Infrastructure\Projection;
181183

182184
use App\Hotel\Domain\Event\GuestIsCheckedIn;
183185
use App\Hotel\Domain\Event\GuestIsCheckedOut;
184-
use App\Hotel\Domain\Event\HotelCreated;
185186
use Doctrine\DBAL\Connection;
187+
use Patchlevel\EventSourcing\Aggregate\Uuid;
186188
use Patchlevel\EventSourcing\Attribute\Projector;
187189
use Patchlevel\EventSourcing\Attribute\Setup;
188190
use Patchlevel\EventSourcing\Attribute\Subscribe;
189191
use Patchlevel\EventSourcing\Attribute\Teardown;
190192
use Patchlevel\EventSourcing\Subscription\Subscriber\SubscriberUtil;
191193

192-
#[Projector('hotel')]
193-
final class HotelProjection
194+
/**
195+
* @psalm-type GuestData = array{
196+
* guest_name: string,
197+
* hotel_id: string,
198+
* check_in_date: string,
199+
* check_out_date: string|null
200+
* }
201+
*/
202+
#[Projector('guests')]
203+
final class GuestProjection
194204
{
195205
use SubscriberUtil;
196206

197207
public function __construct(
198-
private Connection $projectionConnection,
208+
private Connection $db,
199209
) {
200210
}
201211

202-
/** @return list<array{id: string, name: string, guests: int}> */
203-
public function getHotels(): array
212+
/** @return list<GuestData> */
213+
public function findGuestsByHotelId(Uuid $hotelId): array
204214
{
205-
return $this->db->fetchAllAssociative("SELECT id, name, guests FROM {$this->table()};");
215+
return $this->db->createQueryBuilder()
216+
->select('*')
217+
->from($this->table())
218+
->where('hotel_id = :hotel_id')
219+
->setParameter('hotel_id', $hotelId->toString())
220+
->fetchAllAssociative();
206221
}
207222

208-
#[Subscribe(HotelCreated::class)]
209-
public function handleHotelCreated(HotelCreated $event): void
210-
{
223+
#[Subscribe(GuestIsCheckedIn::class)]
224+
public function onGuestIsCheckedIn(
225+
GuestIsCheckedIn $event,
226+
DateTimeImmutable $recordedOn,
227+
): void {
211228
$this->db->insert(
212229
$this->table(),
213230
[
214-
'id' => $event->id->toString(),
215-
'name' => $event->hotelName,
216-
'guests' => 0,
231+
'hotel_id' => $event->hotelId->toString(),
232+
'guest_name' => $event->guestName,
233+
'check_in_date' => $recordedOn->format('Y-m-d H:i:s'),
234+
'check_out_date' => null,
217235
],
218236
);
219237
}
220238

221-
#[Subscribe(GuestIsCheckedIn::class)]
222-
public function handleGuestIsCheckedIn(Uuid $hotelId): void
223-
{
224-
$this->db->executeStatement(
225-
"UPDATE {$this->table()} SET guests = guests + 1 WHERE id = ?;",
226-
[$hotelId->toString()],
227-
);
228-
}
229-
230239
#[Subscribe(GuestIsCheckedOut::class)]
231-
public function handleGuestIsCheckedOut(Uuid $hotelId): void
232-
{
233-
$this->db->executeStatement(
234-
"UPDATE {$this->table()} SET guests = guests - 1 WHERE id = ?;",
235-
[$hotelId->toString()],
240+
public function onGuestIsCheckedOut(
241+
GuestIsCheckedOut $event,
242+
DateTimeImmutable $recordedOn,
243+
): void {
244+
$this->db->update(
245+
$this->table(),
246+
[
247+
'check_out_date' => $recordedOn->format('Y-m-d H:i:s'),
248+
],
249+
[
250+
'hotel_id' => $event->hotelId->toString(),
251+
'guest_name' => $event->guestName,
252+
'check_out_date' => null,
253+
],
236254
);
237255
}
238256

239257
#[Setup]
240258
public function create(): void
241259
{
242-
$this->db->executeStatement("CREATE TABLE IF NOT EXISTS {$this->table()} (id VARCHAR PRIMARY KEY, name VARCHAR, guests INTEGER);");
260+
$this->db->executeStatement(
261+
"CREATE TABLE {$this->table()} (
262+
hotel_id VARCHAR(36) NOT NULL,
263+
guest_name VARCHAR(255) NOT NULL,
264+
check_in_date TIMESTAMP NOT NULL,
265+
check_out_date TIMESTAMP NULL
266+
);",
267+
);
243268
}
244269

245270
#[Teardown]
@@ -271,15 +296,16 @@ namespace App\Hotel\Application\Processor;
271296

272297
use App\Hotel\Domain\Event\GuestIsCheckedIn;
273298
use Patchlevel\EventSourcing\Attribute\Processor;
299+
use Patchlevel\EventSourcing\Attribute\Subscribe;
274300
use Symfony\Component\Mailer\MailerInterface;
275301
use Symfony\Component\Mime\Email;
276302

277303
use function sprintf;
278304

279305
#[Processor('admin_emails')]
280-
final class SendCheckInEmailListener
306+
final class SendCheckInEmailProcessor
281307
{
282-
private function __construct(
308+
public function __construct(
283309
private readonly MailerInterface $mailer,
284310
) {
285311
}
@@ -312,7 +338,12 @@ So that we can actually write the data to a database, we need the associated sch
312338
```bash
313339
bin/console event-sourcing:database:create
314340
bin/console event-sourcing:schema:create
315-
bin/console event-sourcing:subscription:setup
341+
```
342+
or you can use doctrine migrations:
343+
344+
```bash
345+
bin/console event-sourcing:migrations:diff
346+
bin/console event-sourcing:migrations:migrate
316347
```
317348
!!! note
318349

@@ -326,7 +357,7 @@ We are now ready to use the Event Sourcing System. We can load, change and save
326357
namespace App\Hotel\Infrastructure\Controller;
327358

328359
use App\Hotel\Domain\Hotel;
329-
use App\Hotel\Infrastructure\Projection\HotelProjection;
360+
use App\Hotel\Infrastructure\Projection\GuestProjection;
330361
use Patchlevel\EventSourcing\Aggregate\Uuid;
331362
use Patchlevel\EventSourcing\Repository\Repository;
332363
use Symfony\Component\HttpFoundation\JsonResponse;
@@ -337,26 +368,26 @@ use Symfony\Component\Routing\Annotation\Route;
337368
#[AsController]
338369
final class HotelController
339370
{
371+
/** @param Repository<Hotel> $hotelRepository */
340372
public function __construct(
341-
private readonly HotelProjection $hotelProjection,
342-
/** @var Repository<Hotel> */
343373
private readonly Repository $hotelRepository,
374+
private readonly GuestProjection $guestProjection,
344375
) {
345376
}
346377

347-
#[Route('/', methods:['GET'])]
348-
public function listAction(): JsonResponse
378+
#[Route('/{hotelId}/guests', methods:['GET'])]
379+
public function hotelGuestsAction(Uuid $hotelId): JsonResponse
349380
{
350381
return new JsonResponse(
351-
$this->hotelProjection->getHotels(),
382+
$this->guestProjection->findGuestsByHotelId($hotelId),
352383
);
353384
}
354385

355386
#[Route('/create', methods:['POST'])]
356387
public function createAction(Request $request): JsonResponse
357388
{
358-
$hotelName = $request->request->get('name'); // need validation!
359-
$id = Uuid::v7();
389+
$hotelName = $request->getPayload()->get('name'); // need validation!
390+
$id = Uuid::generate();
360391

361392
$hotel = Hotel::create($id, $hotelName);
362393
$this->hotelRepository->save($hotel);
@@ -367,7 +398,7 @@ final class HotelController
367398
#[Route('/{hotelId}/check-in', methods:['POST'])]
368399
public function checkInAction(Uuid $hotelId, Request $request): JsonResponse
369400
{
370-
$guestName = $request->request->get('name'); // need validation!
401+
$guestName = $request->getPayload()->get('name'); // need validation!
371402

372403
$hotel = $this->hotelRepository->load($hotelId);
373404
$hotel->checkIn($guestName);
@@ -379,7 +410,7 @@ final class HotelController
379410
#[Route('/{hotelId}/check-out', methods:['POST'])]
380411
public function checkOutAction(Uuid $hotelId, Request $request): JsonResponse
381412
{
382-
$guestName = $request->request->get('name'); // need validation!
413+
$guestName = $request->getPayload()->get('name'); // need validation!
383414

384415
$hotel = $this->hotelRepository->load($hotelId);
385416
$hotel->checkOut($guestName);

src/DependencyInjection/PatchlevelEventSourcingExtension.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -773,7 +773,6 @@ private function configureCommands(ContainerBuilder $container): void
773773
$container->register(SubscriptionBootCommand::class)
774774
->setArguments([
775775
new Reference(SubscriptionEngine::class),
776-
new Reference(Store::class),
777776
])
778777
->addTag('console.command');
779778

0 commit comments

Comments
 (0)