Added doc
This commit is contained in:
@@ -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);
|
|
||||||
chmod($path, 0664);
|
|
||||||
}
|
}
|
||||||
|
touch($dbFile);
|
||||||
|
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 = [];
|
||||||
|
|
||||||
// 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)
|
// Vue Twig
|
||||||
$container['postRepository'] = new App\Repositories\PostRepositoryMedoo($database);
|
$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
|
// 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();
|
||||||
|
|||||||
@@ -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,
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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]);
|
||||||
|
|||||||
@@ -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'];
|
||||||
|
|||||||
Reference in New Issue
Block a user