first commit
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
composer.lock
|
||||||
|
database/
|
||||||
|
vendor/
|
||||||
15
composer.json
Normal file
15
composer.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "user/blog-slim",
|
||||||
|
"require": {
|
||||||
|
"slim/slim": "4.*",
|
||||||
|
"slim/psr7": "^1.8",
|
||||||
|
"illuminate/database": "^12.52",
|
||||||
|
"twig/twig": "^3.23",
|
||||||
|
"slim/twig-view": "^3.4"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"App\\": "src/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
migrate.php
Normal file
36
migrate.php
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
require __DIR__ . '/vendor/autoload.php';
|
||||||
|
|
||||||
|
use Illuminate\Database\Capsule\Manager as Capsule;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// Connexion SQLite – on utilise un chemin absolu (realpath) afin
|
||||||
|
// d'éviter le helper Laravel `base_path()` qui n'est pas disponible.
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
$capsule = new Capsule;
|
||||||
|
$capsule->addConnection([
|
||||||
|
'driver' => 'sqlite',
|
||||||
|
// __DIR__ pointe sur le répertoire racine du projet
|
||||||
|
// le fichier SQLite se trouve dans le sous‑dossier `database`
|
||||||
|
'database' => realpath(__DIR__ . '/database/blog.sqlite'),
|
||||||
|
'prefix' => '',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$capsule->setAsGlobal();
|
||||||
|
$capsule->bootEloquent();
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// Création de la table `posts` si elle n'existe pas déjà
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
if (!Capsule::schema()->hasTable('posts')) {
|
||||||
|
Capsule::schema()->create('posts', function ($table) {
|
||||||
|
$table->increments('id'); // clé primaire auto‑incrémentée
|
||||||
|
$table->string('title'); // titre de l'article
|
||||||
|
$table->text('content'); // contenu de l'article
|
||||||
|
});
|
||||||
|
echo "Table 'posts' créée avec succès.\n";
|
||||||
|
} else {
|
||||||
|
echo "La table 'posts' existe déjà.\n";
|
||||||
|
}
|
||||||
58
public/index.php
Normal file
58
public/index.php
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Slim\Factory\AppFactory;
|
||||||
|
use Slim\Views\Twig;
|
||||||
|
use Slim\Views\TwigMiddleware;
|
||||||
|
use Illuminate\Database\Capsule\Manager as Capsule;
|
||||||
|
|
||||||
|
require __DIR__ . '/../vendor/autoload.php';
|
||||||
|
|
||||||
|
/* -------------------------------------------------
|
||||||
|
Instanciation de l’application Slim
|
||||||
|
------------------------------------------------- */
|
||||||
|
$app = AppFactory::create();
|
||||||
|
|
||||||
|
/* -------------------------------------------------
|
||||||
|
Middleware d’erreur (affiche les exceptions)
|
||||||
|
------------------------------------------------- */
|
||||||
|
$app->addErrorMiddleware(true, true, true);
|
||||||
|
|
||||||
|
/* -------------------------------------------------
|
||||||
|
Twig (templates)
|
||||||
|
------------------------------------------------- */
|
||||||
|
$twig = Twig::create(__DIR__ . '/../views', ['cache' => false]);
|
||||||
|
$app->add(TwigMiddleware::create($app, $twig));
|
||||||
|
|
||||||
|
/* -------------------------------------------------
|
||||||
|
Vérification / création du fichier SQLite
|
||||||
|
------------------------------------------------- */
|
||||||
|
$dbFile = __DIR__ . '/../database/blog.sqlite';
|
||||||
|
if (!file_exists($dbFile)) {
|
||||||
|
// crée un fichier vide et lui donne les permissions d’écriture
|
||||||
|
touch($dbFile);
|
||||||
|
chmod($dbFile, 0664);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------
|
||||||
|
Eloquent (connexion SQLite)
|
||||||
|
------------------------------------------------- */
|
||||||
|
$capsule = new Capsule;
|
||||||
|
$capsule->addConnection([
|
||||||
|
'driver' => 'sqlite',
|
||||||
|
// le chemin relatif fonctionne ; SQLite créera le fichier si besoin
|
||||||
|
'database' => $dbFile,
|
||||||
|
'prefix' => '',
|
||||||
|
]);
|
||||||
|
$capsule->setAsGlobal();
|
||||||
|
$capsule->bootEloquent();
|
||||||
|
|
||||||
|
/* -------------------------------------------------
|
||||||
|
Chargement des routes
|
||||||
|
------------------------------------------------- */
|
||||||
|
(require __DIR__ . '/../src/routes.php')($app);
|
||||||
|
|
||||||
|
/* -------------------------------------------------
|
||||||
|
Démarrage de l’application
|
||||||
|
------------------------------------------------- */
|
||||||
|
$app->run();
|
||||||
16
src/Models/Post.php
Normal file
16
src/Models/Post.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Models; // ← namespace attendu par routes.php
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modèle Eloquent représentant un article.
|
||||||
|
*/
|
||||||
|
class Post extends Model
|
||||||
|
{
|
||||||
|
protected $table = 'posts';
|
||||||
|
protected $fillable = ['title', 'content'];
|
||||||
|
public $timestamps = false;
|
||||||
|
}
|
||||||
94
src/routes.php
Normal file
94
src/routes.php
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Slim\App;
|
||||||
|
use Slim\Psr7\Request;
|
||||||
|
use Slim\Psr7\Response;
|
||||||
|
use App\Models\Post;
|
||||||
|
use Slim\Views\Twig;
|
||||||
|
|
||||||
|
return function (App $app) {
|
||||||
|
|
||||||
|
// -------------------------------------------------
|
||||||
|
// Page publique – liste des articles
|
||||||
|
// -------------------------------------------------
|
||||||
|
$app->get('/', function (Request $request, Response $response) use ($app) {
|
||||||
|
$posts = Post::orderByDesc('id')->get();
|
||||||
|
|
||||||
|
/** @var Twig $view */
|
||||||
|
$view = $request->getAttribute('view'); // <-- récupération correcte
|
||||||
|
return $view->render($response, 'posts.twig', ['posts' => $posts]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// -------------------------------------------------
|
||||||
|
// Page admin – tableau de bord
|
||||||
|
// -------------------------------------------------
|
||||||
|
$app->get('/admin', function (Request $request, Response $response) use ($app) {
|
||||||
|
$posts = Post::orderByDesc('id')->get();
|
||||||
|
|
||||||
|
/** @var Twig $view */
|
||||||
|
$view = $request->getAttribute('view');
|
||||||
|
return $view->render($response, 'admin.twig', ['posts' => $posts]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// -------------------------------------------------
|
||||||
|
// Création d'un article (POST depuis admin)
|
||||||
|
// -------------------------------------------------
|
||||||
|
$app->post('/admin/create', function (Request $request, Response $response) {
|
||||||
|
$data = $request->getParsedBody();
|
||||||
|
|
||||||
|
Post::create([
|
||||||
|
'title' => $data['title'],
|
||||||
|
'content' => $data['content'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $response
|
||||||
|
->withHeader('Location', '/admin')
|
||||||
|
->withStatus(302);
|
||||||
|
});
|
||||||
|
|
||||||
|
// -------------------------------------------------
|
||||||
|
// Formulaire d'édition (GET depuis admin)
|
||||||
|
// -------------------------------------------------
|
||||||
|
$app->get('/admin/edit/{id}', function (Request $request, Response $response, $args) use ($app) {
|
||||||
|
$id = (int)$args['id'];
|
||||||
|
$post = $id ? Post::findOrFail($id) : null; // id=0 → création
|
||||||
|
|
||||||
|
/** @var Twig $view */
|
||||||
|
$view = $request->getAttribute('view');
|
||||||
|
|
||||||
|
return $view->render($response, 'post_form.twig', [
|
||||||
|
'action' => $id ? "/admin/edit/{$id}" : "/admin/create",
|
||||||
|
'post' => $post,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// -------------------------------------------------
|
||||||
|
// Enregistrement des modifications (POST depuis admin)
|
||||||
|
// -------------------------------------------------
|
||||||
|
$app->post('/admin/edit/{id}', function (Request $request, Response $response, $args) {
|
||||||
|
$post = Post::findOrFail($args['id']);
|
||||||
|
$data = $request->getParsedBody();
|
||||||
|
|
||||||
|
$post->update([
|
||||||
|
'title' => $data['title'],
|
||||||
|
'content' => $data['content'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $response
|
||||||
|
->withHeader('Location', '/admin')
|
||||||
|
->withStatus(302);
|
||||||
|
});
|
||||||
|
|
||||||
|
// -------------------------------------------------
|
||||||
|
// Suppression d'un article (POST depuis admin)
|
||||||
|
// -------------------------------------------------
|
||||||
|
$app->post('/admin/delete/{id}', function (Request $request, Response $response, $args) {
|
||||||
|
$post = Post::findOrFail($args['id']);
|
||||||
|
$post->delete();
|
||||||
|
|
||||||
|
return $response
|
||||||
|
->withHeader('Location', '/admin')
|
||||||
|
->withStatus(302);
|
||||||
|
});
|
||||||
|
};
|
||||||
30
views/admin.twig
Normal file
30
views/admin.twig
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{% extends "layout.twig" %}
|
||||||
|
|
||||||
|
{% block title %}Admin – Gestion des articles{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h2>Gestion des articles</h2>
|
||||||
|
|
||||||
|
<!-- Lien d’ajout -->
|
||||||
|
<a href="/admin/edit/0">+ Ajouter un article</a>
|
||||||
|
|
||||||
|
{% for post in posts %}
|
||||||
|
<div class="post">
|
||||||
|
<h3>{{ post.title }}</h3>
|
||||||
|
<p>{{ post.content|nl2br }}</p>
|
||||||
|
|
||||||
|
<div class="admin-actions">
|
||||||
|
<a href="/admin/edit/{{ post.id }}">Éditer</a>
|
||||||
|
|
||||||
|
<form method="post" action="/admin/delete/{{ post.id }}" style="display:inline;">
|
||||||
|
<button type="submit"
|
||||||
|
onclick="return confirm('Supprimer cet article ?')">
|
||||||
|
Supprimer
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<p>Aucun article à gérer.</p>
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
||||||
21
views/layout.twig
Normal file
21
views/layout.twig
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>{% block title %}Mon Blog{% endblock %}</title>
|
||||||
|
<style>
|
||||||
|
body {font-family: Arial, sans-serif; margin: 2rem;}
|
||||||
|
.post {border-bottom: 1px solid #ccc; padding: 1rem 0;}
|
||||||
|
.admin-actions a,
|
||||||
|
.admin-actions form {margin-right: .5rem;}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>
|
||||||
|
<a href="/">Mon Blog</a> |
|
||||||
|
<a href="/admin">Admin</a>
|
||||||
|
</h1>
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
31
views/post_form.twig
Normal file
31
views/post_form.twig
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
|
||||||
|
{% extends "layout.twig" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{% if post is defined %}Éditer l’article{% else %}Créer un article{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h2>
|
||||||
|
{% if post is defined %}Éditer{% else %}Créer{% endif %} un article
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<form method="post" action="{{ action }}">
|
||||||
|
<p>
|
||||||
|
<label>Titre<br>
|
||||||
|
<input type="text" name="title"
|
||||||
|
value="{{ post.title|default('') }}" required>
|
||||||
|
</label>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<label>Contenu<br>
|
||||||
|
<textarea name="content" rows="6" required>{{ post.content|default('') }}</textarea>
|
||||||
|
</label>
|
||||||
|
</p>
|
||||||
|
<button type="submit">
|
||||||
|
{% if post is defined %}Mettre à jour{% else %}Enregistrer{% endif %}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<p><a href="/admin">Retour à l’admin</a></p>
|
||||||
|
{% endblock %}
|
||||||
14
views/posts.twig
Normal file
14
views/posts.twig
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{% extends "layout.twig" %}
|
||||||
|
|
||||||
|
{% block title %}Articles{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% for post in posts %}
|
||||||
|
<div class="post">
|
||||||
|
<h2>{{ post.title }}</h2>
|
||||||
|
<p>{{ post.content|nl2br }}</p>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<p>Aucun article publié.</p>
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user