From 393561f6b0623d3fe9851f3063055f744092877e Mon Sep 17 00:00:00 2001 From: julien Date: Mon, 9 Mar 2026 10:15:08 +0100 Subject: [PATCH] Added doc --- public/index.php | 85 +++++++++++++++--------- src/Controllers/PostController.php | 80 +++++++++++++++++++++- src/Repositories/PostRepositoryMedoo.php | 21 +++++- src/Routes/web.php | 5 ++ 4 files changed, 155 insertions(+), 36 deletions(-) diff --git a/public/index.php b/public/index.php index 6efd9e9..ac61ba0 100644 --- a/public/index.php +++ b/public/index.php @@ -10,16 +10,27 @@ use Slim\Views\TwigMiddleware; use Twig\Loader\FilesystemLoader; use Medoo\Medoo; use Dotenv\Dotenv; +use Throwable; -// Charger .env +// Charger .env (tolérant l'absence du fichier) $dotenv = Dotenv::createImmutable(__DIR__ . '/../'); -$dotenv->safeLoad(); // safeLoad pour tolérer l'absence du fichier en dev +$dotenv->safeLoad(); -// Environnement -$appEnv = strtolower($_ENV['APP_ENV'] ?? $_SERVER['APP_ENV'] ?? 'production'); -$isDebug = ($appEnv === 'development' || $appEnv === 'dev'); +// Configuration centralisée (valeurs raisonnables par défaut) +$config = [ + 'env' => strtolower($_ENV['APP_ENV'] ?? $_SERVER['APP_ENV'] ?? 'production'), + 'twig' => [ + 'cache' => false, // => mettre un chemin en production, ex: __DIR__ . '/../var/cache/twig' + ], + 'db' => [ + 'file' => __DIR__ . '/../database/app.sqlite', + 'file_mode' => 0664, + ], +]; -// Configuration PHP display errors selon l'environnement +$isDebug = ($config['env'] === 'development' || $config['env'] === 'dev'); + +// Affichage des erreurs selon l'environnement if ($isDebug) { ini_set('display_errors', '1'); ini_set('display_startup_errors', '1'); @@ -31,33 +42,25 @@ if ($isDebug) { } // ------------------------- -// DB SQLite + fichier +// Base de données (SQLite) // ------------------------- -function ensureDatabaseFile(string $path): void -{ - if (!file_exists($path)) { - if (!is_dir(dirname($path))) { - mkdir(dirname($path), 0755, true); - } - touch($path); - chmod($path, 0664); +$dbFile = $config['db']['file']; +if (!file_exists($dbFile)) { + if (!is_dir(dirname($dbFile))) { + mkdir(dirname($dbFile), 0755, true); } + touch($dbFile); + chmod($dbFile, $config['db']['file_mode']); } -$dbFile = __DIR__ . '/../database/app.sqlite'; -ensureDatabaseFile($dbFile); -// ------------------------- -// Instancier Medoo (SQLite) -// ------------------------- +// Options Medoo (SQLite) $medooOptions = [ 'database_type' => 'sqlite', 'database_file' => $dbFile, - 'error' => PDO::ERRMODE_EXCEPTION, + 'database_name' => $dbFile, // nécessaire pour certaines versions de Medoo 'charset' => 'utf8', ]; -if (!isset($medooOptions['database_name'])) { - $medooOptions['database_name'] = $medooOptions['database_file']; -} +// Instancier Medoo $database = new Medoo($medooOptions); // Créer la table si nécessaire (schéma minimal) @@ -74,27 +77,42 @@ SQL // ------------------------- // Services (container simple) // ------------------------- +/** @var array{view: Twig, postRepository: \App\Repositories\PostRepositoryMedoo} $container */ $container = []; -// Vue Twig -$container['view'] = new Twig(new FilesystemLoader(__DIR__ . '/../views'), ['cache' => false]); +/** + * Construire les services et les retourner dans le container. + * + * Reste modulaire : la fonction reste locale et conserve la variable $container = []. + * + * @param array $config + * @param Medoo $database + * @return array + */ +$container = (function (array $config, Medoo $database): array { + $services = []; -// Repository Post (Medoo) -$container['postRepository'] = new App\Repositories\PostRepositoryMedoo($database); + // Vue Twig + $services['view'] = new Twig(new FilesystemLoader(__DIR__ . '/../views'), ['cache' => $config['twig']['cache']]); + + // Repository Post (Medoo) + $services['postRepository'] = new App\Repositories\PostRepositoryMedoo($database); + + return $services; +})($config, $database); // ------------------------- // Slim app // ------------------------- $app = AppFactory::create(); -// Configurer Error Middleware selon l'environnement +// Error middleware $errorMiddleware = $app->addErrorMiddleware($isDebug, $isDebug, $isDebug); -// Optionnel : personnaliser le rendu d'erreur en production pour éviter fuite d'info if (!$isDebug) { $errorHandler = $errorMiddleware->getDefaultErrorHandler(); - $errorHandler->registerErrorRenderer('text/html', function () { - // message générique sans trace + // Renderer générique en production + $errorHandler->registerErrorRenderer('text/html', function (Throwable $exception, bool $displayErrorDetails) { return 'Erreur

Erreur serveur

Une erreur est survenue. Veuillez réessayer plus tard.

'; }); } @@ -103,7 +121,8 @@ if (!$isDebug) { $app->addBodyParsingMiddleware(); $app->add(TwigMiddleware::create($app, $container['view'])); -// Charger routes (web.php reçoit maintenant le container) +// Charger routes (web.php reçoit le container) (require __DIR__ . '/../src/Routes/web.php')($app, $container); +// Lancer l'app $app->run(); diff --git a/src/Controllers/PostController.php b/src/Controllers/PostController.php index 1ebe506..b0dfe61 100644 --- a/src/Controllers/PostController.php +++ b/src/Controllers/PostController.php @@ -9,6 +9,9 @@ use Psr\Http\Message\ServerRequestInterface as Request; use Slim\Views\Twig; use App\Repositories\PostRepositoryMedoo as PostRepository; +/** + * Contrôleur pour les posts. + */ class PostController { private Twig $view; @@ -20,18 +23,40 @@ class PostController $this->repo = $repo; } + /** + * Affiche la page d'accueil avec la liste des posts. + * + * @param Request $req + * @param Response $res + * @return Response + */ public function index(Request $req, Response $res): Response { $posts = $this->repo->allDesc(); return $this->view->render($res, 'pages/home.twig', ['posts' => $posts]); } + /** + * Affiche la page d'administration. + * + * @param Request $req + * @param Response $res + * @return Response + */ public function admin(Request $req, Response $res): Response { $posts = $this->repo->allDesc(); return $this->view->render($res, 'pages/admin.twig', ['posts' => $posts]); } + /** + * Formulaire de création / édition. + * + * @param Request $req + * @param Response $res + * @param array $args + * @return Response + */ public function form(Request $req, Response $res, array $args): Response { $id = (int)($args['id'] ?? 0); @@ -40,23 +65,74 @@ class PostController return $this->view->render($res, 'pages/post_form.twig', ['post' => $post, 'action' => $action]); } + /** + * Crée un nouvel article. + * + * @param Request $req + * @param Response $res + * @return Response + */ public function create(Request $req, Response $res): Response { - $data = $req->getParsedBody(); + $data = $this->sanitize($req->getParsedBody()); $this->repo->create($data); return $res->withHeader('Location', '/admin')->withStatus(302); } + /** + * Met à jour un article existant. + * + * @param Request $req + * @param Response $res + * @param array $args + * @return Response + */ public function update(Request $req, Response $res, array $args): Response { $id = (int)$args['id']; - $this->repo->update($id, $req->getParsedBody()); + $data = $this->sanitize($req->getParsedBody()); + $this->repo->update($id, $data); return $res->withHeader('Location', '/admin')->withStatus(302); } + /** + * Supprime un article. + * + * @param Request $req + * @param Response $res + * @param array $args + * @return Response + */ public function delete(Request $req, Response $res, array $args): Response { $this->repo->delete((int)$args['id']); return $res->withHeader('Location', '/admin')->withStatus(302); } + + /** + * Sanitize minimal des données entrantes : + * - cast string + * - trim + * - limiter la longueur raisonnablement (pour éviter insertion énorme) + * + * @param mixed $input + * @return array{title:string,content:string} + */ + private function sanitize($input): array + { + $title = isset($input['title']) ? (string)$input['title'] : ''; + $content = isset($input['content']) ? (string)$input['content'] : ''; + + $title = trim($title); + $content = trim($content); + + // Limites raisonnables (adaptables) + $title = mb_substr($title, 0, 255); + $content = mb_substr($content, 0, 65535); + + return [ + 'title' => $title, + 'content' => $content, + ]; + } } diff --git a/src/Repositories/PostRepositoryMedoo.php b/src/Repositories/PostRepositoryMedoo.php index 28f4398..b9412c6 100644 --- a/src/Repositories/PostRepositoryMedoo.php +++ b/src/Repositories/PostRepositoryMedoo.php @@ -25,7 +25,13 @@ class PostRepositoryMedoo public function allDesc(): array { $rows = $this->db->select('post', ['id', 'title', 'content'], ['ORDER' => ['id' => 'DESC']]); - return is_array($rows) ? $rows : []; + return is_array($rows) ? array_map(function ($r) { + return [ + 'id' => (int)($r['id'] ?? 0), + 'title' => (string)($r['title'] ?? ''), + 'content' => (string)($r['content'] ?? ''), + ]; + }, $rows) : []; } /** @@ -46,6 +52,10 @@ class PostRepositoryMedoo ]; } + /** + * @param array{title:string,content:string} $data + * @return int Inserted id + */ public function create(array $data): int { $this->db->insert('post', [ @@ -55,6 +65,11 @@ class PostRepositoryMedoo return (int)$this->db->id(); } + /** + * @param int $id + * @param array{title:string,content:string} $data + * @return void + */ public function update(int $id, array $data): void { $this->db->update('post', [ @@ -63,6 +78,10 @@ class PostRepositoryMedoo ], ['id' => $id]); } + /** + * @param int $id + * @return void + */ public function delete(int $id): void { $this->db->delete('post', ['id' => $id]); diff --git a/src/Routes/web.php b/src/Routes/web.php index de21c81..8882e26 100644 --- a/src/Routes/web.php +++ b/src/Routes/web.php @@ -7,6 +7,11 @@ use Slim\Views\Twig; use App\Repositories\PostRepositoryMedoo; use App\Controllers\PostController; +/** + * @param App $app + * @param array{view:Twig, postRepository:PostRepositoryMedoo} $container + * @return void + */ return function (App $app, array $container): void { /** @var Twig $view */ $view = $container['view'];