Added doc

This commit is contained in:
julien
2026-03-09 10:15:08 +01:00
parent 60bd8178e8
commit 393561f6b0
4 changed files with 155 additions and 36 deletions

View File

@@ -10,16 +10,27 @@ use Slim\Views\TwigMiddleware;
use Twig\Loader\FilesystemLoader; use Twig\Loader\FilesystemLoader;
use Medoo\Medoo; use Medoo\Medoo;
use Dotenv\Dotenv; use Dotenv\Dotenv;
use Throwable;
// Charger .env // Charger .env (tolérant l'absence du fichier)
$dotenv = Dotenv::createImmutable(__DIR__ . '/../'); $dotenv = Dotenv::createImmutable(__DIR__ . '/../');
$dotenv->safeLoad(); // safeLoad pour tolérer l'absence du fichier en dev $dotenv->safeLoad();
// Environnement // Configuration centralisée (valeurs raisonnables par défaut)
$appEnv = strtolower($_ENV['APP_ENV'] ?? $_SERVER['APP_ENV'] ?? 'production'); $config = [
$isDebug = ($appEnv === 'development' || $appEnv === 'dev'); '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) { if ($isDebug) {
ini_set('display_errors', '1'); ini_set('display_errors', '1');
ini_set('display_startup_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 $dbFile = $config['db']['file'];
{ if (!file_exists($dbFile)) {
if (!file_exists($path)) { if (!is_dir(dirname($dbFile))) {
if (!is_dir(dirname($path))) { mkdir(dirname($dbFile), 0755, true);
mkdir(dirname($path), 0755, true);
} }
touch($path); touch($dbFile);
chmod($path, 0664); chmod($dbFile, $config['db']['file_mode']);
} }
}
$dbFile = __DIR__ . '/../database/app.sqlite';
ensureDatabaseFile($dbFile);
// ------------------------- // Options Medoo (SQLite)
// Instancier Medoo (SQLite)
// -------------------------
$medooOptions = [ $medooOptions = [
'database_type' => 'sqlite', 'database_type' => 'sqlite',
'database_file' => $dbFile, 'database_file' => $dbFile,
'error' => PDO::ERRMODE_EXCEPTION, 'database_name' => $dbFile, // nécessaire pour certaines versions de Medoo
'charset' => 'utf8', 'charset' => 'utf8',
]; ];
if (!isset($medooOptions['database_name'])) { // Instancier Medoo
$medooOptions['database_name'] = $medooOptions['database_file'];
}
$database = new Medoo($medooOptions); $database = new Medoo($medooOptions);
// Créer la table si nécessaire (schéma minimal) // Créer la table si nécessaire (schéma minimal)
@@ -74,27 +77,42 @@ SQL
// ------------------------- // -------------------------
// Services (container simple) // Services (container simple)
// ------------------------- // -------------------------
/** @var array{view: Twig, postRepository: \App\Repositories\PostRepositoryMedoo} $container */
$container = []; $container = [];
/**
* 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 = [];
// Vue Twig // Vue Twig
$container['view'] = new Twig(new FilesystemLoader(__DIR__ . '/../views'), ['cache' => false]); $services['view'] = new Twig(new FilesystemLoader(__DIR__ . '/../views'), ['cache' => $config['twig']['cache']]);
// Repository Post (Medoo) // Repository Post (Medoo)
$container['postRepository'] = new App\Repositories\PostRepositoryMedoo($database); $services['postRepository'] = new App\Repositories\PostRepositoryMedoo($database);
return $services;
})($config, $database);
// ------------------------- // -------------------------
// Slim app // Slim app
// ------------------------- // -------------------------
$app = AppFactory::create(); $app = AppFactory::create();
// Configurer Error Middleware selon l'environnement // Error middleware
$errorMiddleware = $app->addErrorMiddleware($isDebug, $isDebug, $isDebug); $errorMiddleware = $app->addErrorMiddleware($isDebug, $isDebug, $isDebug);
// Optionnel : personnaliser le rendu d'erreur en production pour éviter fuite d'info
if (!$isDebug) { if (!$isDebug) {
$errorHandler = $errorMiddleware->getDefaultErrorHandler(); $errorHandler = $errorMiddleware->getDefaultErrorHandler();
$errorHandler->registerErrorRenderer('text/html', function () { // Renderer générique en production
// message générique sans trace $errorHandler->registerErrorRenderer('text/html', function (Throwable $exception, bool $displayErrorDetails) {
return '<html><head><meta charset="utf-8"><title>Erreur</title></head><body><h1>Erreur serveur</h1><p>Une erreur est survenue. Veuillez réessayer plus tard.</p></body></html>'; return '<html><head><meta charset="utf-8"><title>Erreur</title></head><body><h1>Erreur serveur</h1><p>Une erreur est survenue. Veuillez réessayer plus tard.</p></body></html>';
}); });
} }
@@ -103,7 +121,8 @@ if (!$isDebug) {
$app->addBodyParsingMiddleware(); $app->addBodyParsingMiddleware();
$app->add(TwigMiddleware::create($app, $container['view'])); $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); (require __DIR__ . '/../src/Routes/web.php')($app, $container);
// Lancer l'app
$app->run(); $app->run();

View File

@@ -9,6 +9,9 @@ use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Views\Twig; use Slim\Views\Twig;
use App\Repositories\PostRepositoryMedoo as PostRepository; use App\Repositories\PostRepositoryMedoo as PostRepository;
/**
* Contrôleur pour les posts.
*/
class PostController class PostController
{ {
private Twig $view; private Twig $view;
@@ -20,18 +23,40 @@ class PostController
$this->repo = $repo; $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 public function index(Request $req, Response $res): Response
{ {
$posts = $this->repo->allDesc(); $posts = $this->repo->allDesc();
return $this->view->render($res, 'pages/home.twig', ['posts' => $posts]); 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 public function admin(Request $req, Response $res): Response
{ {
$posts = $this->repo->allDesc(); $posts = $this->repo->allDesc();
return $this->view->render($res, 'pages/admin.twig', ['posts' => $posts]); 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 public function form(Request $req, Response $res, array $args): Response
{ {
$id = (int)($args['id'] ?? 0); $id = (int)($args['id'] ?? 0);
@@ -40,23 +65,74 @@ class PostController
return $this->view->render($res, 'pages/post_form.twig', ['post' => $post, 'action' => $action]); 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 public function create(Request $req, Response $res): Response
{ {
$data = $req->getParsedBody(); $data = $this->sanitize($req->getParsedBody());
$this->repo->create($data); $this->repo->create($data);
return $res->withHeader('Location', '/admin')->withStatus(302); 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 public function update(Request $req, Response $res, array $args): Response
{ {
$id = (int)$args['id']; $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); 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 public function delete(Request $req, Response $res, array $args): Response
{ {
$this->repo->delete((int)$args['id']); $this->repo->delete((int)$args['id']);
return $res->withHeader('Location', '/admin')->withStatus(302); 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,
];
}
} }

View File

@@ -25,7 +25,13 @@ class PostRepositoryMedoo
public function allDesc(): array public function allDesc(): array
{ {
$rows = $this->db->select('post', ['id', 'title', 'content'], ['ORDER' => ['id' => 'DESC']]); $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 public function create(array $data): int
{ {
$this->db->insert('post', [ $this->db->insert('post', [
@@ -55,6 +65,11 @@ class PostRepositoryMedoo
return (int)$this->db->id(); 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 public function update(int $id, array $data): void
{ {
$this->db->update('post', [ $this->db->update('post', [
@@ -63,6 +78,10 @@ class PostRepositoryMedoo
], ['id' => $id]); ], ['id' => $id]);
} }
/**
* @param int $id
* @return void
*/
public function delete(int $id): void public function delete(int $id): void
{ {
$this->db->delete('post', ['id' => $id]); $this->db->delete('post', ['id' => $id]);

View File

@@ -7,6 +7,11 @@ use Slim\Views\Twig;
use App\Repositories\PostRepositoryMedoo; use App\Repositories\PostRepositoryMedoo;
use App\Controllers\PostController; use App\Controllers\PostController;
/**
* @param App $app
* @param array{view:Twig, postRepository:PostRepositoryMedoo} $container
* @return void
*/
return function (App $app, array $container): void { return function (App $app, array $container): void {
/** @var Twig $view */ /** @var Twig $view */
$view = $container['view']; $view = $container['view'];