diff --git a/public/index.php b/public/index.php index ec86d01..86c4d1b 100644 --- a/public/index.php +++ b/public/index.php @@ -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(); diff --git a/src/Bootstrap.php b/src/Bootstrap.php index 06ee874..d60515a 100644 --- a/src/Bootstrap.php +++ b/src/Bootstrap.php @@ -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; + } } diff --git a/src/Container.php b/src/Container.php new file mode 100644 index 0000000..82ddaa7 --- /dev/null +++ b/src/Container.php @@ -0,0 +1,187 @@ + Services instanciés et en cache */ + private array $services = []; + + /** @var array 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]); + } +}