
Dans la première partie nous avons créé une application web très simple avec du contenu statique. Maintenant, ajoutons un peu de dynamisme en y intégrant une base de données MySQL.
Création de la base de données
CodeIgniter offre la possibilité de manipuler la base de données avec les classes Migration
et Seeder
, mais pour garder cet exemple le plus simple possible, nous allons exécuter les requêtes SQL directement dans MySQL. Créez une nouvelle base de données avec votre logiciel préféré, tel que PhpMyAdmin, DBeaver, mysql en ligne de commande ou n'importe quoi d'autre. Ensuite, exécutez les requêtes suivantes pour créer les tables et y insérer les données exemples.
La table recette
contient les recettes : le id, le titre de la recette et ses instructions. Comme une recette contient plusieurs ingrédients, on crée une table ingredient
qui contient la liste des ingrédients de toutes les recettes : le id, le nom de l'ingrédient, la quantité requise et le id de la recette auquel il appartient. On ajoute aussi une contrainte de clé étrangère (foreign key) pour garantir l'intégrité entre les deux tables. Avec cette contrainte, on est certains que la colonne id_recette
pointe vers une recette existante.
Exécutez les requêtes suivantes dans votre logiciel de base de données favoris pour créer les tables :
--
-- Table pour les recettes
--
CREATE TABLE `recette` (
`id` int(11) NOT NULL,
`titre` varchar(100) NOT NULL,
`instructions` text NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE `recette`
ADD PRIMARY KEY (`id`);
ALTER TABLE `recette`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
--
-- Table pour les ingrédients
--
CREATE TABLE `ingredient` (
`id` int(11) NOT NULL,
`id_recette` int(11) NOT NULL,
`nom` varchar(50) NOT NULL,
`quantite` varchar(20) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE `ingredient`
ADD PRIMARY KEY (`id`),
ADD KEY `recette_fk` (`id_recette`);
ALTER TABLE `ingredient`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
--
-- Contrainte de clé étrangère (foreign key)
--
ALTER TABLE `ingredient`
ADD CONSTRAINT `recette_fk` FOREIGN KEY (`id_recette`) REFERENCES `recette` (`id`);
Insérer les données dans la base de données
Exécutez les requêtes suivantes pour ajouter les recettes et leurs ingrédients dans les tables qu'on vient de créer. Évidemment vous pouvez ajouter des vraies recettes au lieu de ces exemples un peu niaiseux :
--
-- Insérer les recettes
-- Le id des recettes est généré automatiquement par MySQL
--
INSERT INTO `recette`
(`titre`,
`instructions`)
VALUES
('Eau bouillante',
'Mettre l''eau dans un chaudron et faire bouillir.'),
('Thé',
'Préparez la recette d''eau bouillante. Mettre l''eau dans une tasse, ajoutez la poche de thé et laissez infuser quelques minutes.'),
('Verre d''eau',
'Mettre des glaçons dans un grand verre et remplir d''eau. Ajoutez une tranche de citron si désiré.');
--
-- Insérer les ingrédients pour chaque recette
-- Le id des ingrédient est généré automatiquement par MySQL
-- mais on sélectionne le id de la recette correspondante en faisant une recherche par nom
--
INSERT INTO `ingredient`
(`id_recette`,
`nom`,
`quantite`)
VALUES
((SELECT `id` FROM `recette` WHERE `titre` = 'Eau bouillante'),
'Eau fraîche',
'250 ml'),
((SELECT `id` FROM `recette` WHERE `titre` = 'Thé'),
'Eau fraîche',
'250 ml'),
((SELECT `id` FROM `recette` WHERE `titre` = 'Thé'),
'Poche de thé',
'1'),
((SELECT `id` FROM `recette` WHERE `titre` = 'Verre d''eau'),
'Eau fraîche',
'300 ml'),
((SELECT `id` FROM `recette` WHERE `titre` = 'Verre d''eau'),
'Glaçon',
'2-3'),
((SELECT `id` FROM `recette` WHERE `titre` = 'Verre d''eau'),
'Citron (facultatif)',
'1 tranche');
app/Config/Database.php
Configuration de la connexion à la base de données. Ajustez les valeurs selon votre configuration :
/**
* The default database connection.
*
* @var array
*/
public $default = [
'DSN' => '',
// Définir le hostname.
// Si MySQL est installé sur votre ordinateur, localhost devrait être bon.
'hostname' => 'localhost',
// Définir le username et password.
// Le compte par défaut de MySQL est root/root
'username' => 'root',
'password' => 'root',
// Définir le nom de la base de données.
// Je l'ai nommée ci4 mais ça peut être n'importe quoi.
'database' => 'ci4',
Les modèles RecetteModel et IngredientModel
La classe Model
de CodeIgniter 4 offre beaucoup de fonctionnalités. Dans sa forme la plus simple, il suffit de définir le nom de la table, le type d'objet à retourner et la liste des champs modifiables. Dans cet exemple, on permet de modifier tous les champs, sauf les id. Comme on n'a pas encore de formulaire pour éditer une recette, on pourrait tout simplement laisser le champ vide et tout les champs seraient non modifiables.
app/Models/RecetteModel.php
<?php namespace App\Models;
use CodeIgniter\Model;
use App\Entities\Recette;
class RecetteModel extends Model
{
// Le nom de la table MySQL
protected $table = 'recette';
// Le type d'objet à retourner
protected $returnType = Recette::Class;
// Les champs modifiables
protected $allowedFields = [
'titre',
'instructions',
];
}
app/Models/IngredientModel.php
<?php namespace App\Models;
use CodeIgniter\Model;
use App\Entities\Ingredient;
class IngredientModel extends Model
{
protected $table = 'ingredient';
protected $returnType = Ingredient::Class;
protected $allowedFields = [
'nom',
'quantite',
];
}
Les entités Recette et Ingredient
CodeIgniter 4 introduit un nouveau concept qui n'était pas présent dans la version 3, les entités. On définit une entité par table et au lieu de retourner un objet générique, le modèle retournera une instance de cette classe. Tout comme la classe Model
, la classe Entity
offre des fonctionnalités intéressantes, mais pour cet exemple on va seulement ajouter une variable qui contiendra la liste d'ingrédients de chaque recette. On n'a pas besoin d'ajouter de variables pour le id, le titre, etc. CodeIgniter va automatiquement y ajouter les champs de la table MySQL.
app/Entities/Recette.php
Voici l'entité Recette
. CodeIgniter va y ajouter automatiquement les variables id
, titre
et instructions
de la table recette
. On ajoute un tableau $ingredients
pour la liste d'ingrédients de cette recette :
<?php namespace App\Entities;
use CodeIgniter\Entity;
class Recette extends Entity
{
public $ingredients;
public function __construct (array $data = null)
{
parent::__construct($data);
// Initialiser la liste d'ingrédients avec un tableau vide.
$this->ingredients = [];
}
}
app/Entities/Ingredient.php
Voici l'entité Ingrdient
dans son expression la plus simple. CodeIgniter va y ajouter automatiquement les variables id
, id_recette
, nom
et quantite
de la table ingredient
. On n'a donc rien d'autre à faire. La classe est vide et ne fait qu'étendre la classe Entity
:
<?php namespace App\Entities;
use CodeIgniter\Entity;
class Ingredient extends Entity
{
}
La librairie MesRecettes
Pour simplifier l'obtention des recettes et leurs ingrédients, j'ai créé une librairie MesRecettes
qui s'occupe de charger les modèles et obtenir les données.
app/Libraries/MesRecettes.php
<?php namespace App\Libraries;
use App\Models\RecetteModel;
use App\Models\IngredientModel;
class MesRecettes
{
/**
* Obtenir toutes les recettes
* @return array
*/
public function getToutesLesRecettes ()
{
// Créer une instance de nos deux modèles
$recetteModel = new RecetteModel();
$ingredientModel = new IngredientModel();
// Faire un SELECT des recettes, triées par id
$recettes = $recetteModel
->orderBy('id')
->findAll();
// Pour chaque recette, faire un SELECT de ses ingrédients
foreach ($recettes as &$recette)
{
$recette->ingredients = $ingredientModel
->where( ['id_recette' => $recette->id] )
->orderBy('id')
->findAll();
}
unset($recette);
return $recettes;
}
}
Le controlleur RecettesController
Dans le controlleur, on charge la lirairie MesRecettes
et on obtiens les données de la base de données MySQL. La fonction _donnees_bidon()
définit dans la première partie peut être supprimée.
app/Controllers/RecettesController.php
<?php namespace App\Controllers;
use App\Libraries\MesRecettes;
class RecettesController extends BaseController
{
public function index()
{
// Créer une instance de notre librairie
$mesRecettes = new MesRecettes();
// Rassembler toutes les données utilisées par la vue dans un tableau $data
$data = [
'titre_page' => "Mes recettes",
'sous_titre_page' => "Je vous présente mes recettes favorites...",
'recettes' => $mesRecettes->getToutesLesRecettes(),
];
/* Chacun des items du tableau $data sera accessible dans la vue
* par des variables portant le même nom que la clé :
* $titre_page, $sous_titre_page et $recettes
*/
return view('liste_recettes', $data);
}
}
La vue liste_recettes
Dans la vue liste_recettes
, on fait quelques ajustements pour afficher les données obtenues de la base de données. La variable recette
n'est plus un simple array
. C'est maintenant une instance de la classe App\Entities\Recette
. Tout comme la variable ingredient
n'est plus une string
. C'est maintenant une instance de la classe App\Entities\Ingredient
.
app/Views/liste_recettes.php
<?php
/**
* @var string $titre_page Le titre de la page (créée automatiquement par CI via le tableau $data)
* @var string $sous_titre_page Le sous-titre de la page (créée automatiquement par CI via le tableau $data)
* @var array $recettes Liste des recettes (créée automatiquement par CI via le tableau $data)
* @var App\Entities\Recette $recette Une recette (créée par l'instruction foreach)
* @var App\Entities\Ingredient $ingredient Un ingrédient (créée par l'instruction foreach)
*/
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<title><?= esc($titre_page) ?></title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css"
integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk"
crossorigin="anonymous">
<style type="text/css">
.titre
{
padding: 3rem 1.5rem;
}
article
{
padding: 1.5rem 1.5rem;
}
</style>
</head>
<body>
<main role="main" class="container">
<div class="titre">
<h1>
<?= esc($titre_page) ?>
<small class="text-muted"><?= esc($sous_titre_page) ?></small>
</h1>
</div>
<div class="container">
<?php foreach ($recettes as $recette): ?>
<article>
<h3><?= esc($recette->titre) ?></h3>
<h5>Ingrédients</h5>
<ul>
<?php foreach ($recette->ingredients as $ingredient): ?>
<li><?= esc($ingredient->quantite) ?> <?= esc($ingredient->nom) ?></li>
<?php endforeach; ?>
</ul>
<h5>Préparation</h5>
<p><?= esc($recette->instructions) ?></p>
</article>
<hr>
<?php endforeach; ?>
</div>
</main>
<footer>
<p class="text-center">© 2020 Mon site de recettes</p>
</footer>
</body>
</html>
Deuxième partie terminée !
Vous pouvez maintenant rafaîchir la page http://ci4.test:8888 qui affichera toujours les trois recettes. À première vue il n'y a pas beaucoup de différences, mais les données sont maintenant obtenue de MySQL. Vous pouvez insérer des nouvelles recettes dans les tables recette
et ingredient
et rafraîchir la page pour voir les nouvelles recettes. Si on a beaucoup de recette, ça ne fait pas beaucoup de sens d'avoir une liste interminable. La liste de recettes devrait afficher seulement le titre et les détails de chaque recette devraient être affichés sur une nouvelle page. C'est ce que nous allons faire dès maintenant dans la troisième partie.