From ef19baab0c6a3d9e9a5862ccd5628f7d48dd1218 Mon Sep 17 00:00:00 2001 From: florentdevk Date: Thu, 2 Jul 2026 18:34:02 +0200 Subject: [PATCH 1/2] feat(application): add GetGameState query, handler and GameStateDTO --- .../Query/GetGameState/GameStateDTO.php | 19 ++++ .../GetGameState/GetGameStateHandler.php | 49 ++++++++++ .../Query/GetGameState/GetGameStateQuery.php | 13 +++ .../GetGameState/GetGameStateHandlerTest.php | 91 +++++++++++++++++++ 4 files changed, 172 insertions(+) create mode 100644 src/Chess/Application/Query/GetGameState/GameStateDTO.php create mode 100644 src/Chess/Application/Query/GetGameState/GetGameStateHandler.php create mode 100644 src/Chess/Application/Query/GetGameState/GetGameStateQuery.php create mode 100644 tests/Chess/Application/Query/GetGameState/GetGameStateHandlerTest.php diff --git a/src/Chess/Application/Query/GetGameState/GameStateDTO.php b/src/Chess/Application/Query/GetGameState/GameStateDTO.php new file mode 100644 index 0000000..112372c --- /dev/null +++ b/src/Chess/Application/Query/GetGameState/GameStateDTO.php @@ -0,0 +1,19 @@ + $pieces + */ + public function __construct( + public readonly string $gameId, + public readonly string $currentTurn, + public readonly bool $isOver, + public readonly array $pieces, + ) { + } +} diff --git a/src/Chess/Application/Query/GetGameState/GetGameStateHandler.php b/src/Chess/Application/Query/GetGameState/GetGameStateHandler.php new file mode 100644 index 0000000..d575d3d --- /dev/null +++ b/src/Chess/Application/Query/GetGameState/GetGameStateHandler.php @@ -0,0 +1,49 @@ +repository->findById(GameId::fromString($query->gameId)); + + if (null === $game) { + throw new \DomainException(\sprintf('Game "%s" not found.', $query->gameId)); + } + + $pieces = []; + foreach (['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'] as $file) { + foreach (range(1, 8) as $rank) { + $square = new Square($file, $rank); + $piece = $game->board()->pieceAt($square); + if (null !== $piece) { + $pieces[$square->toString()] = [ + 'type' => $piece->type()->name, + 'color' => $piece->color()->name, + ]; + } + } + } + + return new GameStateDTO( + gameId: $game->id()->toString(), + currentTurn: $game->currentTurn()->name, + isOver: $game->isOver(), + pieces: $pieces, + ); + } +} diff --git a/src/Chess/Application/Query/GetGameState/GetGameStateQuery.php b/src/Chess/Application/Query/GetGameState/GetGameStateQuery.php new file mode 100644 index 0000000..8d22c41 --- /dev/null +++ b/src/Chess/Application/Query/GetGameState/GetGameStateQuery.php @@ -0,0 +1,13 @@ +game; + } + }; + + $handler = new GetGameStateHandler($repository); + $dto = $handler(new GetGameStateQuery($game->id()->toString())); + + self::assertInstanceOf(GameStateDTO::class, $dto); + self::assertSame($game->id()->toString(), $dto->gameId); + self::assertSame('White', $dto->currentTurn); + self::assertFalse($dto->isOver); + self::assertCount(32, $dto->pieces); + } + + public function testItThrowsWhenGameNotFound(): void + { + $repository = new class implements GameRepositoryInterface { + public function save(Game $game): void + { + } + + public function findById(GameId $id): ?Game + { + return null; + } + }; + + $this->expectException(\DomainException::class); + + $handler = new GetGameStateHandler($repository); + $handler(new GetGameStateQuery('unknown-id')); + } + + public function testPiecesContainCorrectDataForWhiteKing(): void + { + $game = Game::start(); + + $repository = new class($game) implements GameRepositoryInterface { + public function __construct(private readonly Game $game) + { + } + + public function save(Game $game): void + { + } + + public function findById(GameId $id): Game + { + return $this->game; + } + }; + + $handler = new GetGameStateHandler($repository); + $dto = $handler(new GetGameStateQuery($game->id()->toString())); + + self::assertArrayHasKey('e1', $dto->pieces); + self::assertSame('King', $dto->pieces['e1']['type']); + self::assertSame('White', $dto->pieces['e1']['color']); + } +} From 930aba32fcc5e9b18b4bfde29982748ded46f942 Mon Sep 17 00:00:00 2001 From: florentdevk Date: Thu, 2 Jul 2026 18:36:33 +0200 Subject: [PATCH 2/2] docs: mark Phase 2 as complete in roadmap --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8e4d980..61d7147 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,6 @@ php bin/phpunit - [x] Phase 0 — Project setup (Symfony skeleton, CI, PHPStan, CS Fixer) - [x] Phase 1 — Domain modeling (Value Objects, Entities, Aggregate Root) -- [ ] Phase 2 — Application layer (CQRS, Symfony Messenger) +- [x] Phase 2 — Application layer (CQRS, Symfony Messenger) - [ ] Phase 3 — Infrastructure (Doctrine, REST API) - [ ] Phase 4 — PHPStan level 8, OpenAPI docs \ No newline at end of file