Première base SQL¶
Objectif : lire une donnée en base de données avec du SQL visible, sans ORM.
Ce que vous allez apprendre : créer une table via une migration, puis la lire avec fetch_one(...) depuis un nouveau contrôleur dédié au domaine des messages.
WelcomeController couvre tous les paliers HTTP précédents, et mvc/routes.py déclare ses routes jusqu'à /welcome/validate.
Nous abordons un nouveau domaine, la base de données : selon le principe « nouveau domaine = nouveau contrôleur », nous créons un second contrôleur, MessageController.
Documentations
Pour bien comprendre ce palier :
| Document | Ce qu'il apporte |
|---|---|
| Migrations SQL | comment créer une table et appliquer une migration |
Migrations
Avant la première migration, initialisez la base de données (une seule fois pour ce mini-projet) :
forge db:init crée la base, l'utilisateur applicatif et la table forge_migrations qui enregistre les migrations déjà appliquées.
Sans cette étape, forge migration:apply n'a ni base ni table de suivi et échoue.
Générez ensuite le fichier de migration ; Forge l'horodate pour vous :
Forge crée mvc/migrations/<timestamp>_create_first_sql_messages.sql.
Ouvrez ce fichier et placez-y le SQL suivant :
CREATE TABLE IF NOT EXISTS first_sql_messages (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
content VARCHAR(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO first_sql_messages (content)
SELECT 'Bonjour SQL'
WHERE NOT EXISTS (SELECT 1 FROM first_sql_messages);
L'INSERT est idempotent : il n'ajoute le message « Bonjour SQL » que si la table est vide, donc rejouer la migration ne crée pas de doublon.
Appliquez enfin la migration avec forge migration:apply avant de tester /message.
Contrôleurs
Créez le fichier mvc/controllers/message_controller.py :
# mvc/controllers/message_controller.py
from core.database.db import fetch_one, insert
from core.http.request import Request
from core.http.response import Response
from core.mvc.controller.base_controller import BaseController
SELECT_FIRST_MESSAGE = "SELECT content FROM first_sql_messages ORDER BY id LIMIT 1"
INSERT_MESSAGE = "INSERT INTO first_sql_messages (content) VALUES (?)"
class MessageController(BaseController):
@staticmethod
def index(request: Request) -> Response:
row = fetch_one(SELECT_FIRST_MESSAGE)
message = row["content"] if row else "(aucun message)"
return Response.text(f"Message depuis la base : {message}")
| Élément | Rôle |
|---|---|
SELECT content FROM first_sql_messages ... |
Le SQL reste visible, lisible tel quel, sans couche d'abstraction. |
fetch_one(...) |
Renvoie une seule ligne sous forme de dictionnaire, ou None si la table est vide ; d'où le repli (aucun message). |
MessageController |
Un nouveau domaine justifie un nouveau contrôleur : il ne mélange pas la logique base de données avec les démonstrations HTTP de WelcomeController. |
L'import insert et la constante INSERT_MESSAGE serviront au palier suivant ; ils sont déjà en place pour ne plus toucher aux imports.
Routes
Ajoutez l'import du contrôleur et la route /message dans mvc/routes.py :
# mvc/routes.py
from core.http.router import Router
from mvc.controllers.home_controller import HomeController
from mvc.controllers.welcome_controller import WelcomeController
from mvc.controllers.message_controller import MessageController
router = Router()
with router.group("", public=True) as public:
public.add("GET", "/", HomeController.index, name="home-index")
public.add("GET", "/welcome", WelcomeController.index, name="welcome-index")
public.add("GET", "/welcome/hello", WelcomeController.hello, name="welcome-hello")
public.add("GET", "/welcome/html", WelcomeController.html, name="welcome-html")
public.add("GET", "/welcome/article/{id}", WelcomeController.article, name="welcome-article")
public.add("GET", "/welcome/debug", WelcomeController.debug, name="welcome-debug")
public.add("GET", "/welcome/json", WelcomeController.json_demo, name="welcome-json")
public.add("GET", "/welcome/csrf", WelcomeController.csrf, name="welcome-csrf")
public.add("GET", "/welcome/form", WelcomeController.form, name="welcome-form")
public.add("POST", "/welcome/form-submit", WelcomeController.form_submit, name="welcome-form_submit")
public.add("GET", "/welcome/validate", WelcomeController.validate, name="welcome-validate")
public.add("POST", "/welcome/validate-submit", WelcomeController.validate_submit, name="welcome-validate_submit")
public.add("GET", "/message", MessageController.index, name="message-index")
Tests
| URL | Résultat |
|---|---|
https://localhost:8000/message |
Message depuis la base : Bonjour SQL |
À retenir
- La base s'initialise une fois avec
forge db:init, puis chaque table naît d'une migration générée (forge migration:make), éditée, et appliquée (forge migration:apply). fetch_one(...)lit une ligne avec du SQL écrit à la main.- Un nouveau domaine se loge dans son propre contrôleur.
Au palier suivant, nous écrivons à notre tour une ligne dans cette table.