Better code organization

This commit is contained in:
julien
2026-03-09 17:33:26 +01:00
parent 99a1f2c5ab
commit 898b4b75c8
3 changed files with 309 additions and 108 deletions

View File

@@ -4,116 +4,11 @@ declare(strict_types=1);
require __DIR__ . '/../vendor/autoload.php';
use Dotenv\Dotenv;
use Slim\Factory\AppFactory;
use Slim\Views\TwigMiddleware;
use Slim\Views\Twig;
use Slim\Csrf\Guard;
use Medoo\Medoo;
use App\Controllers\PostController;
use App\Repositories\PostRepository;
use App\Services\HtmlSanitizer;
use App\Services\HtmlPurifierFactory;
use App\Services\CsrfExtension;
use App\Database\Migrator;
use App\Bootstrap;
use App\Routes;
use App\Config;
// ============================================
// Démarrer la session PHP
// ============================================
session_start();
// ============================================
// Vérifier les répertoires
// ============================================
Bootstrap::checkDirectories();
// ============================================
// Charger les variables d'environnement
// ============================================
$dotenv = Dotenv::createImmutable(__DIR__ . '/..');
$dotenv->load();
// ============================================
// Configuration
// ============================================
$env = $_ENV['APP_ENV'] ?? 'production';
$isDev = strtolower($env) === 'development';
// ============================================
// Initialisation de l'application Slim
// ============================================
$app = AppFactory::create();
$responseFactory = $app->getResponseFactory();
// ============================================
// Initialisation des services
// ============================================
// CSRF Guard (middleware)
$csrf = new Guard($responseFactory);
// Twig
$twig = Twig::create(
__DIR__ . '/../views',
['cache' => Config::getTwigCache($isDev)]
);
// Ajouter l'extension CSRF à Twig
$twig->addExtension(new CsrfExtension($csrf));
// Medoo (SQLite)
$dbFile = Config::getDatabasePath();
$db = new Medoo([
'type' => 'sqlite',
'database' => $dbFile,
]);
// Exécuter les migrations
Migrator::run($db);
// HtmlPurifier (créé via la factory)
$htmlPurifierCacheDir = __DIR__ . '/../var/cache/htmlpurifier';
$htmlPurifier = HtmlPurifierFactory::create($htmlPurifierCacheDir);
// HtmlSanitizer (reçoit HTMLPurifier injecté)
$htmlSanitizer = new HtmlSanitizer($htmlPurifier);
// PostRepository
$postRepository = new PostRepository($db);
// ============================================
// Middleware
// ============================================
$app->addBodyParsingMiddleware();
$app->add(TwigMiddleware::create($app, $twig));
// Enregistrer le middleware CSRF pour toutes les routes
$app->add($csrf);
// ============================================
// Routes
// ============================================
$controller = new PostController($twig, $postRepository, $htmlSanitizer);
Routes::register($app, $controller);
// ============================================
// Error Handling
// ============================================
$errorMiddleware = $app->addErrorMiddleware($isDev, $isDev, $isDev);
// ============================================
// Run
// ============================================
// Initialiser et exécuter l'application
$app = Bootstrap::create()->initialize();
$app->run();

View File

@@ -4,9 +4,69 @@ declare(strict_types=1);
namespace App;
use Dotenv\Dotenv;
use Slim\App;
use Slim\Views\TwigMiddleware;
/**
* Classe d'amorçage de l'application.
* Orchestre l'initialisation complète de l'application Slim.
*/
final class Bootstrap
{
public static function checkDirectories(): void
private Container $container;
private App $app;
/**
* Crée une nouvelle instance de Bootstrap.
*/
public static function create(): self
{
return new self();
}
private function __construct()
{
$this->container = new Container();
}
/**
* Initialise complètement l'application et retourne l'instance Slim.
*
* @return App L'application Slim prête à être exécutée
*/
public function initialize(): App
{
// 1. Vérifier les répertoires
$this->checkDirectories();
// 2. Charger les variables d'environnement
$this->loadEnvironment();
// 3. Initialiser l'application Slim
$this->app = $this->container->get('app');
// 4. Exécuter les migrations
$this->container->get('migrator');
// 5. Enregistrer les middlewares
$this->registerMiddlewares();
// 6. Enregistrer les routes
$this->registerRoutes();
// 7. Configurer la gestion des erreurs
$this->configureErrorHandling();
return $this->app;
}
/**
* Vérifie et crée les répertoires nécessaires.
*
* @throws \RuntimeException Si un répertoire ne peut pas être créé
*/
private function checkDirectories(): void
{
$dirs = [
__DIR__ . '/../var/cache/twig',
@@ -23,4 +83,63 @@ final class Bootstrap
}
}
}
/**
* Charge les variables d'environnement depuis le fichier .env
*/
private function loadEnvironment(): void
{
$dotenv = Dotenv::createImmutable(__DIR__ . '/..');
$dotenv->load();
}
/**
* Enregistre tous les middlewares de l'application.
*/
private function registerMiddlewares(): void
{
// Middleware de parsing du body
$this->app->addBodyParsingMiddleware();
// Twig et extension CSRF
$twig = $this->container->get('twig');
$csrfExtension = $this->container->get('csrfExtension');
$twig->addExtension($csrfExtension);
$this->app->add(TwigMiddleware::create($this->app, $twig));
// CSRF Guard
$csrf = $this->container->get('csrf');
$this->app->add($csrf);
}
/**
* Enregistre toutes les routes de l'application.
*/
private function registerRoutes(): void
{
$controller = $this->container->get('postController');
Routes::register($this->app, $controller);
}
/**
* Configure la gestion des erreurs en fonction de l'environnement.
*/
private function configureErrorHandling(): void
{
$config = $this->container->get('config');
$isDev = $config['isDev'];
$this->app->addErrorMiddleware($isDev, $isDev, $isDev);
}
/**
* Retourne le conteneur de services.
* Utile pour accéder aux services en dehors du Bootstrap.
*
* @return Container
*/
public function getContainer(): Container
{
return $this->container;
}
}

187
src/Container.php Normal file
View File

@@ -0,0 +1,187 @@
<?php
declare(strict_types=1);
namespace App;
use Psr\Container\ContainerInterface;
use Slim\App;
use Slim\Factory\AppFactory;
use Slim\Views\Twig;
use Slim\Csrf\Guard;
use Medoo\Medoo;
use App\Controllers\PostController;
use App\Repositories\PostRepository;
use App\Services\HtmlSanitizer;
use App\Services\HtmlPurifierFactory;
use App\Services\CsrfExtension;
use App\Database\Migrator;
/**
* Conteneur de services pour l'injection de dépendances.
* Gère l'instanciation et la mise en cache de tous les services.
*/
final class Container implements ContainerInterface
{
/** @var array<string, mixed> Services instanciés et en cache */
private array $services = [];
/** @var array<string, callable> Factories pour créer les services */
private array $factories = [];
public function __construct()
{
$this->registerFactories();
}
/**
* Enregistre toutes les factories de services.
*/
private function registerFactories(): void
{
// ============================================
// Configuration
// ============================================
$this->factories['config'] = function (): array {
return [
'env' => $_ENV['APP_ENV'] ?? 'production',
'isDev' => strtolower($_ENV['APP_ENV'] ?? 'production') === 'development',
];
};
// ============================================
// Application Slim
// ============================================
$this->factories['app'] = function (): App {
return AppFactory::create();
};
// ============================================
// Base de données
// ============================================
$this->factories['database'] = function (): Medoo {
return new Medoo([
'type' => 'sqlite',
'database' => Config::getDatabasePath(),
]);
};
// ============================================
// Migrations
// ============================================
$this->factories['migrator'] = function (): Migrator {
$db = $this->get('database');
Migrator::run($db);
return new Migrator();
};
// ============================================
// Twig (Template Engine)
// ============================================
$this->factories['twig'] = function (): Twig {
$config = $this->get('config');
return Twig::create(
__DIR__ . '/../views',
['cache' => Config::getTwigCache($config['isDev'])]
);
};
// ============================================
// CSRF Guard
// ============================================
$this->factories['csrf'] = function (): Guard {
$app = $this->get('app');
return new Guard($app->getResponseFactory());
};
// ============================================
// CSRF Extension pour Twig
// ============================================
$this->factories['csrfExtension'] = function (): CsrfExtension {
$csrf = $this->get('csrf');
return new CsrfExtension($csrf);
};
// ============================================
// HTML Purifier
// ============================================
$this->factories['htmlPurifier'] = function () {
$cacheDir = __DIR__ . '/../var/cache/htmlpurifier';
return HtmlPurifierFactory::create($cacheDir);
};
// ============================================
// HTML Sanitizer
// ============================================
$this->factories['htmlSanitizer'] = function (): HtmlSanitizer {
$htmlPurifier = $this->get('htmlPurifier');
return new HtmlSanitizer($htmlPurifier);
};
// ============================================
// Repositories
// ============================================
$this->factories['postRepository'] = function (): PostRepository {
$db = $this->get('database');
return new PostRepository($db);
};
// ============================================
// Controllers
// ============================================
$this->factories['postController'] = function (): PostController {
return new PostController(
$this->get('twig'),
$this->get('postRepository'),
$this->get('htmlSanitizer')
);
};
}
/**
* Récupère un service du conteneur.
* Les services sont instanciés une seule fois et mis en cache.
*
* @param string $id Identifiant du service
* @return mixed
* @throws \Exception Si le service n'existe pas
*/
public function get(string $id): mixed
{
// Retourner le service en cache s'il existe
if (isset($this->services[$id])) {
return $this->services[$id];
}
// Vérifier que la factory existe
if (!isset($this->factories[$id])) {
throw new \Exception("Service '{$id}' not found in container");
}
// Créer et mettre en cache le service
$this->services[$id] = $this->factories[$id]();
return $this->services[$id];
}
/**
* Vérifie si un service existe dans le conteneur.
*
* @param string $id Identifiant du service
* @return bool
*/
public function has(string $id): bool
{
return isset($this->factories[$id]) || isset($this->services[$id]);
}
}