feat: 3 layers archi + healthcheck test

This commit is contained in:
Aymeric GUERACAGUE 2025-05-19 15:01:41 +02:00
parent a62b5e333c
commit c9b3d8e11b
Signed by: Superkooka
GPG Key ID: F78F2B172E894865
26 changed files with 504 additions and 28 deletions

View File

@ -10,7 +10,7 @@ APP_SECRET=79e3e28a27aa752548962c961aee5324
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db" # DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
# DATABASE_URL="postgres://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8&charset=utf8mb4" # DATABASE_URL="postgres://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8&charset=utf8mb4"
DB_DATABASE=stat_an DB_DATABASE=stat_an
DB_HOST=postgres DB_HOST=stat-an-postgres
DB_PORT=5432 DB_PORT=5432
DB_USERNAME=postgres DB_USERNAME=postgres
DB_PASSWORD=postgres DB_PASSWORD=postgres

View File

@ -11,6 +11,8 @@
"doctrine/doctrine-bundle": "^2.14", "doctrine/doctrine-bundle": "^2.14",
"doctrine/doctrine-migrations-bundle": "^3.4", "doctrine/doctrine-migrations-bundle": "^3.4",
"doctrine/orm": "^3.3", "doctrine/orm": "^3.3",
"league/tactician-bundle": "^1.5",
"league/tactician-doctrine": "^1.2",
"symfony/console": "7.2.*", "symfony/console": "7.2.*",
"symfony/dotenv": "7.2.*", "symfony/dotenv": "7.2.*",
"symfony/flex": "^2", "symfony/flex": "^2",

309
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "0de5194aa02e73e25f4a87a916a437ef", "content-hash": "77a2f75dd7bf69a84b35ce22200e7da3",
"packages": [ "packages": [
{ {
"name": "doctrine/cache", "name": "doctrine/cache",
@ -1223,6 +1223,313 @@
}, },
"time": "2025-01-24T11:45:48+00:00" "time": "2025-01-24T11:45:48+00:00"
}, },
{
"name": "league/tactician",
"version": "v1.1.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/tactician.git",
"reference": "e79f763170f3d5922ec29e85cffca0bac5cd8975"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/tactician/zipball/e79f763170f3d5922ec29e85cffca0bac5cd8975",
"reference": "e79f763170f3d5922ec29e85cffca0bac5cd8975",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"require-dev": {
"mockery/mockery": "^1.3",
"phpunit/phpunit": "^7.5.20 || ^9.3.8",
"squizlabs/php_codesniffer": "^3.5.8"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0-dev"
}
},
"autoload": {
"psr-4": {
"League\\Tactician\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ross Tuck",
"homepage": "http://tactician.thephpleague.com"
}
],
"description": "A small, flexible command bus. Handy for building service layers.",
"keywords": [
"command",
"command bus",
"service layer"
],
"support": {
"issues": "https://github.com/thephpleague/tactician/issues",
"source": "https://github.com/thephpleague/tactician/tree/v1.1.0"
},
"time": "2021-02-14T15:29:04+00:00"
},
{
"name": "league/tactician-bundle",
"version": "v1.5.2",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/tactician-bundle.git",
"reference": "8acdfffc227295cca945ff1a964f88e4f034cdf9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/tactician-bundle/zipball/8acdfffc227295cca945ff1a964f88e4f034cdf9",
"reference": "8acdfffc227295cca945ff1a964f88e4f034cdf9",
"shasum": ""
},
"require": {
"league/tactician": "^1.0",
"league/tactician-container": "^2.0|^3.0",
"league/tactician-logger": "^0.10|^0.11",
"php": ">=7.2",
"symfony/config": "^3.4|^4.4|^5.0|^6.0|^7.0",
"symfony/dependency-injection": "^3.4|^4.4|^5.0|^6.0|^7.0",
"symfony/http-kernel": "^3.4|^4.4|^5.0|^6.0|^7.0",
"symfony/yaml": "^3.4|^4.4|^5.0|^6.0|^7.0"
},
"require-dev": {
"matthiasnoback/symfony-config-test": "^4.2.1",
"matthiasnoback/symfony-dependency-injection-test": "^4.2.1",
"mockery/mockery": "~1.0",
"phpspec/prophecy": "^1.18",
"phpunit/phpunit": "~8.5",
"symfony/console": "^3.4|^4.4|^5.0|^6.0|^7.0",
"symfony/framework-bundle": "^3.4.31|^4.4|^5.0|^6.0|^7.0",
"symfony/security-bundle": "^3.4|^4.4|^5.0|^6.0|^7.0",
"symfony/security-core": "^3.4|^4.4|^5.0|^6.0|^7.0",
"symfony/validator": "^3.4|^4.4|^5.0|^6.0|^7.0"
},
"suggest": {
"league/tactician-doctrine": "For doctrine transaction middleware",
"symfony/console": "For debugging command-to-handler routing using the tactician:debug console command",
"symfony/security": "For command security middleware",
"symfony/validator": "For command validator middleware"
},
"type": "symfony-bundle",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"autoload": {
"psr-4": {
"League\\Tactician\\Bundle\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Rafael Dohms",
"homepage": "http://doh.ms"
},
{
"name": "Richard Tuin",
"homepage": "http://www.rtuin.nl/"
},
{
"name": "Xander Smalbil",
"email": "xander@videofunk.nl"
},
{
"name": "Ross Tuck",
"email": "me@rosstuck.com"
}
],
"description": "Bundle to integrate Tactician with Symfony projects",
"keywords": [
"bundle",
"symfony",
"tactician"
],
"support": {
"issues": "https://github.com/thephpleague/tactician-bundle/issues",
"source": "https://github.com/thephpleague/tactician-bundle/tree/v1.5.2"
},
"time": "2025-02-19T08:17:44+00:00"
},
{
"name": "league/tactician-container",
"version": "v3.0.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/tactician-container.git",
"reference": "5150cbf861a51b1b27eba28449b46deee18cc423"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/tactician-container/zipball/5150cbf861a51b1b27eba28449b46deee18cc423",
"reference": "5150cbf861a51b1b27eba28449b46deee18cc423",
"shasum": ""
},
"require": {
"league/tactician": "^1.0",
"php": ">=7.4",
"psr/container": "~2.0"
},
"require-dev": {
"league/container": "~4.0",
"phpunit/phpunit": "~6.0",
"squizlabs/php_codesniffer": "~2.0"
},
"type": "library",
"autoload": {
"psr-4": {
"League\\Tactician\\Container\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nigel Greenway",
"homepage": "http://futurepixels.co.uk"
}
],
"description": "Tactician integration for any container implementing PSR-11",
"keywords": [
"container",
"container-interop",
"di",
"interoperable",
"league",
"tactician"
],
"support": {
"issues": "https://github.com/thephpleague/tactician-container/issues",
"source": "https://github.com/thephpleague/tactician-container/tree/v3.0.0"
},
"time": "2021-12-16T20:15:05+00:00"
},
{
"name": "league/tactician-doctrine",
"version": "v1.2.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/tactician-doctrine.git",
"reference": "3ae678caddd008da3b54ba77be8dbf6e46e7c06a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/tactician-doctrine/zipball/3ae678caddd008da3b54ba77be8dbf6e46e7c06a",
"reference": "3ae678caddd008da3b54ba77be8dbf6e46e7c06a",
"shasum": ""
},
"require": {
"doctrine/dbal": "^2.4 || ^3.1.5",
"league/tactician": "^1.1",
"php": ">=7.1"
},
"require-dev": {
"doctrine/orm": "^2.4",
"mockery/mockery": "^1.3.5",
"phpunit/phpunit": "^7.5.20 || ^9.5.10",
"squizlabs/php_codesniffer": "^3.6.2",
"yoast/phpunit-polyfills": "^1.0"
},
"suggest": {
"doctrine/orm": "Required if you need to use ORM Transaction Middleware"
},
"type": "library",
"autoload": {
"psr-4": {
"League\\Tactician\\Doctrine\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ross Tuck"
}
],
"description": "Plugins for Tactician commands using Doctrine, like wrapping every command in a transaction",
"homepage": "https://github.com/thephpleague/tactician-doctrine",
"keywords": [
"command bus",
"doctrine",
"tactician",
"transactions"
],
"support": {
"issues": "https://github.com/thephpleague/tactician-doctrine/issues",
"source": "https://github.com/thephpleague/tactician-doctrine/tree/v1.2.0"
},
"time": "2022-01-17T17:03:37+00:00"
},
{
"name": "league/tactician-logger",
"version": "v0.11.1",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/tactician-logger.git",
"reference": "586a9f9781e9481a527010561ae80c0403d3f23d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/tactician-logger/zipball/586a9f9781e9481a527010561ae80c0403d3f23d",
"reference": "586a9f9781e9481a527010561ae80c0403d3f23d",
"shasum": ""
},
"require": {
"league/tactician": "^1.1",
"php": ">=7.3",
"psr/log": "^1.0|^2.0|^3.0"
},
"require-dev": {
"mockery/mockery": "^1.3",
"phpunit/phpunit": "^9.3",
"squizlabs/php_codesniffer": "^3.5.8"
},
"type": "library",
"autoload": {
"psr-4": {
"League\\Tactician\\Logger\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ross Tuck"
}
],
"description": "Adds PSR-3 logging support to the Tactician command bus",
"homepage": "https://github.com/thephpleague/tactician-logger",
"keywords": [
"log",
"logging",
"tactician"
],
"support": {
"issues": "https://github.com/thephpleague/tactician-logger/issues",
"source": "https://github.com/thephpleague/tactician-logger/tree/v0.11.1"
},
"time": "2025-04-18T13:03:20+00:00"
},
{ {
"name": "psr/cache", "name": "psr/cache",
"version": "3.0.0", "version": "3.0.0",

View File

@ -5,4 +5,5 @@ return [
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true], Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
League\Tactician\Bundle\TacticianBundle::class => ['all' => true],
]; ];

View File

@ -23,11 +23,17 @@ doctrine:
Doctrine\DBAL\Platforms\PostgreSQLPlatform: identity Doctrine\DBAL\Platforms\PostgreSQLPlatform: identity
auto_mapping: true auto_mapping: true
mappings: mappings:
App: App\Domain\Entity:
type: attribute type: php
dir: '%kernel.project_dir%/src/Infrastructure/Persistence/Mapping'
is_bundle: false is_bundle: false
dir: '%kernel.project_dir%/src/Entity' prefix: App\Domain\Entity
prefix: 'App\Entity' alias: App
App\Application\ReadModel:
type: php
dir: '%kernel.project_dir%/src/Infrastructure/Persistence/Mapping'
is_bundle: false
prefix: App\Application\ReadModel
alias: App alias: App
controller_resolver: controller_resolver:
auto_mapping: false auto_mapping: false

View File

@ -0,0 +1,15 @@
# Library documentation: http://tactician.thephpleague.com/
# Bundle documentation: https://github.com/thephpleague/tactician-bundle/blob/v1.0/README.md
tactician:
commandbus:
default:
middleware:
# Security middleware - https://github.com/thephpleague/tactician-bundle#security-middleware-tacticianmiddlewaresecurity
# - tactician.middleware.security
# Validator middleware - https://github.com/thephpleague/tactician-bundle/tree/v1.0#validator-middleware-tacticianmiddlewarevalidator
# - tactician.middleware.validator
# Locking middleware - http://tactician.thephpleague.com/plugins/locking-middleware/
- tactician.middleware.locking
# Doctrine transactional middleware, requires additional package - https://github.com/thephpleague/tactician-doctrine
- tactician.middleware.doctrine
- command_handler

View File

@ -1,5 +1,4 @@
controllers: healthcheck:
resource: path: /
path: ../src/Controller/ controller: App\Infrastructure\Controller\HealthCheck
namespace: App\Controller methods: GET
type: attribute

View File

@ -16,9 +16,30 @@ services:
App\: App\:
resource: '../src/' resource: '../src/'
exclude: exclude:
- '../src/DependencyInjection/' - '../src/Domain'
- '../src/Entity/' - '../src/Infrastructure/Persistence/Mapping'
- '../src/Application/ReadModel'
- '../src/Kernel.php' - '../src/Kernel.php'
App\Application\UseCase\:
resource: '../src/Application/UseCase'
exclude:
- '../src/Application/UseCase/**/*Request.php'
- '../src/Application/UseCase/**/*Context.php'
public: true
League\Tactician\Handler\Locator\HandlerLocator: '@App\Application\CommandBus\ContainerCommandHandlerLocator'
App\Infrastructure\CommandBus\CommandBusCaller: '@App\Infrastructure\CommandBus\UnmanagedErrorsCommandBusCaller'
command_handler:
class: League\Tactician\Handler\CommandHandlerMiddleware
arguments:
$commandNameExtractor: '@tactician.handler.command_name_extractor.class_name'
$handlerLocator: '@App\Application\CommandBus\ContainerCommandHandlerLocator'
$methodNameInflector: '@tactician.handler.method_name_inflector.handle'
Psr\Container\ContainerInterface: '@service_container'
# add more service definitions when explicit configuration is needed # add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones # please note that last definitions always *replace* previous ones

View File

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace App\Application\CommandBus;
use League\Tactician\Exception\MissingHandlerException;
use League\Tactician\Handler\Locator\HandlerLocator;
use Psr\Container\ContainerInterface;
class ContainerCommandHandlerLocator implements HandlerLocator
{
public function __construct(
private readonly ContainerInterface $container
) {
}
public function getHandlerForCommand($commandName)
{
$commandHandler = substr($commandName, 0, -\strlen('Request'));
if (!$this->container->has($commandHandler)) {
throw MissingHandlerException::forCommand($commandName);
}
return $this->container->get($commandHandler);
}
}

View File

@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace App\Application\UseCase;
class HealthCheck
{
public function handle(HealthCheckRequest $request): string
{
return $request->test;
}
}

View File

@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace App\Application\UseCase;
readonly class HealthCheckRequest
{
public function __construct(
public string $test,
) {
}
}

View File

@ -1,15 +0,0 @@
<?php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class HealthcheckController
{
#[Route('/', name: 'healthcheck')]
public function healthcheck(): Response
{
return new Response("ok");
}
}

0
src/Domain/Entity/.gitignore vendored Normal file
View File

View File

View File

@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace App\Infrastructure\CommandBus;
interface CommandBusCaller
{
public function callCommandBus(object $request): mixed;
}

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace App\Infrastructure\CommandBus;
use League\Tactician\CommandBus;
readonly class UnmanagedErrorsCommandBusCaller implements CommandBusCaller
{
public function __construct(
private CommandBus $commandBus
) {
}
public function callCommandBus(object $request): mixed
{
return $this->commandBus->handle($request);
}
}

0
src/Infrastructure/Console/.gitignore vendored Normal file
View File

View File

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace App\Infrastructure\Controller;
use App\Infrastructure\CommandBus\CommandBusCaller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
abstract class AController extends AbstractController
{
public function __construct(
private readonly CommandBusCaller $commandBusCaller,
) {
}
protected function callCommandBus(object $request): mixed
{
return $this->commandBusCaller->callCommandBus($request);
}
}

View File

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace App\Infrastructure\Controller;
use App\Application\UseCase\HealthCheckRequest;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;
class HealthCheck extends AController
{
#[Route('/', name: 'healthcheck')]
public function __invoke(): JsonResponse
{
$healthcheck = $this->callCommandBus(new HealthCheckRequest('ok'));
return new JsonResponse(
[
'status' => $healthcheck,
]
);
}
}

View File

View File

View File

@ -38,6 +38,18 @@
".php-cs-fixer.dist.php" ".php-cs-fixer.dist.php"
] ]
}, },
"league/tactician-bundle": {
"version": "1.5",
"recipe": {
"repo": "github.com/symfony/recipes-contrib",
"branch": "main",
"version": "1.0",
"ref": "222c3d39d38378bc6a9790a0b5baf841ba6679b9"
},
"files": [
"config/packages/league_tactician.yaml"
]
},
"phpstan/phpstan": { "phpstan/phpstan": {
"version": "2.1", "version": "2.1",
"recipe": { "recipe": {