234 lines
7.1 KiB
PHP
234 lines
7.1 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Shared;
|
|
|
|
use App\Post\Infrastructure\TwigPostExtension;
|
|
use App\Shared\Database\DatabaseNotProvisionedException;
|
|
use App\Shared\Database\DatabaseReadiness;
|
|
use App\Shared\Extension\AppExtension;
|
|
use App\Shared\Extension\CsrfExtension;
|
|
use App\Shared\Extension\SessionExtension;
|
|
use DI\ContainerBuilder;
|
|
use Dotenv\Dotenv;
|
|
use Dotenv\Exception\InvalidPathException;
|
|
use PDO;
|
|
use Psr\Container\ContainerInterface;
|
|
use Psr\Http\Message\ResponseInterface;
|
|
use Psr\Http\Message\ServerRequestInterface;
|
|
use Psr\Http\Server\RequestHandlerInterface;
|
|
use Psr\Log\LoggerInterface;
|
|
use Slim\App;
|
|
use Slim\Csrf\Guard;
|
|
use Slim\Exception\HttpException;
|
|
use Slim\Factory\AppFactory;
|
|
use Slim\Views\Twig;
|
|
use Slim\Views\TwigMiddleware;
|
|
use Throwable;
|
|
|
|
final class Bootstrap
|
|
{
|
|
private ?ContainerInterface $container = null;
|
|
private ?App $app = null;
|
|
|
|
public static function create(): self
|
|
{
|
|
return new self();
|
|
}
|
|
|
|
private function __construct()
|
|
{
|
|
}
|
|
|
|
public function initialize(): App
|
|
{
|
|
$this->initializeInfrastructure();
|
|
|
|
return $this->createHttpApp();
|
|
}
|
|
|
|
public function initializeInfrastructure(): ContainerInterface
|
|
{
|
|
if ($this->container !== null) {
|
|
return $this->container;
|
|
}
|
|
|
|
$this->checkDirectories();
|
|
$this->checkExtensions();
|
|
$this->loadEnvironment();
|
|
$this->buildContainer();
|
|
|
|
return $this->container;
|
|
}
|
|
|
|
public function createHttpApp(): App
|
|
{
|
|
if ($this->app !== null) {
|
|
return $this->app;
|
|
}
|
|
|
|
$container = $this->initializeInfrastructure();
|
|
$this->app = AppFactory::createFromContainer($container);
|
|
|
|
$this->registerMiddlewares();
|
|
$this->registerRoutes();
|
|
$this->configureErrorHandling();
|
|
|
|
return $this->app;
|
|
}
|
|
|
|
public function getContainer(): ContainerInterface
|
|
{
|
|
return $this->initializeInfrastructure();
|
|
}
|
|
|
|
private function buildContainer(): void
|
|
{
|
|
$isDev = strtolower($_ENV['APP_ENV'] ?? 'production') === 'development';
|
|
$builder = new ContainerBuilder();
|
|
$builder->addDefinitions(__DIR__ . '/../../config/container.php');
|
|
|
|
if (!$isDev) {
|
|
$builder->enableCompilation(__DIR__ . '/../../var/cache/di');
|
|
}
|
|
|
|
$this->container = $builder->build();
|
|
}
|
|
|
|
private function checkDirectories(): void
|
|
{
|
|
$dirs = [
|
|
__DIR__.'/../../var/cache/twig',
|
|
__DIR__.'/../../var/cache/htmlpurifier',
|
|
__DIR__.'/../../var/cache/di',
|
|
__DIR__.'/../../var/logs',
|
|
__DIR__.'/../../database',
|
|
__DIR__.'/../../public/media',
|
|
];
|
|
|
|
foreach ($dirs as $dir) {
|
|
if (!is_dir($dir) && !@mkdir($dir, 0755, true)) {
|
|
throw new \RuntimeException("Impossible de créer le répertoire : {$dir}");
|
|
}
|
|
}
|
|
}
|
|
|
|
private function checkExtensions(): void
|
|
{
|
|
if (!function_exists('imagewebp')) {
|
|
throw new \RuntimeException(
|
|
'L\'extension PHP GD avec le support WebP est requise. ' .
|
|
'Installez le paquet php-gd (ex: apt install php-gd) puis redémarrez PHP.'
|
|
);
|
|
}
|
|
}
|
|
|
|
private function loadEnvironment(): void
|
|
{
|
|
$rootDir = dirname(__DIR__, 2);
|
|
$envPath = $rootDir . '/.env';
|
|
|
|
try {
|
|
$dotenv = Dotenv::createImmutable($rootDir);
|
|
$dotenv->load();
|
|
} catch (InvalidPathException $exception) {
|
|
throw new \RuntimeException(
|
|
sprintf(
|
|
"Fichier .env introuvable a la racine du projet (%s). Copiez .env.example vers .env avant de demarrer l'application.",
|
|
$envPath
|
|
),
|
|
previous: $exception,
|
|
);
|
|
}
|
|
|
|
$dotenv->required(['APP_URL', 'ADMIN_USERNAME', 'ADMIN_EMAIL', 'ADMIN_PASSWORD']);
|
|
|
|
date_default_timezone_set($_ENV['TIMEZONE'] ?? 'UTC');
|
|
|
|
$isDev = strtolower($_ENV['APP_ENV'] ?? 'production') === 'development';
|
|
|
|
if (!$isDev && ($_ENV['ADMIN_PASSWORD'] ?? '') === 'changeme123') {
|
|
throw new \RuntimeException(
|
|
'ADMIN_PASSWORD doit être changé avant de démarrer en production.'
|
|
);
|
|
}
|
|
}
|
|
|
|
private function registerMiddlewares(): void
|
|
{
|
|
$this->app->addBodyParsingMiddleware();
|
|
|
|
$twig = $this->container->get(Twig::class);
|
|
$twig->addExtension($this->container->get(AppExtension::class));
|
|
$twig->addExtension($this->container->get(SessionExtension::class));
|
|
$twig->addExtension($this->container->get(TwigPostExtension::class));
|
|
|
|
$this->app->add(TwigMiddleware::create($this->app, $twig));
|
|
|
|
$guard = new Guard($this->app->getResponseFactory());
|
|
$guard->setPersistentTokenMode(true);
|
|
$twig->addExtension(new CsrfExtension($guard));
|
|
$this->app->add($guard);
|
|
|
|
$container = $this->container;
|
|
|
|
$this->app->add(function (
|
|
ServerRequestInterface $request,
|
|
RequestHandlerInterface $handler,
|
|
) use ($container): ResponseInterface {
|
|
DatabaseReadiness::assertProvisioned($container->get(PDO::class));
|
|
|
|
return $handler->handle($request);
|
|
});
|
|
}
|
|
|
|
private function registerRoutes(): void
|
|
{
|
|
Routes::register($this->app);
|
|
}
|
|
|
|
private function configureErrorHandling(): void
|
|
{
|
|
$isDev = strtolower($_ENV['APP_ENV'] ?? 'production') === 'development';
|
|
$logger = $this->container->get(LoggerInterface::class);
|
|
$errorHandler = $this->app->addErrorMiddleware($isDev, true, true, $logger);
|
|
$app = $this->app;
|
|
$container = $this->container;
|
|
|
|
$errorHandler->setDefaultErrorHandler(
|
|
function (
|
|
ServerRequestInterface $request,
|
|
Throwable $exception,
|
|
bool $displayErrorDetails,
|
|
bool $logErrors,
|
|
bool $logErrorDetails,
|
|
) use ($isDev, $app, $container): ResponseInterface {
|
|
if ($isDev && !$exception instanceof DatabaseNotProvisionedException) {
|
|
throw $exception;
|
|
}
|
|
|
|
$statusCode = 500;
|
|
$message = 'Une erreur inattendue s\'est produite.';
|
|
|
|
if ($exception instanceof DatabaseNotProvisionedException) {
|
|
$statusCode = 503;
|
|
$message = $exception->getMessage();
|
|
} elseif ($exception instanceof HttpException) {
|
|
$statusCode = $exception->getCode() ?: 500;
|
|
$message = $statusCode === 404
|
|
? 'La page demandée est introuvable.'
|
|
: $message;
|
|
}
|
|
|
|
$response = $app->getResponseFactory()->createResponse($statusCode);
|
|
$twig = $container->get(Twig::class);
|
|
|
|
return $twig->render($response, 'pages/error.twig', [
|
|
'status' => $statusCode,
|
|
'message' => $message,
|
|
]);
|
|
}
|
|
);
|
|
}
|
|
}
|