More MVC style
This commit is contained in:
173
public/index.php
173
public/index.php
@@ -5,185 +5,30 @@ declare(strict_types=1);
|
|||||||
require __DIR__ . '/../vendor/autoload.php';
|
require __DIR__ . '/../vendor/autoload.php';
|
||||||
|
|
||||||
use Slim\Factory\AppFactory;
|
use Slim\Factory\AppFactory;
|
||||||
use Slim\Views\Twig;
|
|
||||||
use Slim\Views\TwigMiddleware;
|
use Slim\Views\TwigMiddleware;
|
||||||
use Twig\Loader\FilesystemLoader;
|
|
||||||
use Medoo\Medoo;
|
|
||||||
use Dotenv\Dotenv;
|
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
/**
|
// Charger et créer les services centralisés
|
||||||
* Charger les variables d'environnement si présentes (tolérant l'absence du fichier).
|
$services = App\Factories\ServiceFactory::createServices();
|
||||||
*/
|
|
||||||
$dotenv = Dotenv::createImmutable(__DIR__ . '/../');
|
|
||||||
$dotenv->safeLoad();
|
|
||||||
|
|
||||||
/**
|
/** @var \Slim\Views\Twig $twig */
|
||||||
* Configuration centrale avec valeurs par défaut raisonnables.
|
$twig = $services['view'];
|
||||||
*
|
|
||||||
* - 'env' : environnement d'exécution ('production' par défaut)
|
|
||||||
* - 'twig.cache' : résolu plus bas à partir de TWIG_CACHE ou par défaut selon l'environnement
|
|
||||||
* - 'db.file' et 'db.file_mode' : fichier SQLite et permissions
|
|
||||||
*/
|
|
||||||
$config = [
|
|
||||||
'env' => strtolower((string) ($_ENV['APP_ENV'] ?? $_SERVER['APP_ENV'] ?? 'production')),
|
|
||||||
'twig' => [
|
|
||||||
'cache' => null,
|
|
||||||
],
|
|
||||||
'db' => [
|
|
||||||
'file' => __DIR__ . '/../database/app.sqlite',
|
|
||||||
'file_mode' => 0664,
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Résolution de la valeur twig.cache depuis la variable d'environnement TWIG_CACHE.
|
|
||||||
* Si TWIG_CACHE est vide : false en dev, chemin par défaut en production.
|
|
||||||
*/
|
|
||||||
$envTwigCache = $_ENV['TWIG_CACHE'] ?? $_SERVER['TWIG_CACHE'] ?? null;
|
|
||||||
if ($envTwigCache !== null && $envTwigCache !== '') {
|
|
||||||
$config['twig']['cache'] = (string) $envTwigCache;
|
|
||||||
} else {
|
|
||||||
$devEnvs = ['development', 'dev'];
|
|
||||||
$config['twig']['cache'] = in_array($config['env'], $devEnvs, true)
|
|
||||||
? false
|
|
||||||
: __DIR__ . '/../var/cache/twig';
|
|
||||||
}
|
|
||||||
|
|
||||||
$isDebug = in_array($config['env'], ['development', 'dev'], true);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Affichage et rapport d'erreurs selon l'environnement.
|
|
||||||
*/
|
|
||||||
if ($isDebug) {
|
|
||||||
ini_set('display_errors', '1');
|
|
||||||
ini_set('display_startup_errors', '1');
|
|
||||||
error_reporting(E_ALL);
|
|
||||||
} else {
|
|
||||||
ini_set('display_errors', '0');
|
|
||||||
ini_set('display_startup_errors', '0');
|
|
||||||
error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------
|
|
||||||
// Base de données (SQLite)
|
|
||||||
// -------------------------
|
|
||||||
|
|
||||||
$dbFile = $config['db']['file'];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Créer le fichier de base de données si nécessaire et appliquer les permissions.
|
|
||||||
*/
|
|
||||||
if (!file_exists($dbFile)) {
|
|
||||||
$dbDir = dirname($dbFile);
|
|
||||||
if (!is_dir($dbDir)) {
|
|
||||||
mkdir($dbDir, 0755, true);
|
|
||||||
}
|
|
||||||
touch($dbFile);
|
|
||||||
@chmod($dbFile, $config['db']['file_mode']);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Options Medoo pour SQLite.
|
|
||||||
*/
|
|
||||||
$medooOptions = [
|
|
||||||
'database_type' => 'sqlite',
|
|
||||||
'database_name' => $dbFile,
|
|
||||||
'charset' => 'utf8',
|
|
||||||
];
|
|
||||||
|
|
||||||
/** @var Medoo $database */
|
|
||||||
$database = new Medoo($medooOptions);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Schéma minimal : création de la table 'post' si elle n'existe pas.
|
|
||||||
*/
|
|
||||||
$database->query(
|
|
||||||
<<<'SQL'
|
|
||||||
CREATE TABLE IF NOT EXISTS post (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
title TEXT NOT NULL,
|
|
||||||
content TEXT NOT NULL
|
|
||||||
);
|
|
||||||
SQL
|
|
||||||
);
|
|
||||||
|
|
||||||
// -------------------------
|
|
||||||
// Services (container simple)
|
|
||||||
// -------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Container simple (table associative de services).
|
|
||||||
*
|
|
||||||
* @var array<string, mixed> $container
|
|
||||||
*/
|
|
||||||
$container = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construire les services et les retourner dans le container.
|
|
||||||
*
|
|
||||||
* Cette fonction reste locale pour préserver l'encapsulation.
|
|
||||||
*
|
|
||||||
* @param array $config
|
|
||||||
* @param Medoo $database
|
|
||||||
* @return array<string, mixed>
|
|
||||||
*/
|
|
||||||
$container = (static function (array $config, Medoo $database): array {
|
|
||||||
$services = [];
|
|
||||||
|
|
||||||
// Résoudre le cache Twig et créer le dossier si nécessaire.
|
|
||||||
$twigCache = $config['twig']['cache'];
|
|
||||||
if ($twigCache && $twigCache !== false && !is_dir((string) $twigCache)) {
|
|
||||||
mkdir((string) $twigCache, 0755, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Vue Twig
|
|
||||||
$loader = new FilesystemLoader(__DIR__ . '/../views');
|
|
||||||
$services['view'] = new Twig($loader, ['cache' => $config['twig']['cache']]);
|
|
||||||
|
|
||||||
// Repository Post (implémentation Medoo)
|
|
||||||
$services['postRepository'] = new App\Repositories\PostRepositoryMedoo($database);
|
|
||||||
|
|
||||||
return $services;
|
|
||||||
})($config, $database);
|
|
||||||
|
|
||||||
// -------------------------
|
|
||||||
// Slim app
|
// Slim app
|
||||||
// -------------------------
|
|
||||||
|
|
||||||
$app = AppFactory::create();
|
$app = AppFactory::create();
|
||||||
|
|
||||||
/**
|
|
||||||
* Middleware d'erreurs.
|
|
||||||
*
|
|
||||||
* Les trois flags correspondent à : displayErrorDetails, logErrors, logErrorDetails.
|
|
||||||
*/
|
|
||||||
$errorMiddleware = $app->addErrorMiddleware($isDebug, $isDebug, $isDebug);
|
|
||||||
|
|
||||||
if (!$isDebug) {
|
|
||||||
$errorHandler = $errorMiddleware->getDefaultErrorHandler();
|
|
||||||
// Renderer HTML générique en production pour masquer les détails d'exception.
|
|
||||||
$errorHandler->registerErrorRenderer('text/html', static function (Throwable $exception, bool $displayErrorDetails): string {
|
|
||||||
return '<!doctype html><html lang="fr"><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>';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Middlewares essentiels
|
// Middlewares essentiels
|
||||||
$app->addBodyParsingMiddleware();
|
$app->addBodyParsingMiddleware();
|
||||||
$app->add(TwigMiddleware::create($app, $container['view']));
|
$app->add(TwigMiddleware::create($app, $twig));
|
||||||
|
|
||||||
/**
|
// Charger les routes
|
||||||
* Charger les routes : le fichier web.php reçoit l'application et le container.
|
|
||||||
* On utilise require pour que toute exception remonte à Slim / au handler d'erreurs.
|
|
||||||
*/
|
|
||||||
$routesPath = __DIR__ . '/../src/Routes/web.php';
|
$routesPath = __DIR__ . '/../src/Routes/web.php';
|
||||||
if (file_exists($routesPath)) {
|
if (file_exists($routesPath)) {
|
||||||
/** @var callable $routes */
|
/** @var callable $routes */
|
||||||
$routes = require $routesPath;
|
$routes = require $routesPath;
|
||||||
$routes($app, $container);
|
$routes($app);
|
||||||
} else {
|
|
||||||
// En cas d'absence du fichier de routes, on peut laisser l'application démarrer sans routes.
|
|
||||||
// (Conserver le comportement antérieur — aucune exception levée ici)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$errorMiddleware = $app->addErrorMiddleware(true, true, true);
|
||||||
|
|
||||||
$app->run();
|
$app->run();
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ namespace App\Controllers;
|
|||||||
use Psr\Http\Message\ResponseInterface as Response;
|
use Psr\Http\Message\ResponseInterface as Response;
|
||||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||||
use Slim\Views\Twig;
|
use Slim\Views\Twig;
|
||||||
use App\Repositories\PostRepositoryMedoo as PostRepository;
|
use App\Repositories\PostRepositoryInterface as PostRepository;
|
||||||
|
use App\Requests\PostRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contrôleur pour les posts.
|
* Contrôleur pour les posts.
|
||||||
@@ -23,26 +24,12 @@ 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();
|
||||||
@@ -61,78 +48,56 @@ class PostController
|
|||||||
{
|
{
|
||||||
$id = (int)($args['id'] ?? 0);
|
$id = (int)($args['id'] ?? 0);
|
||||||
$post = $id ? $this->repo->find($id) : null;
|
$post = $id ? $this->repo->find($id) : null;
|
||||||
|
|
||||||
|
// Si id fourni mais post introuvable -> 404
|
||||||
|
if ($id > 0 && $post === null) {
|
||||||
|
return $res->withStatus(404)->write('Article non trouvé');
|
||||||
|
}
|
||||||
|
|
||||||
$action = $id ? "/admin/edit/{$id}" : "/admin/create";
|
$action = $id ? "/admin/edit/{$id}" : "/admin/create";
|
||||||
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 = $this->sanitize($req->getParsedBody());
|
$postRequest = PostRequest::fromArray($req->getParsedBody());
|
||||||
|
if (! $postRequest->isValid()) {
|
||||||
|
// Simple gestion d'erreur : rediriger vers admin (on peut ajouter flash messages plus tard)
|
||||||
|
return $res->withHeader('Location', '/admin')->withStatus(302);
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $postRequest->validated();
|
||||||
$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'];
|
||||||
$data = $this->sanitize($req->getParsedBody());
|
$existing = $this->repo->find($id);
|
||||||
|
if ($existing === null) {
|
||||||
|
return $res->withStatus(404)->write('Article non trouvé');
|
||||||
|
}
|
||||||
|
|
||||||
|
$postRequest = PostRequest::fromArray($req->getParsedBody());
|
||||||
|
if (! $postRequest->isValid()) {
|
||||||
|
return $res->withHeader('Location', '/admin')->withStatus(302);
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $postRequest->validated();
|
||||||
$this->repo->update($id, $data);
|
$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']);
|
$id = (int)$args['id'];
|
||||||
|
$existing = $this->repo->find($id);
|
||||||
|
if ($existing === null) {
|
||||||
|
return $res->withStatus(404)->write('Article non trouvé');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->repo->delete($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,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
31
src/Factories/PostControllerFactory.php
Normal file
31
src/Factories/PostControllerFactory.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Factories;
|
||||||
|
|
||||||
|
use App\Controllers\PostController;
|
||||||
|
use Slim\Views\Twig;
|
||||||
|
use App\Repositories\PostRepositoryInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fabrique d'instance de PostController pour faciliter l'intégration avec un container.
|
||||||
|
*/
|
||||||
|
final class PostControllerFactory
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Crée une instance de PostController depuis le tableau de services renvoyé par ServiceFactory.
|
||||||
|
*
|
||||||
|
* @param array<string,mixed> $services
|
||||||
|
* @return PostController
|
||||||
|
*/
|
||||||
|
public static function create(array $services): PostController
|
||||||
|
{
|
||||||
|
/** @var Twig $view */
|
||||||
|
$view = $services['view'];
|
||||||
|
/** @var PostRepositoryInterface $repo */
|
||||||
|
$repo = $services['postRepository'];
|
||||||
|
|
||||||
|
return new PostController($view, $repo);
|
||||||
|
}
|
||||||
|
}
|
||||||
94
src/Factories/ServiceFactory.php
Normal file
94
src/Factories/ServiceFactory.php
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Factories;
|
||||||
|
|
||||||
|
use Slim\Views\Twig;
|
||||||
|
use Twig\Loader\FilesystemLoader;
|
||||||
|
use Medoo\Medoo;
|
||||||
|
use Dotenv\Dotenv;
|
||||||
|
use App\Repositories\PostRepositoryMedoo;
|
||||||
|
use App\Repositories\PostRepositoryInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fabrique des services de l'application.
|
||||||
|
*
|
||||||
|
* Cette fabrique centralise la création des dépendances (Twig, DB, repositories...)
|
||||||
|
* pour garder public/index.php minimal et faciliter les tests/unit.
|
||||||
|
*/
|
||||||
|
final class ServiceFactory
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Crée et retourne un tableau associatif de services.
|
||||||
|
*
|
||||||
|
* Clefs retournées :
|
||||||
|
* - 'view' => Twig
|
||||||
|
* - 'postRepository' => PostRepositoryInterface
|
||||||
|
*
|
||||||
|
* @param array<string,mixed>|null $overrides Permet d'injecter des remplacements pour les tests.
|
||||||
|
* @return array<string,mixed>
|
||||||
|
*/
|
||||||
|
public static function createServices(?array $overrides = null): array
|
||||||
|
{
|
||||||
|
// Charger .env si présent (tolérant l'absence)
|
||||||
|
$dotenv = Dotenv::createImmutable(__DIR__ . '/../../');
|
||||||
|
$dotenv->safeLoad();
|
||||||
|
|
||||||
|
// Config basique (idem que précédemment mais centralisé)
|
||||||
|
$env = strtolower((string) ($_ENV['APP_ENV'] ?? $_SERVER['APP_ENV'] ?? 'production'));
|
||||||
|
$devEnvs = ['development', 'dev'];
|
||||||
|
$twigCacheEnv = $_ENV['TWIG_CACHE'] ?? $_SERVER['TWIG_CACHE'] ?? null;
|
||||||
|
if ($twigCacheEnv !== null && $twigCacheEnv !== '') {
|
||||||
|
$twigCache = (string)$twigCacheEnv;
|
||||||
|
} else {
|
||||||
|
$twigCache = in_array($env, $devEnvs, true) ? false : __DIR__ . '/../../var/cache/twig';
|
||||||
|
}
|
||||||
|
|
||||||
|
$dbFile = $_ENV['DB_FILE'] ?? __DIR__ . '/../../database/app.sqlite';
|
||||||
|
$dbFileMode = 0664;
|
||||||
|
|
||||||
|
// Créer dossier cache si nécessaire
|
||||||
|
if ($twigCache && $twigCache !== false && !is_dir((string) $twigCache)) {
|
||||||
|
@mkdir((string) $twigCache, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Twig
|
||||||
|
$loader = new FilesystemLoader(__DIR__ . '/../../views');
|
||||||
|
$twig = new Twig($loader, ['cache' => $twigCache]);
|
||||||
|
|
||||||
|
// Medoo (SQLite)
|
||||||
|
$dbDir = dirname($dbFile);
|
||||||
|
if (!is_dir($dbDir)) {
|
||||||
|
@mkdir($dbDir, 0755, true);
|
||||||
|
}
|
||||||
|
if (!file_exists($dbFile)) {
|
||||||
|
@touch($dbFile);
|
||||||
|
@chmod($dbFile, $dbFileMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
$medooOptions = [
|
||||||
|
'database_type' => 'sqlite',
|
||||||
|
'database_name' => $dbFile,
|
||||||
|
'charset' => 'utf8',
|
||||||
|
];
|
||||||
|
$database = new Medoo($medooOptions);
|
||||||
|
|
||||||
|
// Repository
|
||||||
|
$postRepository = new PostRepositoryMedoo($database);
|
||||||
|
|
||||||
|
$services = [
|
||||||
|
'view' => $twig,
|
||||||
|
'database' => $database,
|
||||||
|
'postRepository' => $postRepository,
|
||||||
|
];
|
||||||
|
|
||||||
|
// Appliquer overrides si fournis (utile pour tests)
|
||||||
|
if (is_array($overrides)) {
|
||||||
|
$services = array_merge($services, $overrides);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $services;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
51
src/Repositories/PostRepositoryInterface.php
Normal file
51
src/Repositories/PostRepositoryInterface.php
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Repositories;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface décrivant les opérations sur les posts.
|
||||||
|
*/
|
||||||
|
interface PostRepositoryInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Retourne tous les posts triés par id descendant.
|
||||||
|
*
|
||||||
|
* @return array<int, array{id:int, title:string, content:string}>
|
||||||
|
*/
|
||||||
|
public function allDesc(): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trouve un post par son id.
|
||||||
|
*
|
||||||
|
* @param int $id
|
||||||
|
* @return array{id:int, title:string, content:string}|null
|
||||||
|
*/
|
||||||
|
public function find(int $id): ?array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crée un post.
|
||||||
|
*
|
||||||
|
* @param array{title:string,content:string} $data
|
||||||
|
* @return int id inséré
|
||||||
|
*/
|
||||||
|
public function create(array $data): int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Met à jour un post.
|
||||||
|
*
|
||||||
|
* @param int $id
|
||||||
|
* @param array{title:string,content:string} $data
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function update(int $id, array $data): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supprime un post.
|
||||||
|
*
|
||||||
|
* @param int $id
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function delete(int $id): void;
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ use Medoo\Medoo;
|
|||||||
* Repository pour "post" basé sur Medoo.
|
* Repository pour "post" basé sur Medoo.
|
||||||
* Retourne et consomme des tableaux associatifs.
|
* Retourne et consomme des tableaux associatifs.
|
||||||
*/
|
*/
|
||||||
class PostRepositoryMedoo
|
class PostRepositoryMedoo implements PostRepositoryInterface
|
||||||
{
|
{
|
||||||
private Medoo $db;
|
private Medoo $db;
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ class PostRepositoryMedoo
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<int, array{id:int, title:string, content:string}>
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
public function allDesc(): array
|
public function allDesc(): array
|
||||||
{
|
{
|
||||||
@@ -35,7 +35,7 @@ class PostRepositoryMedoo
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array{id:int, title:string, content:string}|null
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
public function find(int $id): ?array
|
public function find(int $id): ?array
|
||||||
{
|
{
|
||||||
@@ -53,8 +53,7 @@ class PostRepositoryMedoo
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array{title:string,content:string} $data
|
* @inheritDoc
|
||||||
* @return int Inserted id
|
|
||||||
*/
|
*/
|
||||||
public function create(array $data): int
|
public function create(array $data): int
|
||||||
{
|
{
|
||||||
@@ -66,9 +65,7 @@ class PostRepositoryMedoo
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $id
|
* @inheritDoc
|
||||||
* @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
|
||||||
{
|
{
|
||||||
@@ -79,8 +76,7 @@ class PostRepositoryMedoo
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $id
|
* @inheritDoc
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
public function delete(int $id): void
|
public function delete(int $id): void
|
||||||
{
|
{
|
||||||
|
|||||||
68
src/Requests/PostRequest.php
Normal file
68
src/Requests/PostRequest.php
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Requests;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classe simple responsable de la validation / sanitation des données
|
||||||
|
* provenant des formulaires de post.
|
||||||
|
*
|
||||||
|
* Usage : $valid = PostRequest::fromArray($data)->validated();
|
||||||
|
*/
|
||||||
|
final class PostRequest
|
||||||
|
{
|
||||||
|
private string $title;
|
||||||
|
private string $content;
|
||||||
|
|
||||||
|
private function __construct(string $title, string $content)
|
||||||
|
{
|
||||||
|
$this->title = $title;
|
||||||
|
$this->content = $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crée une instance à partir d'un tableau (ex: $request->getParsedBody()).
|
||||||
|
*
|
||||||
|
* @param mixed $input
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public static function fromArray($input): self
|
||||||
|
{
|
||||||
|
$title = isset($input['title']) ? (string)$input['title'] : '';
|
||||||
|
$content = isset($input['content']) ? (string)$input['content'] : '';
|
||||||
|
|
||||||
|
$title = trim($title);
|
||||||
|
$content = trim($content);
|
||||||
|
|
||||||
|
// Limites raisonnables (configurable si besoin)
|
||||||
|
$title = mb_substr($title, 0, 255);
|
||||||
|
$content = mb_substr($content, 0, 65535);
|
||||||
|
|
||||||
|
return new self($title, $content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne les données validées/sanitizées prêtes à persister.
|
||||||
|
*
|
||||||
|
* @return array{title:string,content:string}
|
||||||
|
*/
|
||||||
|
public function validated(): array
|
||||||
|
{
|
||||||
|
// Ici on pourrait ajouter des validations plus strictes (ex: required, longueur minimale)
|
||||||
|
return [
|
||||||
|
'title' => $this->title,
|
||||||
|
'content' => $this->content,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indique si le formulaire est suffisamment rempli.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isValid(): bool
|
||||||
|
{
|
||||||
|
return $this->title !== '' && $this->content !== '';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,23 +3,19 @@
|
|||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Slim\App;
|
use Slim\App;
|
||||||
use Slim\Views\Twig;
|
use App\Factories\ServiceFactory;
|
||||||
use App\Repositories\PostRepositoryMedoo;
|
use App\Factories\PostControllerFactory;
|
||||||
use App\Controllers\PostController;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param App $app
|
* @param App $app
|
||||||
* @param array{view:Twig, postRepository:PostRepositoryMedoo} $container
|
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
return function (App $app, array $container): void {
|
return function (App $app): void {
|
||||||
/** @var Twig $view */
|
// Créer services (centralisé)
|
||||||
$view = $container['view'];
|
$services = \App\Factories\ServiceFactory::createServices();
|
||||||
/** @var PostRepositoryMedoo $repo */
|
|
||||||
$repo = $container['postRepository'];
|
|
||||||
|
|
||||||
// Instancier le controller une seule fois tout en gardant la modularité
|
// Créer controller via sa factory
|
||||||
$controller = new PostController($view, $repo);
|
$controller = PostControllerFactory::create($services);
|
||||||
|
|
||||||
$app->get('/', [$controller, 'index']);
|
$app->get('/', [$controller, 'index']);
|
||||||
$app->get('/admin', [$controller, 'admin']);
|
$app->get('/admin', [$controller, 'admin']);
|
||||||
|
|||||||
Reference in New Issue
Block a user