autowire(AuthService::class), PostServiceInterface::class => autowire(PostService::class), UserServiceInterface::class => autowire(UserService::class), CategoryServiceInterface::class => autowire(CategoryService::class), CategoryRepositoryInterface::class => autowire(CategoryRepository::class), MediaRepositoryInterface::class => autowire(MediaRepository::class), PostRepositoryInterface::class => autowire(PostRepository::class), UserRepositoryInterface::class => autowire(UserRepository::class), LoginAttemptRepositoryInterface::class => autowire(LoginAttemptRepository::class), PasswordResetRepositoryInterface::class => autowire(PasswordResetRepository::class), PasswordResetServiceInterface::class => autowire(PasswordResetService::class), FlashServiceInterface::class => autowire(FlashService::class), SessionManagerInterface::class => autowire(SessionManager::class), HtmlSanitizerInterface::class => autowire(HtmlSanitizer::class), // ── Infrastructure ──────────────────────────────────────────────────────── LoggerInterface::class => factory(function (): LoggerInterface { $isDev = strtolower($_ENV['APP_ENV'] ?? 'production') === 'development'; $logger = new Logger('slim-blog'); $level = $isDev ? Level::Debug : Level::Warning; $logger->pushHandler( new StreamHandler(dirname(__DIR__) . '/var/logs/app.log', $level) ); return $logger; }), PDO::class => factory(function (): PDO { $pdo = new PDO('sqlite:' . Config::getDatabasePath(), options: [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]); // strip_tags() PHP exposée comme fonction SQLite pour les triggers FTS5 $pdo->sqliteCreateFunction('strip_tags', 'strip_tags', 1); $pdo->exec('PRAGMA journal_mode=WAL'); // Attend jusqu'à 3s avant d'échouer sur une contention en écriture $pdo->exec('PRAGMA busy_timeout=3000'); // Réduit les fsync sans sacrifier la cohérence en WAL mode $pdo->exec('PRAGMA synchronous=NORMAL'); // Active l'application réelle des contraintes de clé étrangère. // Sans ce pragma, les ON DELETE SET NULL / CASCADE déclarés dans les // migrations sont enregistrés dans le schéma mais silencieusement ignorés // à l'exécution — SQLite désactive les FK par défaut pour la compatibilité. $pdo->exec('PRAGMA foreign_keys=ON'); return $pdo; }), Twig::class => factory(function (): Twig { $isDev = strtolower($_ENV['APP_ENV'] ?? 'production') === 'development'; return Twig::create( dirname(__DIR__) . '/views', ['cache' => Config::getTwigCache($isDev)], ); }), \HTMLPurifier::class => factory(function (): \HTMLPurifier { return HtmlPurifierFactory::create(dirname(__DIR__) . '/var/cache/htmlpurifier'); }), // ── Services nécessitant une configuration .env ─────────────────────────── MailServiceInterface::class => factory(function (Twig $twig): MailServiceInterface { return new MailService( twig: $twig, host: $_ENV['MAIL_HOST'] ?? '', port: (int) ($_ENV['MAIL_PORT'] ?? 587), username: $_ENV['MAIL_USERNAME'] ?? '', password: $_ENV['MAIL_PASSWORD'] ?? '', encryption: strtolower($_ENV['MAIL_ENCRYPTION'] ?? 'tls'), from: $_ENV['MAIL_FROM'] ?? '', fromName: $_ENV['MAIL_FROM_NAME'] ?? 'Slim Blog', ); }), MediaServiceInterface::class => factory( function (MediaRepositoryInterface $mediaRepository): MediaServiceInterface { return new MediaService( mediaRepository: $mediaRepository, uploadDir: dirname(__DIR__) . '/public/media', uploadUrl: '/media', maxSize: (int) ($_ENV['UPLOAD_MAX_SIZE'] ?? 5 * 1024 * 1024), ); } ), // ── Contrôleurs nécessitant des paramètres scalaires ───────────────────── RssController::class => factory( function (PostServiceInterface $postService): RssController { return new RssController( $postService, rtrim($_ENV['APP_URL'] ?? 'http://localhost', '/'), $_ENV['APP_NAME'] ?? 'Slim Blog', ); } ), PasswordResetController::class => factory( function ( Twig $twig, PasswordResetServiceInterface $passwordResetService, AuthServiceInterface $authService, FlashServiceInterface $flash, ClientIpResolver $clientIpResolver, ): PasswordResetController { return new PasswordResetController( $twig, $passwordResetService, $authService, $flash, $clientIpResolver, rtrim($_ENV['APP_URL'] ?? 'http://localhost', '/'), ); } ), ClientIpResolver::class => factory(function (): ClientIpResolver { $trusted = array_filter(array_map('trim', explode(',', (string) ($_ENV['TRUSTED_PROXIES'] ?? '')))); return new ClientIpResolver($trusted); }), AppExtension::class => factory(function (): AppExtension { return new AppExtension(rtrim($_ENV['APP_URL'] ?? 'http://localhost', '/')); }), ];