first commit

This commit is contained in:
julien
2026-03-16 01:47:07 +01:00
commit 8f7e61bda0
185 changed files with 27731 additions and 0 deletions

213
src/Shared/Bootstrap.php Normal file
View File

@@ -0,0 +1,213 @@
<?php
declare(strict_types=1);
namespace App\Shared;
use App\Post\PostExtension;
use App\Shared\Database\Provisioner;
use App\Shared\Extension\AppExtension;
use App\Shared\Extension\CsrfExtension;
use App\Shared\Extension\SessionExtension;
use DI\ContainerBuilder;
use Dotenv\Dotenv;
use PDO;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
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();
$this->runAutoProvisioningIfEnabled();
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
{
$dotenv = Dotenv::createImmutable(__DIR__.'/../..');
$dotenv->load();
$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 runAutoProvisioningIfEnabled(): void
{
$flag = strtolower(trim((string) ($_ENV['APP_AUTO_PROVISION'] ?? '')));
$isDev = strtolower($_ENV['APP_ENV'] ?? 'production') === 'development';
$enabled = $flag !== ''
? in_array($flag, ['1', 'true', 'yes', 'on'], true)
: $isDev;
if (!$enabled) {
return;
}
Provisioner::run($this->container->get(PDO::class));
}
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(PostExtension::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);
}
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);
$errorHandler->setDefaultErrorHandler(
function (
ServerRequestInterface $request,
Throwable $exception,
bool $displayErrorDetails,
bool $logErrors,
bool $logErrorDetails,
) use ($isDev): ResponseInterface {
if ($isDev) {
throw $exception;
}
$statusCode = 500;
if ($exception instanceof HttpException) {
$statusCode = $exception->getCode() ?: 500;
}
$response = $this->app->getResponseFactory()->createResponse($statusCode);
$twig = $this->container->get(Twig::class);
return $twig->render($response, 'pages/error.twig', [
'status' => $statusCode,
'message' => $statusCode === 404
? 'La page demandée est introuvable.'
: 'Une erreur inattendue s\'est produite.',
]);
}
);
}
}