diff --git a/composer.json b/composer.json index 41f29d0..3bf160f 100644 --- a/composer.json +++ b/composer.json @@ -13,10 +13,8 @@ "require": { "slim/slim": "4.*", "slim/psr7": "*", - "gabordemooij/redbean": "^5.7", "twig/twig": "*", - "slim/twig-view": "*", - "php-di/php-di": "^7.1" + "catfan/medoo": "^2.1" }, "autoload": { "psr-4": { diff --git a/public/index.php b/public/index.php index 2a40915..d432110 100644 --- a/public/index.php +++ b/public/index.php @@ -2,90 +2,67 @@ declare(strict_types=1); -/* ------------------------------------------------- - * Autoload Composer + notre configuration RedBean - * ------------------------------------------------- */ require __DIR__ . '/../vendor/autoload.php'; -require __DIR__ . '/../src/Config/redbean.php'; -/* ------------------------------------------------- - * Imports (facades, DI, Slim, Twig, etc.) - * ------------------------------------------------- */ -use DI\ContainerBuilder; use Slim\Factory\AppFactory; use Slim\Views\Twig; use Slim\Views\TwigMiddleware; use Twig\Loader\FilesystemLoader; -use RedBeanPHP\R; // façade RedBean (nécessaire pour le service DB) +use Medoo\Medoo; -/* ------------------------------------------------- - * Construction du conteneur DI - * ------------------------------------------------- */ -$containerBuilder = new ContainerBuilder(); +// ------------------------- +// DB SQLite + fichier +// ------------------------- +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 = __DIR__ . '/../database/app.sqlite'; +ensureDatabaseFile($dbFile); -$containerBuilder->addDefinitions([ - - /* ------------------------------------------------- - * Service Twig (template engine) - * ------------------------------------------------- */ - Twig::class => function () { - $loader = new FilesystemLoader(__DIR__ . '/../views'); - // En développement on désactive le cache pour voir les changements immédiatement - return new Twig($loader, ['cache' => false]); - }, - - /* ------------------------------------------------- - * Service « db » – connexion RedBean - * ------------------------------------------------- */ - 'db' => function () { - // Le chemin du fichier SQLite est déterminé dans redbean.php - $dbPath = getDefaultDbPath(); // ex. …/database/app.sqlite ou autre via DB_FILE - initRedBean($dbPath); // crée le fichier + lance R::setup() - // Retourner l’adaptateur (facultatif, on le garde pour que le service existe) - return R::getDatabaseAdapter(); - }, - - // ----------------------------------------------------------------- - // Ajoutez d’autres services ici (logger, repository, etc.) - // ----------------------------------------------------------------- +// ------------------------- +// Instancier Medoo (SQLite) +// ------------------------- +$database = new Medoo([ + 'database_type' => 'sqlite', + 'database_file' => $dbFile, + 'error' => PDO::ERRMODE_EXCEPTION, + 'charset' => 'utf8', ]); -/* ------------------------------------------------- - * Build du conteneur - * ------------------------------------------------- */ -$container = $containerBuilder->build(); +// Créer la table si nécessaire (schéma minimal) +$database->query( + <<<'SQL' +CREATE TABLE IF NOT EXISTS post ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + content TEXT NOT NULL +); +SQL +); -/* ------------------------------------------------- - * Initialise la connexion DB dès le bootstrap - * ------------------------------------------------- */ -$container->get('db'); // déclenche initRedBean() avant le chargement des routes +// ------------------------- +// Services +// ------------------------- +$services = []; +$services['twig'] = new Twig(new FilesystemLoader(__DIR__ . '/../views'), ['cache' => false]); +$services['db'] = $database; +$services['post_repository'] = new App\Repositories\PostRepositoryMedoo($database); -/* ------------------------------------------------- - * Instanciation de l’application Slim avec le conteneur - * ------------------------------------------------- */ -AppFactory::setContainer($container); +// ------------------------- +// Slim app +// ------------------------- $app = AppFactory::create(); - -/* ------------------------------------------------- - * Middleware d’erreur (affiche les traces en dev) - * ------------------------------------------------- */ $app->addErrorMiddleware(true, true, true); +$app->add(TwigMiddleware::create($app, $services['twig'])); -/* ------------------------------------------------- - * Middleware Twig – rend les vues disponibles - * ------------------------------------------------- */ -$twig = $container->get(Twig::class); -$app->add(TwigMiddleware::create($app, $twig)); +// Charger routes et injecter services +(require __DIR__ . '/../src/Routes/web.php')($app, $services); -/* ------------------------------------------------- - * Chargement dynamique de toutes les définitions de routes - * ------------------------------------------------- */ -foreach (glob(__DIR__ . '/../src/Routes/*.routes.php') as $file) { - // Chaque fichier retourne une closure qui reçoit $app - (require $file)($app); -} - -/* ------------------------------------------------- - * Démarrage de l’application - * ------------------------------------------------- */ $app->run(); diff --git a/src/Config/redbean.php b/src/Config/redbean.php deleted file mode 100644 index 98d23ec..0000000 --- a/src/Config/redbean.php +++ /dev/null @@ -1,69 +0,0 @@ -view = $view; + $this->repo = $repo; } - /** Page publique – liste des articles */ - public function index(Request $request, Response $response): Response + public function index(Request $req, Response $res): Response { - $posts = Post::allDesc(); - return $this->view->render($response, 'pages/home.twig', ['posts' => $posts]); + $posts = $this->repo->allDesc(); + return $this->view->render($res, 'pages/home.twig', ['posts' => $posts]); } - /** Tableau de bord admin */ - public function admin(Request $request, Response $response): Response + public function admin(Request $req, Response $res): Response { - $posts = Post::allDesc(); - return $this->view->render($response, 'pages/admin.twig', ['posts' => $posts]); + $posts = $this->repo->allDesc(); + return $this->view->render($res, 'pages/admin.twig', ['posts' => $posts]); } - /** Formulaire création / édition */ - public function form(Request $request, Response $response, array $args): Response + public function form(Request $req, Response $res, array $args): Response { - $id = (int)($args['id'] ?? 0); - $post = $id ? Post::find($id) : null; // id=0 → création - + $id = (int)($args['id'] ?? 0); + $post = $id ? $this->repo->find($id) : null; $action = $id ? "/admin/edit/{$id}" : "/admin/create"; - - return $this->view->render($response, 'pages/post_form.twig', [ - 'action' => $action, - 'post' => $post, - ]); + return $this->view->render($res, 'pages/post_form.twig', ['post' => $post, 'action' => $action]); } - /** Enregistrement d’un nouvel article */ - public function create(Request $request, Response $response): Response + public function create(Request $req, Response $res): Response { - $data = $request->getParsedBody(); - $post = Post::make($data); - $post->save(); - - return $response->withHeader('Location', '/admin')->withStatus(302); + $data = $req->getParsedBody(); + $this->repo->create($data); + return $res->withHeader('Location', '/admin')->withStatus(302); } - /** Mise à jour d’un article existant */ - public function update(Request $request, Response $response, array $args): Response + public function update(Request $req, Response $res, array $args): Response { - $post = Post::find((int)$args['id']); - $post->updateFromArray($request->getParsedBody()); - $post->save(); - - return $response->withHeader('Location', '/admin')->withStatus(302); + $id = (int)$args['id']; + $this->repo->update($id, $req->getParsedBody()); + return $res->withHeader('Location', '/admin')->withStatus(302); } - /** Suppression d’un article */ - public function delete(Request $request, Response $response, array $args): Response + public function delete(Request $req, Response $res, array $args): Response { - $post = Post::find((int)$args['id']); - $post->delete(); - - return $response->withHeader('Location', '/admin')->withStatus(302); + $this->repo->delete((int)$args['id']); + return $res->withHeader('Location', '/admin')->withStatus(302); } } diff --git a/src/Middleware/AuthMiddleware.php b/src/Middleware/AuthMiddleware.php deleted file mode 100644 index c287d21..0000000 --- a/src/Middleware/AuthMiddleware.php +++ /dev/null @@ -1,3 +0,0 @@ -bean = $bean; - } - - /* ------------------------------------------------- - FACTORIES - ------------------------------------------------- */ - - /** Crée un nouveau post à partir d’un tableau de données */ - public static function make(array $data): self - { - $bean = R::dispense('post'); - $bean->title = $data['title'] ?? ''; - $bean->content = $data['content'] ?? ''; - return new self($bean); - } - - /** Charge un post existant (lève une exception s’il n’existe pas) */ - public static function find(int $id): self - { - $bean = R::load('post', $id); - if ($bean->id === 0) { - throw new \RuntimeException("Post {$id} introuvable"); - } - return new self($bean); - } - - /** Retourne tous les posts, triés par id décroissant */ - public static function allDesc(): array - { - $beans = R::findAll('post', ' ORDER BY id DESC '); - return array_map(fn (OODBBean $b) => new self($b), $beans); - } - - /* ------------------------------------------------- - PERSISTENCE - ------------------------------------------------- */ - - /** Enregistre (INSERT ou UPDATE) le bean */ - public function save(): void - { - R::store($this->bean); - } - - /** Supprime le bean de la base */ - public function delete(): void - { - R::trash($this->bean); - } - - /* ------------------------------------------------- - ACCESSEURS / MUTATEURS - ------------------------------------------------- */ - - public function getId(): int - { - return (int) $this->bean->id; - } - public function getTitle(): string - { - return (string) $this->bean->title; - } - public function getContent(): string - { - return (string) $this->bean->content; - } - - public function setTitle(string $title): void - { - $this->bean->title = $title; - } - public function setContent(string $content): void - { - $this->bean->content = $content; - } - - /* ------------------------------------------------- - MISE À JOUR À PARTIR D'UN TABLEAU - ------------------------------------------------- */ - - /** - * Met à jour le bean à partir d’un tableau de données. - * - * Seuls les champs connus sont pris en compte ; les clés inconnues - * sont simplement ignorées. Cette méthode facilite l’appel depuis - * le contrôleur : `$post->updateFromArray($request->getParsedBody());` - * - * @param array $data Données du formulaire - */ - public function updateFromArray(array $data): void - { - // titre - if (array_key_exists('title', $data)) { - $this->setTitle((string) $data['title']); - } - - // contenu - if (array_key_exists('content', $data)) { - $this->setContent((string) $data['content']); - } - - // Si d’autres champs sont ajoutés à l’entité (ex. author, status), - // il suffit de les gérer ici de la même façon. - } -} diff --git a/src/Repositories/PostRepositoryMedoo.php b/src/Repositories/PostRepositoryMedoo.php new file mode 100644 index 0000000..4940e9d --- /dev/null +++ b/src/Repositories/PostRepositoryMedoo.php @@ -0,0 +1,54 @@ +db = $db; + } + + public function allDesc(): array + { + return $this->db->select('post', ['id', 'title', 'content'], ['ORDER' => ['id' => 'DESC']]); + } + + public function find(int $id): ?array + { + $row = $this->db->get('post', ['id', 'title', 'content'], ['id' => $id]); + return $row === null ? null : $row; + } + + public function create(array $data): int + { + $this->db->insert('post', [ + 'title' => $data['title'] ?? '', + 'content' => $data['content'] ?? '', + ]); + return (int)$this->db->id(); + } + + public function update(int $id, array $data): void + { + $this->db->update('post', [ + 'title' => $data['title'] ?? '', + 'content' => $data['content'] ?? '', + ], ['id' => $id]); + } + + public function delete(int $id): void + { + $this->db->delete('post', ['id' => $id]); + } +} diff --git a/src/Routes/admin.routes.php b/src/Routes/admin.routes.php deleted file mode 100644 index 416c0ab..0000000 --- a/src/Routes/admin.routes.php +++ /dev/null @@ -1,25 +0,0 @@ -get('/admin', [PostController::class, 'admin']); - - // Formulaire de création - $app->get('/admin/create', [PostController::class, 'form']); - $app->post('/admin/create', [PostController::class, 'create']); - - // Formulaire d'édition – l'ID doit être fourni dans l'URL - $app->get('/admin/edit/{id}', [PostController::class, 'form']); - $app->post('/admin/edit/{id}', [PostController::class, 'update']); - - // Suppression d'un post - $app->post('/admin/delete/{id}', [PostController::class, 'delete']); -}; diff --git a/src/Routes/home.routes.php b/src/Routes/home.routes.php deleted file mode 100644 index 8dc819f..0000000 --- a/src/Routes/home.routes.php +++ /dev/null @@ -1,18 +0,0 @@ -get('/', [PostController::class, 'index']); -}; diff --git a/src/Routes/web.php b/src/Routes/web.php new file mode 100644 index 0000000..0f45864 --- /dev/null +++ b/src/Routes/web.php @@ -0,0 +1,19 @@ +get('/', [$controller, 'index']); + $app->get('/admin', [$controller, 'admin']); + $app->get('/admin/edit/{id}', [$controller, 'form']); + $app->post('/admin/create', [$controller, 'create']); + $app->post('/admin/edit/{id}', [$controller, 'update']); + $app->post('/admin/delete/{id}', [$controller, 'delete']); +};