Session BRE04 Help

Mettre en place mon premier CRUD

Le but de ce guide c'est de vous faire mettre en place, étape par étape le premier CRUD (Create, Read, Update, Delete) de votre projet, celui des utilisateurs.

Pourquoi commencer par les utilisateurs ? Comme votre projet doit avoir une interface d'administration, vous devrez tous et toutes mettre en place une gestion, même minimale des utilisateurs. Cela permet donc de faire un exercice qui vous est utile, indépendamment des spécificités de votre projet.

L'autre avantage de commencer par les utilisateurs, c'est qu'une fois qu'ils sont en place, vous pourrez enchaîner sur le guide suivant, qui vous permettra de mettre en place l'authentification, qui est un processus qui fait parfois peur, mais dont vous avez tous et toutes besoin dans vos projets.

Étape 1 : Créer la table dans la base de données

L'utilisateur que nous allons manipuler dans ce guide comprend donc 5 champs :

  • id

  • email

  • password

  • first_name (prénom em francais)

  • last_name (nom de famille en français)

Dans votre base de données, vous allez donc créer une table users qui contient 5 colonnes :

Nom du champ

Type du champ

id

INT auto-incrémenté

email

VARCHAR(255)

password

VARCHAR(255)

first_name

VARCHAR(255)

last_name

VARCHAR(255)

Utilisez PHPMyAdmin pour créer votre table. Une fois que c'est fait sa structure doit être semblable à l'image ci-dessous :

Structure de la table users

Étape 2 : créer le modèle User

Dans votre fichier models/User.php vous allez créer une classe User qui correspond à la table que vous venez de créer.

Votre classe aura donc des attributs qui correspondent aux colonnes de la table dams la base de données :

Nom de l'attribut

Type de l'attribut

id

int ou null

email

string

password

string

first_name

string

last_name

string

Votre classe doit également contenir un constructeur et des getters et setters pour chacun des attributs.

<?php class User { public function __construct(private string $email, private string $password, private string $firstName, private string $lastName, private ? int $id = null) { } public function getEmail(): string { return $this->email; } public function setEmail(string $email): void { $this->email = $email; } public function getPassword(): string { return $this->password; } public function setPassword(string $password): void { $this->password = $password; } public function getFirstName(): string { return $this->firstName; } public function setFirstName(string $firstName): void { $this->firstName = $firstName; } public function getLastName(): string { return $this->lastName; } public function setLastName(string $lastName): void { $this->lastName = $lastName; } public function getId(): ?int { return $this->id; } public function setId(?int $id): void { $this->id = $id; } }

Étape 3 : mise en place du Routeur

Maintenant que nous avons notre base de données et notre model, nous allons un peu nous éloigner de ce qui touche aux données et nous concentrer sur le fait de pouvoir afficher des choses.

Nous allons commencer par mettre en place notre routeur. Pour pouvoir avoir un CRUD efficace nous avons besoin de pouvoir accomplir plusieurs actions :

  • consulter la liste des utilisateurs

  • consulter le détail d'un utilisateur

  • créer un utilisateur

  • modifier un utilisateur

  • supprimer un utilisateur

Pour faire ces actions, nous allons avoir besoin de routes (plus de routes que d'actions, car nous avons des routes de vérification). Voici la liste des routes que nous devrons mettre en place :

Nom de la route

Paramètres de la route

À quoi elle sert

list-users

Liste des utilisateurs

details-user

id de l'utilisateur

Détails d'un utilisateur

update-user

id de l'utilisateur

Affiche le formulaire de modification d'un utilisateur

check-update-user

id de l'utilisateur

Vérifie le formulaire et modifie l'utilisateur

create-user

Affiche le formulaire de création d'un utilisateur

check-create-user

Vérifie le formulaire et créée l'utilisateur

delete-user

id de l'utilisateur

Supprime un utilisateur

Notre classe Router devra donc nous permettre de mettre en place ces routes et par la suite de les rediriger vers les bonnes méthodes de Controllers.

Notre classe Router ne contient qu'une seule méthode, dont le prototype est le suivant :

public function handleRequest(array $get)

Cette méthode handleRequest va vérifier si le tableau $get qu'elle reçoit en paramètres contient bien une entrée "route".

Si oui, elle va ensuite devoir vérifier que la valeur de "route" correspond bien au nom d'une des routes du tableau un peu plus haut.

À quoi ça ressemble en code ?

class Router { public function handleRequest(array $get) { if(!empty($get['route'])) { // route existe et n'est pas vide if($get['route'] === "list-users") { } else if($get['route'] === "details-user") { } else if($get['route'] === "update-user") { } else if($get['route'] === "check-update-user") { } else if($get['route'] === "create-user") { } else if($get['route'] === "check-create-user") { } else if($get['route'] === "delete-user") { } else // n'importe quelle route pas prévue : page 404 { } } else // pas de route je renvoie vers la home { } } }

Ensuite pour pouvoir tester notre router, nous allons mettre des affichages temporaires dans nos conditions :

class Router { public function handleRequest(array $get) { if(!empty($get['route'])) { // route existe et n'est pas vide if($get['route'] === "list-users") { echo "La route demandée est list-users<br>"; } else if($get['route'] === "details-user") { } else if($get['route'] === "update-user") { } else if($get['route'] === "check-update-user") { } else if($get['route'] === "create-user") { } else if($get['route'] === "check-create-user") { } else if($get['route'] === "delete-user") { } else // n'importe quelle route pas prévue : page 404 { echo "La route demandée n'existe pas<br>"; } } else // pas de route je renvoie vers la home { echo "Aucune route n'est demandée<br>"; } } }

Je vous ai fait des exemples pour certaines conditions, à vous de créer les affichages de texte pour celles restantes.

class Router { public function handleRequest(array $get) { if(!empty($get['route'])) { // route existe et n'est pas vide if($get['route'] === "list-users") { echo "La route demandée est list-users<br>"; } else if($get['route'] === "details-user") { echo "La route demandée est details-user<br>"; } else if($get['route'] === "update-user") { echo "La route demandée est update-user<br>"; } else if($get['route'] === "check-update-user") { echo "La route demandée est check-update-user<br>"; } else if($get['route'] === "create-user") { echo "La route demandée est create-user<br>"; } else if($get['route'] === "check-create-user") { echo "La route demandée est check-create-user<br>"; } else if($get['route'] === "delete-user") { echo "La route demandée est delete-user<br>"; } else // n'importe quelle route pas prévue : page 404 { echo "La route demandée n'existe pas<br>"; } } else // pas de route je renvoie vers la home { echo "Aucune route n'est demandée<br>"; } } }

Étape 4 : Mise en place du UserController

Nous avons maintenant un Router, nous allons créer le principal Controller qu'il va devoir appeler.

Reprenons la liste de nos routes :

Nom de la route

Paramètres de la route

À quoi elle sert

list-users

Liste des utilisateurs

details-user

id de l'utilisateur

Détails d'un utilisateur

update-user

id de l'utilisateur

Affiche le formulaire de modification d'un utilisateur

check-update-user

id de l'utilisateur

Vérifie le formulaire et modifie l'utilisateur

create-user

Affiche le formulaire de création d'un utilisateur

check-create-user

Vérifie le formulaire et créée l'utilisateur

delete-user

id de l'utilisateur

Supprime un utilisateur

Avec cette liste, nous pouvons déduire les méthodes qui existeront dans notre Controller :

Nom de la route

Méthode Correspondante

list-users

UserController::list()

details-user

UserController::details(int $id)

update-user

UserController::update(int $id)

check-update-user

UserController::checkUpdate(int $id)

create-user

UserController::create()

check-create-user

UserController::checkCreate()

delete-user

UserController::delete(int $id)

Mettons donc en place le squelette de notre UserController avec ces méthodes :

class UserController extends AbstractController { public function __construct(){ parent::__construct(); } public function list() : void { } public function details(int $id) : void { } public function update(int $id) : void { } public function checkUpdate(int $id) : void { } public function create() : void { } public function checkCreate() : void { } public function delete(int $id) : void { } }

Ensuite, nous allons faire en sorte d'afficher dans chaque méthode le nom de celle ci, pour pouvoir tester le bon fonctionnement de notre solution.

public function list() : void { echo "UserController::list<br>"; }

Basez-vous sur l'exemple ci-dessus et faites des affichages dans chacune des méthodes.

class UserController extends AbstractController { public function __construct(){ parent::__construct(); } public function list() : void { echo "UserController::list<br>"; } public function details(int $id) : void { echo "UserController::details<br>"; } public function update(int $id) : void { echo "UserController::update<br>"; } public function checkUpdate(int $id) : void { echo "UserController::checkUpdate<br>"; } public function create() : void { echo "UserController::create<br>"; } public function checkCreate() : void { echo "UserController::checkCreate<br>"; } public function delete(int $id) : void { echo "UserController::delete<br>"; } }

Étape 5 : appeler le UserController dans le Router

Nous avons maintenant un Router et un Controller. Dans cette étape, nous allons relier les deux, en nous basant sur notre tableau de correspondance entre route et méthode de controller :

Nom de la route

Méthode Correspondante

list-users

UserController::list()

details-user

UserController::details(int $id)

update-user

UserController::update(int $id)

check-update-user

UserController::checkUpdate(int $id)

create-user

UserController::create()

check-create-user

UserController::checkCreate()

delete-user

UserController::delete(int $id)

Nous allons commencer par modifier notre Router en lui ajoutant un UserController en attribut :

class Router { private UserController $uc; public function __construct() { $this->uc = new UserController(); } public function handleRequest(array $get) { if(!empty($get['route'])) { // route existe et n'est pas vide if($get['route'] === "list-users") { echo "La route demandée est list-users<br>"; } else if($get['route'] === "details-user") { echo "La route demandée est details-user<br>"; } else if($get['route'] === "update-user") { echo "La route demandée est update-user<br>"; } else if($get['route'] === "check-update-user") { echo "La route demandée est check-update-user<br>"; } else if($get['route'] === "create-user") { echo "La route demandée est create-user<br>"; } else if($get['route'] === "check-create-user") { echo "La route demandée est check-create-user<br>"; } else if($get['route'] === "delete-user") { echo "La route demandée est delete-user<br>"; } else // n'importe quelle route pas prévue : page 404 { echo "La route demandée n'existe pas<br>"; } } else // pas de route je renvoie vers la home { echo "Aucune route n'est demandée<br>"; } } }

Nous allons ensuite remplacer nos affichages temporaires par l'appel des méthodes du Controller. Pour les méthodes qui prennent en id en paramètre, emvoyez simplement le nombre 42.

Nous n'avons pas encore de Controller qui gère la home et la page 404 donc laissez ces deux conditions telles quelles.

class Router { private UserController $uc; public function __construct() { $this->uc = new UserController(); } public function handleRequest(array $get) { if(!empty($get['route'])) { // route existe et n'est pas vide if($get['route'] === "list-users") { $this->uc->list(); } else if($get['route'] === "details-user") { $this->uc->details(42); } else if($get['route'] === "update-user") { $this->uc->update(42); } else if($get['route'] === "check-update-user") { $this->uc->checkUpdate(42); } else if($get['route'] === "create-user") { $this->uc->create(); } else if($get['route'] === "check-create-user") { $this->uc->checkCreate(); } else if($get['route'] === "delete-user") { $this->uc->delete(42); } else // n'importe quelle route pas prévue : page 404 { echo "La route demandée n'existe pas<br>"; } } else // pas de route je renvoie vers la home { echo "Aucune route n'est demandée<br>"; } } }

Étape 6 : appel du Router dans l'index

Avant toute chose, nous allons commencer par faire charger nos classes à notre programme, vu que nous utilisons un composer.json nous allons utiliser une commande composer pour charger nos classes :

composer dump-autoload

Puis nous allons instancier un Router dans notre index.php et appeler sa méthode handleRequest:

$router = new Router(); $router->handleRequest($_GET);

Étape 7 : tester les routes

Pour tester vos routes, vous allez run votre fichier index.php et modifier à la main les URLs dans le navigateur. Dans mon navigateur à moi le projet est sur l'URL de base localhost adaptez les URLs suivantes au format sur votre poste :

Route testée

URL à saisir

Résultat attendu

Pas de route

index.php

Aucune route n'est demandée

list-users

index.php?route=list-users

UserController::list

details-user

index.php?route=details-user

UserController::details

create-user

index.php?route=create-user

UserController::create

check-create-user

index.php?route=check-create-user

UserController::checkCreate

update-user

index.php?route=update-user

UserController::update

check-update-user

index.php?route=check-update-user

UserController::checkUpdate

delete-user

index.php?route=delete-user

UserController::delete

toto

index.php?route=toto

La route demandée n'existe pas

Si tous ces tests fonctionnent : votre routeur et votre controller font bien leur travail de routing.

Étape 8 : les templates

Dans cette étape, nous allons mettre en place nos templates.

Commencez par remplir votre fichier templates/admin/layout.html.twig avec le contenu suivant :

<!doctype html> <html lang="fr"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>{% block title %} CRUD Users {% endblock %}</title> {% block stylesheets %} <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-LN+7fdVzj6u52u30Kp6M/trliBMCMKTyK833zpbD+pXdCLuTusPj697FH4R/5mcr" crossorigin="anonymous"> {% endblock %} </head> <body class="container-fluid p-0" data-bs-theme="dark"> <header class="navbar navbar-expand-lg bg-body-tertiary"> <nav class="container-fluid"> <a class="navbar-brand" href="#">CRUD Users</a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Ouvrir le Menu de navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarSupportedContent"> <ul class="navbar-nav me-auto mb-2 mb-lg-0"> <li class="nav-item"> <a class="nav-link" href="#">Utilisateurs</a> </li> </ul> </nav> </header> {% block main %} {% endblock %} {% block scripts %} <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/js/bootstrap.bundle.min.js" integrity="sha384-ndDqU0Gzau9qJ1lfW4pNLlhNTkCfHzAVBReH9diLvGRem5+R9g2FzA8ZGN954O5Q" crossorigin="anonymous"></script> {% endblock %} </body> </html>

Vous allez ensuite créer un dossier templates/admin/users dans lequel vous allez créer 4 fichiers :

  • list.html.twig

  • details.html.twig

  • create.html.twig

  • update.html.twig

Pour l'instant, nous n'allons nous occuper que de list.html.twig, vous pouvez y ajouter le code suivant :

{% extends 'admin/layout.html.twig' %} {% block title %} Liste des Users | {{ parent() }} {% endblock %} {% block main %} <main class="container py-5"> <h1 class="mb-3">Liste des utilisateurs</h1> <a href="index.php?route=create-user" class="my-3 btn btn-primary">Ajouter un utilisateur</a> <table class="table table-striped"> <thead> <tr> <th>#</th> <th>Email</th> <th>Prénom</th> <th>Nom</th> <th>Actions</th> </tr> </thead> <tbody> </tbody> </table> </main> {% endblock %}

Étape 9 : afficher le template depuis la méthode du Controller

Dans la méthode list de votre UserController, vous allez remplacer le echo d'affichage par un appel au moteur de rendu Twig en utilisant la méthode render que vous fournit l'AbstractController :

public function list() : void { $this->render('admin/users/list.html.twig', []); }

À présent lorsque vous testez l'URL pour list-users dans votre navigateur, elle devrait afficher ceci :

Page vide de la liste des utilisateurs

Étape 10 : mettre en place le UserManager

Nous allons commencer par mettre en place le squelette de notre UserManager avec ses méthodes vides :

class UserManager extends AbstractManager { public function __construct() { parent::__construct(); } public function findAll() : array { return []; } public function findOne(int $id) : ? User { return null; } public function findByEmail(string $email) : ?User { return null; } public function create(User $user) : bool { return true; } public function update(User $user) : bool { return true; } public function delete(User $user) : bool { return true; } }

Puis, nous allons l'ajouter à notre autoload en faisant la commande suivante dans le terminal :

composer dump-autoload

Ensuite, nous allons mettre en place le contenu de la méthode dont nous avons besoin pour notre page de liste des utilisateurs : la méthode findAll.

Le but de cette méthode, c'est de nous renvoyer un tableau de tous les utilisateurs présents dans la base de données. Et comme nous travaillons en MVC et POO de nous les renvoyer sous la forme d'instances de notre model User.

Comment allons-nous faire ça ?

Tout d'abord, nous allons préparer une requête SQL qui permet de récupérer tous les champs de tous les users inscrits en base de données :

SELECT * FROM users

Nous allons ensuite utiliser l'instance de PDO qui est chargée dans le constructeur de l'AbstractManager et donc héritée par le UserManager pour interroger la base de données.

$query = $this->db->prepare('SELECT * FROM users'); $parameters = []; $query->execute($parameters); $results = $query->fetchAll(PDO::FETCH_ASSOC);

Notre variable $results conteint à présent un tableau de tableaux associatifs, renvoyés par la base de données. Nous voulons donc transformer ça en tableau d'instances de la classe User que notre méthode pourra return.

$users = []; foreach($results as $result) { $user = new User($result['email'], $result['password'], $result['first_name'], $result['last_name'], $result['id']); $users[] = $user; } return $users;

Une fois le tout assemblé ça donne :

public function findAll() : array { $query = $this->db->prepare('SELECT * FROM users'); $parameters = []; $query->execute($parameters); $results = $query->fetchAll(PDO::FETCH_ASSOC); $users = []; foreach($results as $result) { $user = new User($result['email'], $result['password'], $result['first_name'], $result['last_name'], $result['id']); $users[] = $user; } return $users; }

Dans l'étape suivante, nous allons voir comment tester que tout cela fonctionne.

Étape 11 : tester la méthode findAll du UserManager

Notre méthode UserManager::findAll va devoir être appellée dans notre méthode UserController::list, ça toombe bien nous avons déjà mis en place cette méthode et nous savons qu'elle fonctionne. Nous allons simplement lui ajouter :

  • L'instanciation d'un UserManager

  • L'appel à la méthode findAll

  • Un dump du résultat de findAll

  • l'envoie du résultat de findAll au template

En code ça ressemble à ça :

public function list() : void { $um = new UserManager(); $users = $um->findAll(); dump($users); die; $this->render('admin/users/list.html.twig', [ 'users' => $users ]); }

Lorsque vous allez tester votre route pour l'URL index.php?route=list-users dans votre navigateur, vous allez obtenir ceci :

FindAll vide

C'est normal, nous n'avons pas encore d'utilisateurs dans notre base de données.

Je vais donc utiliser PHPMyAdmin pour en ajouter un de test :

Création d'un user de test dans PHPMyAdmin

Quand vous retestez votre URL, vous obtenez ceci :

findAll avec user de test

Une fois que cela fonctionne pour vous, retirez l'affichage de test dans votre méthode de Controller :

public function list() : void { $um = new UserManager(); $users = $um->findAll(); $this->render('admin/users/list.html.twig', [ 'users' => $users ]); }

Étape 12 : dynamiser le template de la liste des utilisateurs

Pour afficher la liste de nos utilisateurs dans notre template admin/users/list.html.twig nous allons devoir utiliser une boucle pour remplir la balise <tbody> de notre tableau.

Pour chacun des utilisateurs, nous allons créer une ligne dans le tableau (balise <tr>) et chacun des champs de l'utilisateur correspondra à une colonne de cette ligne (balise <td>). Dans la dernière colonne nous allons mettre nos 3 boutons d'actions qui permettront de :

  • Voir le détail de l'utilisateur

  • Modifier l'utilisateur

  • Supprimer l'utilisateur

Voilà ce que cette boucle donne en code en utilisant Twig :

{% for user in users %} <tr> <td>{{ user.id }}</td> <td>{{ user.email }}</td> <td>{{ user.firstName }}</td> <td>{{ user.lastName }}</td> <td> <a href="index.php?route=details-user&id={{ user.id }}" class="btn btn-primary mx-1">Détails</a> <a href="index.php?route=update-user&id={{ user.id }}" class="btn btn-success mx-1">Modifier</a> <a href="index.php?route=delete-user&id={{ user.id }}" class="btn btn-danger mx-1">Supprimer</a> </td> </tr> {% endfor %}

Placez ce code au bon emplacement dans votre template pour pouvoir afficher les informations des utilisateurs. Une fois que cela fonctionne, vous devriez obtenir ceci :

la liste des utilisateurs
{% extends 'admin/layout.html.twig' %} {% block title %} Liste des Users | {{ parent() }} {% endblock %} {% block main %} <main class="container py-5"> <h1 class="mb-3">Liste des utilisateurs</h1> <a href="index.php?route=create-user" class="my-3 btn btn-primary">Ajouter un utilisateur</a> <table class="table table-striped"> <thead> <tr> <th>#</th> <th>Email</th> <th>Prénom</th> <th>Nom</th> <th>Actions</th> </tr> </thead> <tbody> {% for user in users %} <tr> <td>{{ user.id }}</td> <td>{{ user.email }}</td> <td>{{ user.firstName }}</td> <td>{{ user.lastName }}</td> <td> <a href="index.php?route=details-user&id={{ user.id }}" class="btn btn-primary mx-1">Détails</a> <a href="index.php?route=update-user&id={{ user.id }}" class="btn btn-success mx-1">Modifier</a> <a href="index.php?route=delete-user&id={{ user.id }}" class="btn btn-danger mx-1">Supprimer</a> </td> </tr> {% endfor %} </tbody> </table> </main> {% endblock %}

Étape 13 : le template du formulaire de création d'un utilisateur

La prochaine partie du CRUD sur laquelle nous allons nous pencher c'est la création d'un utilisateur, et nous allons commencer par le template qui affiche le formulaire de création d'un utilisateur.

Apparté diagramme de séquence

Voici le diagramme de séquence du déroulement de la création d'un utilisateur :

UserUserUserControllerUserControllerUserUserUserManagerUserManagerSoumission du formulaire de créationValidation du formulairealt[Le formulaire est valide]Instancie un User avec les données du formulaireInstancie un UserManagercreate(User)Return (success/failure)Redirige vers la page de liste des utilisateurs[Le formulaire est invalide]Afficher les erreurs

Retour au template

Dans le template admin/users/create.html.twig, placez le code suivant :

{% extends 'admin/layout.html.twig' %} {% block title %} Créer un User | {{ parent() }} {% endblock %} {% block main %} <main class="container py-5"> <h1 class="mb-3">Créer un utilisateur</h1> <form method="post" action="index.php?route=check-create-user"> <p class="form-text">Tous les champs sont obligatoires</p> <fieldset class="mb-3"> <label for="email" class="form-label">Email</label> <input type="email" name="email" id="email" class="form-control" required /> </fieldset> <fieldset class="mb-3"> <label for="password" class="form-label">Mot de passe</label> <input type="password" name="password" id="password" class="form-control" required/> </fieldset> <fieldset class="mb-3"> <label for="first_name" class="form-label">Prénom</label> <input type="text" name="first_name" id="first_name" class="form-control" required/> </fieldset> <fieldset class="mb-3"> <label for="last_name" class="form-label">Nom</label> <input type="text" name="last_name" id="last_name" class="form-control" required/> </fieldset> <fieldset class="mb-3"> <button type="submit" class="btn btn-primary">Enregistrer</button> </fieldset> </form> </main> {% endblock %}

Regardez bien l'action de notre formulaire :

<form method="post" action="index.php?route=check-create-user">

Elle enverra directement le formulaire vers la route que nous avons mise en place pour traiter le formulaire de création.

C'est donc la partie "soumission du formulaire de création" de notre diagramme de séquence

UserUserUserControllerUserControllerSoumission du formulaire de création

Étape 14 : Afficher le template du formulaire depuis le UserController

Faites en sorte d'afficher le template admin/users/create.html.twig dans la méthode UserController::create.

public function create() : void { $this->render('admin/users/create.html.twig', [ ]); }

Si tout fonctionne bien lorsque vous testez l'URL de la page de création index.php?route=create-user vous devriez obtenir ceci :

Formulaire de création

Étape 15 : Traiter le formulaire de création

Nous allons maintenant nous occuper des étapes suivantes de notre diagramme de séquence :

UserUserUserControllerUserControllerValidation du formulaire

Nous allons donc vérifier 2 conditions principales :

  • Tous les champs sont remplis

  • Il n'existe pas d'utilisateur avec cet email

Dans la méthode UserController::checkCreate vous allez d'abord vérifier que tous les champs du formulaire sont bien remplis :

if(!empty($_POST["email"]) && !empty($_POST["password"]) && !empty($_POST["first_name"]) && !empty($_POST["last_name"])){ // tous les champs sont remplis } else { // certains champs ne sont pas remplis }

Ensuite si tous les champs sont remplis, nous allons utiliser la méthode findByEmail du UserManager pour vérifier si un utilisateur avec cet email existe déjà. Cette méthode n'est pas encore codé, mais en regardant son prototype, nous savons qu'elle peut renvoyer null ou un User.

C'est le cas où elle renvoie null qui nous intéresse, car cela voudrait dire que l'utilisateur n'existe pas déjà donc nous pouvons le créer.

public function checkCreate() : void { if(!empty($_POST["email"]) && !empty($_POST["password"]) && !empty($_POST["first_name"]) && !empty($_POST["last_name"])) { // tous les champs sont remplis $um = new UserManager(); $user = $um->findByEmail($_POST["email"]); if($user === null) { // il n'existe pas, on peut le créer } else { // un utilisateur avec cet email existe déjà } } else { // certains champs ne sont pas remplis } }

Si nous pouvons créer l'utilisateur (il n'existe pas déjà et tous les champs sont saisis) nous allons passer au bloc "Le formulaire est valide" de notre diagramme de séquence :

UserUserUserControllerUserControllerUserUserUserManagerUserManageralt[Le formulaire est valide]Instancie un User avec les données du formulaireInstancie un UserManagercreate(User)Return (success/failure)Redirige vers la page de liste des utilisateurs

La première chose que nous allons faire c'est donc d'instancier un modèle User avec les données du formulaire :

$user = new User($_POST["email"], $_POST["password"], $_POST["first_name"], $_POST["last_name"]);

Le UserManager a déjà été instancié pour vérifier la validité du formulaire nous allons donc le réutiliser pour appeler sa méthode create:

$ret = $um->create($user);

UserManager::create retourne un booleen donc nous pouvons savoir si notre création a fonctionné ou pas. Si elle a fonctionné nous redirigeons vers la page de liste des utilisateurs :

if($ret) { header("Location: index.php?route=list-users"); } else { // la création a échoué. }
class UserController extends AbstractController { public function __construct(){ parent::__construct(); } public function list() : void { $um = new UserManager(); $users = $um->findAll(); $this->render('admin/users/list.html.twig', [ 'users' => $users ]); } public function details(int $id) : void { echo "UserController::details<br>"; } public function update(int $id) : void { echo "UserController::update<br>"; } public function checkUpdate(int $id) : void { echo "UserController::checkUpdate<br>"; } public function create() : void { $this->render('admin/users/create.html.twig', [ ]); } public function checkCreate() : void { if(!empty($_POST["email"]) && !empty($_POST["password"]) && !empty($_POST["first_name"]) && !empty($_POST["last_name"])) { // tous les champs sont remplis $um = new UserManager(); $user = $um->findByEmail($_POST["email"]); if($user === null) { // il n'existe pas, on peut le créer $user = new User($_POST["email"], $_POST["password"], $_POST["first_name"], $_POST["last_name"]); $ret = $um->create($user); if($ret) { header("Location: index.php?route=list-users"); } else { // la création a échoué. } } else { // un utilisateur avec cet email existe déjà } } else { // certains champs ne sont pas remplis } } public function delete(int $id) : void { echo "UserController::delete<br>"; } }

Étape 16 : gérer les erreurs du formulaire

Pour pouvoir gérer les erreurs de nos formulaires, nous allons devoir utiliser les sessions. Nous allons donc les activer dans notre index.php:

<?php // charge l'autoload de composer require "vendor/autoload.php"; session_start(); // démarrage de la session // charge le contenu du .env dans $_ENV $dotenv = Dotenv\Dotenv::createImmutable(__DIR__); $dotenv->load(); $router = new Router(); $router->handleRequest($_GET);

Ensuite dans nos templates, nous allons faire en sorte de pouvoir afficher des messages d'erreur.

D'abord dans le template de notre formulaire de création admin/users/create.html.twig:

{% if errors %} {% for error in errors %} <div class="alert alert-danger mb-3" role="alert"> {{ error }} </div> {% endfor %} {% endif %}

Si notre template reçoit des erreurs à afficher, il fait une boucle sur la liste des erreurs et affiche une alerte pour chacune d'entre elle.

Mais comment envoyer ces erreurs au template ? Nous allons le faire via la session dans notre UserController:

Nous allons commencer par créer un tableau d'erreurs vide :

$_SESSION["errors"] = [];

Puis si nous rencontrons des erreurs, nous allons le remplir :

$_SESSION["errors"][] = "La création a échoué lors de l'écriture dans la base de données."; $_SESSION["errors"][] = "Un utilisateur avec cet email existe déjà."; $_SESSION["errors"][] = "Au moins un champ obligatoire est manquant.";

Si nous ne rencontrons aucune erreur, nous supprimons le tableau avant de rediriger vers la liste des utilisateurs :

unset($_SESSION["errors"]);

Si nous rencontrons une erreur, nous redirigeons vers la page du formulaire :

header("Location: index.php?route=create-user");

Le code complet :

public function checkCreate() : void { $_SESSION["errors"] = []; if(!empty($_POST["email"]) && !empty($_POST["password"]) && !empty($_POST["first_name"]) && !empty($_POST["last_name"])) { // tous les champs sont remplis $um = new UserManager(); $user = $um->findByEmail($_POST["email"]); if($user === null) { // il n'existe pas, on peut le créer $user = new User($_POST["email"], $_POST["password"], $_POST["first_name"], $_POST["last_name"]); $ret = $um->create($user); if($ret) { unset($_SESSION["errors"]); header("Location: index.php?route=list-users"); } else { $_SESSION["errors"][] = "La création a échoué lors de l'écriture dans la base de données."; header("Location: index.php?route=create-user"); } } else { $_SESSION["errors"][] = "Un utilisateur avec cet email existe déjà."; header("Location: index.php?route=create-user"); } } else { $_SESSION["errors"][] = "Au moins un champ obligatoire est manquant."; header("Location: index.php?route=create-user"); } }

Ensuite c'est notre méthode UserController::create qui va se charger d'envoyer les erreurs au template et de les effacer de la session pour qu'elles ne s'affichent pas en boucle :

public function create() : void { if (!empty($_SESSION["errors"])) { $errors = $_SESSION["errors"]; unset($_SESSION["errors"]); $this->render('admin/users/create.html.twig', [ "errors" => $errors ]); } else { $this->render('admin/users/create.html.twig', [ ]); } }

Pour pouvoir tester, nous allons devoir faire une modification à la méthode UserManager::create pour le moment elle renvoie true, faites lui renvoyer false.

Ensuite si vous testez votre formulaire de création, vous devriez obtenir ceci :

Erreur formulaire création

Étape 17 : les méthodes du UserManager pour la création de l'utilisateur

Dans l'étape de validation par le controller, nous avons appellé deux méthodes de notre UserManager: la méthode findByEmail et la méthode create, nous allons donc les compléter.

UserManager::findByEmail

Voici le prototype de notre méthode UserManager::findByEmail:

public function findByEmail(string $email) : ? User

Elle prend donc un paramètre string $email et retourne null ou une instance de la classe User.

Elle va devoir effectuer une requête pour trouver si un utilisateur inscrit dans la base de données utilise l'email passé en paramètres :

$query = $this->db->prepare('SELECT * FROM users WHERE email = :email'); $parameters = [ ':email' => $email ]; $query->execute($parameters);

Si un utilisateur existe, elle va instancier une classe User avec les infos de l'utilisateur et retourner cette instance de classe :

$result = $query->fetch(PDO::FETCH_ASSOC); if($result) { $user = new User($result['email'], $result['password'], $result['first_name'], $result['last_name'], $result['id']); return $user; }

Si l'utilisateur n'existe pas, elle va retourner null:

else { return null; }
public function findByEmail(string $email) : ? User { $query = $this->db->prepare('SELECT * FROM users WHERE email = :email'); $parameters = [ ':email' => $email ]; $query->execute($parameters); $result = $query->fetch(PDO::FETCH_ASSOC); if($result) { $user = new User($result['email'], $result['password'], $result['first_name'], $result['last_name'], $result['id']); return $user; } else { return null; } }

UserManager::create

Voici le prototype de notre méthode UserManager::create:

public function create(User $user) : bool

Elle prend une instance de la classe User en paramètres et retourne un booleen.

Elle va devoir utiliser les informations de l'instance de User pour faire un INSERT dans la base de données :

$query = $this->db->prepare("INSERT INTO users (id, email, password, first_name, last_name) VALUES (NULL, :email, :password, :first_name, :last_name)"); $parameters = [ 'email' => $user->getEmail(), 'password' => $user->getPassword(), 'first_name' => $user->getFirstName(), 'last_name' => $user->getLastName() ]; $query->execute($parameters);

Ensuite si l'utilisateur a bien été inséré, elle renvoie true, sinon elle renvoie false.

public function create(User $user) : bool { $query = $this->db->prepare("INSERT INTO users (id, email, password, first_name, last_name) VALUES (NULL, :email, :password, :first_name, :last_name)"); $parameters = [ 'email' => $user->getEmail(), 'password' => $user->getPassword(), 'first_name' => $user->getFirstName(), 'last_name' => $user->getLastName() ]; $query->execute($parameters); if($this->db->lastInsertId()) return true; else return false; }

À présent si vous retestez votre formulaire de création d'utilisateur, vous ne devriez plus avoir d'erreur et votre nouvel utilisateur devrait apparaitre dans votre liste.

Étape 18 : supprimer un utilisateur

Pour supprimer un utilisateur, nous allons devoir modifier 2 méthodes : UserController::delete et UserManager::delete.

UserController::delete

public function delete(int $id) : void { $um = new UserManager(); $um->delete($id); header("Location: index.php?route=list-users"); }

UserManager::delete

public function delete(int $id) : bool { $query = $this->db->prepare("DELETE FROM users WHERE id = :id"); $parameters = [ 'id' => $id ]; $query->execute($parameters); return true; }

Nous allons également devoir modifier notre Router pour qu'il envoie l'id de l'utilisateur à supprimer au Controller :

else if($get['route'] === "delete-user") { if(!empty($get["id"])) { $this->uc->delete(intval($get["id"])); } }

À présent si vous testez la suppression d'un utilisateur depuis votre liste, cela devrait fonctionner.

Étape 19 : afficher les détails d'un utilisateur

Router

else if($get['route'] === "details-user") { if(!empty($get["id"])) { $this->uc->details(intval($get["id"])); } }

UserController::details

public function details(int $id) : void { $um = new UserManager(); $user = $um->findOne($id); $this->render('admin/users/details.html.twig', [ 'user' => $user ]); }

UserManager::findOne

public function findOne(int $id) : ? User { $query = $this->db->prepare('SELECT * FROM users WHERE id = :id'); $parameters = [ ':id' => $id ]; $query->execute($parameters); $result = $query->fetch(PDO::FETCH_ASSOC); if($result) { $user = new User($result['email'], $result['password'], $result['first_name'], $result['last_name'], $result['id']); return $user; } else { return null; } }

admin/users/details.html.twig

{% extends 'admin/layout.html.twig' %} {% block title %} Détails d'un User | {{ parent() }} {% endblock %} {% block main %} <main class="container py-5"> <h1 class="mb-3">Détails d'un utilisateur</h1> {% if user %} <form> <fieldset class="mb-3"> <label for="id_user" class="form-label">Id</label> <input type="number" name="id_user" id="id_user" class="form-control" value="{{ user.id }}" required readonly/> </fieldset> <fieldset class="mb-3"> <label for="email" class="form-label">Email</label> <input type="email" name="email" id="email" class="form-control" value="{{ user.email }}" required readonly/> </fieldset> <fieldset class="mb-3"> <label for="password" class="form-label">Mot de passe</label> <input type="password" name="password" id="password" class="form-control" value="{{ user.password }}" required readonly/> </fieldset> <fieldset class="mb-3"> <label for="first_name" class="form-label">Prénom</label> <input type="text" name="first_name" id="first_name" class="form-control" value="{{ user.firstName }}" required readonly/> </fieldset> <fieldset class="mb-3"> <label for="last_name" class="form-label">Nom</label> <input type="text" name="last_name" id="last_name" class="form-control" value="{{ user.lastName }}" required readonly/> </fieldset> </form> {% else %} <p>Utilisateur introuvable</p> {% endif %} </main> {% endblock %}
21 August 2025