Créa-blog

#100joursPourCoder
Projet Créa-code

Ressources pour développeur web

Jour 15 : Formulaire d’inscription sécurisé en PHP et JS

Temps de lecture : 19 minutes
Accueil Projets Jour 15 : Formulaire d’inscription sécurisé en PHP et JS

Aujourd’hui, nous allons aborder un point crucial pour tous site dynamique : le formulaire d’inscription. Nous allons apprendre à le concevoir, non pas graphiquement, mais du point de vue fonctionnel avec du PHP, du JavaScript tout en l’adaptant à notre structure MVC.

Notre but : permettre à un utilisateur de s’inscrire, puis de se connecter automatiquement à un espace personnel. Et évidemment, que cet espace soit sécurisé.

1. Création de la vue signin

Commençons par la base : créer une nouvelle vue dans notre dossier des vues. On va donc le placer dans notre structure MVC dans le dossier app/Views. Codons ce fichier signin.php :

Fichier : app/Views/signin.php

<h1>Inscription</h1>

<form id="signupForm" action="<?= BASE_URL ?>register" method="POST" novalidate>
    <label for="pseudo">Pseudo :</label>
    <input type="text" name="pseudo" id="pseudo" value="<?= $_SESSION['old']['pseudo'] ?? '' ?>" required>
    <?php if (!empty($_SESSION['errors']['pseudo'])): ?>
        <div class="error"><?= $_SESSION['errors']['pseudo'] ?></div>
    <?php endif; ?>

    <label for="email">Email :</label>
    <input type="email" name="email" id="email" value="<?= $_SESSION['old']['email'] ?? '' ?>" required>
    <?php if (!empty($_SESSION['errors']['email'])): ?>
        <div class="error"><?= $_SESSION['errors']['email'] ?></div>
    <?php endif; ?>

    <label for="password">Mot de passe :</label>
    <input type="password" name="password" id="password" required>
    <?php if (!empty($_SESSION['errors']['password'])): ?>
        <div class="error"><?= $_SESSION['errors']['password'] ?></div>
    <?php endif; ?>

    <label for="confirm_password">Confirmer le mot de passe :</label>
    <input type="password" name="confirm_password" id="confirm_password" required>
    <?php if (!empty($_SESSION['errors']['confirm'])): ?>
        <div class="error"><?= $_SESSION['errors']['confirm'] ?></div>
    <?php endif; ?>

    <label for="niveau">Niveau :</label>
    <select name="niveau" id="niveau">
        <option value="">-- Choisissez un niveau --</option>
        <option value="débutant" <?= ($_SESSION['old']['niveau'] ?? '') === 'débutant' ? 'selected' : '' ?>>Débutant</option>
        <option value="intermédiaire" <?= ($_SESSION['old']['niveau'] ?? '') === 'intermédiaire' ? 'selected' : '' ?>>Intermédiaire</option>
        <option value="expérimenté" <?= ($_SESSION['old']['niveau'] ?? '') === 'expérimenté' ? 'selected' : '' ?>>Expérimenté</option>
        <option value="professionnel" <?= ($_SESSION['old']['niveau'] ?? '') === 'professionnel' ? 'selected' : '' ?>>Professionnel</option>
    </select>
    <?php if (!empty($_SESSION['errors']['niveau'])): ?>
        <div class="error"><?= $_SESSION['errors']['niveau'] ?></div>
    <?php endif; ?>

    <button type="submit">S'inscrire</button>
</form>

Rien de trop compliqué ici. Ce formulaire POST contient tous les champs nécessaires à l’inscription, avec les bons attributs name qui nous permettront de récupérer les valeurs côté PHP.

Des espaces vides avec la class error sont prévu pour afficher les erreurs potentielles.

2. Ajout des liens dans le menu de la vue header

Nous allons maintenant ajouter un lien vers cette page d’inscription. Imaginons que notre en-tête soit dans app/Views/partials/header.php.

Ajoutons une condition pour n’afficher le lien « Déconnexion » que si un utilisateur est connecté, et sinon, afficher les liens « Inscription » et « Connexion ». La déconnexion sera gérée un peu plus loin dans ce tutoriel.

Fichier : app/Views/partials/header.php

<header>
    <h1><a href="<?= BASE_URL ?>">Créa-code</a></h1>
    <nav>
        <a href="<?= BASE_URL ?>">Accueil</a>

        <?php if (isset($_SESSION['user'])): ?>
            <a href="<?= BASE_URL ?>dashboard">Dashboard</a>
            <a href="<?= BASE_URL ?>logout">Déconnexion</a>
        <?php else: ?>
            <a href="<?= BASE_URL ?>signin">Inscription</a>
            <a href="<?= BASE_URL ?>login">Connexion</a>
        <?php endif; ?>
    </nav>
</header>

Cela rendra notre menu dynamique, ce qui est essentiel pour une bonne UX (expérience utilisateur).

3. Vérification des champs en JavaScript

Avant d’envoyer le formulaire, faisons une vérification rapide côté client pour éviter les erreurs basiques.

Nous aurions pu mettre un script JS à la fin du fichier signin.php mais les headers HTTP nous bloquerait l’exécution de ce script. Nous allons donc créer un fichier signin-form-validation.js, et le placer dans /public/assets/js/.

Le fichier /public/assets/js/signin-form-validation.js :

document.addEventListener('DOMContentLoaded', function () {

    function clearErrors() {
        document.querySelectorAll('.error-message').forEach(error => error.remove());
    }

    document.getElementById('signupForm').addEventListener('submit', function (e) {
        
        clearErrors();
        
        let formIsValid = true;
        
        document.querySelectorAll('.error').forEach(error => error.remove());

        const pseudo = document.getElementById('pseudo');
        const email = document.getElementById('email');
        const password = document.getElementById('password');
        const confirm = document.getElementById('confirm_password');
        const niveau = document.getElementById('niveau');

        if (pseudo.value.trim().length < 4) {
            showError(pseudo, "Le pseudo doit contenir au moins 4 caractères.");
            formIsValid = false;
        }

        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        if (!emailRegex.test(email.value.trim())) {
            showError(email, "Adresse email invalide.");
            formIsValid = false;
        }

        if (niveau.value === '') {
            showError(niveau, "Veuillez choisir votre niveau.");
            formIsValid = false;
        }

        const passwordValue = password.value;
        const confirmValue = confirm.value;
        const passwordRegex = /^(?=.*[A-Z])(?=.*\d).{8,}$/;

        if (!passwordRegex.test(passwordValue)) {
            showError(password, "Le mot de passe doit contenir au moins 8 caractères, une majuscule et un chiffre.");
            formIsValid = false;
        }

        if (passwordValue !== confirmValue) {
            showError(confirm, "Les mots de passe ne correspondent pas.");
            formIsValid = false;
        }

        if (!formIsValid) {
            e.preventDefault();
        }
    });

    function showError(input, message) {
        let errorElement = input.nextElementSibling;
        if (!errorElement || !errorElement.classList.contains('error-message')) {
            errorElement = document.createElement('div');
            errorElement.classList.add('error-message');
            errorElement.style.color = 'red';
            errorElement.style.fontSize = '0.9em';
            input.parentNode.insertBefore(errorElement, input.nextSibling);
        }
        errorElement.textContent = message;
    }
});

Ce petit script empêche l’envoi du formulaire si il y a une ou des erreurs. C’est une sécurité de base, mais utile pour améliorer l’expérience. Ce script JavaScript permet de :

  • vérifier que le pseudo n’est pas vide et contient au moins 4 caractères ;
  • vérifier que le mail est au bon format ;
  • vérifier que le niveau a été sélectionné ;
  • vérifier que les deux mots de passe sont identiques ;
  • vérifier que le mot de passe contient au moins 1 majuscule, 1 chiffre, et 8 caractères ;
  • afficher un message sous chaque champ en cas d’erreur.

Et dans le fichier de la vue signin, nous ajoutons le code ci-dessous pour relier notre script JS au formulaire. Il faut insérer la ligne de code suivante, après le formulaire HTML, dans le fichier app/Views/signin.php :

<script src="<?= BASE_URL ?>assets/js/signin-form-validation.js" defer></script>

4. Création du contrôleur SigninController

Dans notre dossier app/Controllers, nous allons créer un contrôleur dédié à la gestion de cette page. C’est lui qui sera appelé pour afficher la vue Signin.

Fichier : app/Controllers/SigninController.php

<?php

namespace App\Controllers;

use App\Models\User;
use App\Core\Controller;
use App\Core\View;
use App\Core\Database;


class SigninController extends Controller
{
    public function index()
    {
        
        if (isset($_SESSION['user'])) {
            header('Location: '.BASE_URL.'dashboard');
            exit;
        }
        
        $title = "Signin - Créa-code";
        $desc = "Signin - Créa-code";

        View::render('signin', compact('title', 'desc'));
    }

    public function register()
    {
        
        if (isset($_SESSION['user'])) {
            header('Location: '.BASE_URL.'dashboard');
            exit;
        }
        
        if ($_SERVER['REQUEST_METHOD'] === 'POST') {

            $errors = [];
            $pseudo = trim($_POST['pseudo'] ?? '');
            $email = trim($_POST['email'] ?? '');
            $password = $_POST['password'] ?? '';
            $confirm = $_POST['confirm_password'] ?? '';
            $niveau = $_POST['niveau'] ?? '';

            // Vérification du pseudo
            if (empty($pseudo)) {
                $errors['pseudo'] = "Le pseudo est obligatoire.";
            } elseif (strlen($pseudo) < 4) {
                $errors['pseudo'] = "Le pseudo doit contenir au moins 4 caractères.";
            }

            // Vérification de l’email
            if (empty($email)) {
                $errors['email'] = "L'email est obligatoire.";
            } elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
                $errors['email'] = "L'adresse email est invalide.";
            }

            // Vérification du niveau
            $niveaux_valides = ['débutant', 'intermédiaire', 'expérimenté', 'professionnel'];
            if (empty($niveau)) {
                $errors['niveau'] = "Le niveau est obligatoire.";
            } elseif (!in_array($niveau, $niveaux_valides)) {
                $errors['niveau'] = "Le niveau choisi est invalide.";
            }

            // Vérification du mot de passe
            if (empty($password)) {
                $errors['password'] = "Le mot de passe est obligatoire.";
            } elseif (!preg_match('/^(?=.*[A-Z])(?=.*\d).{8,}$/', $password)) {
                $errors['password'] = "Le mot de passe doit contenir au moins 8 caractères, une majuscule et un chiffre.";
            }

            // Vérification de la confirmation du mot de passe
            if ($password !== $confirm) {
                $errors['confirm'] = "Les mots de passe ne correspondent pas.";
            }

            // Si erreurs, retour à la vue signin avec les messages
            if (!empty($errors)) {
                // Stocker les erreurs en session flash ou variable
                $_SESSION['errors'] = $errors;
                $_SESSION['old'] = $_POST;

                header('Location: '.BASE_URL.'signin');
                exit;
            }

            $userModel = new User(Database::getInstance());

            $existCheck = $userModel->checkExistingEmailAndPseudo(strtolower($email), $pseudo);

            if ($existCheck['email'] || $existCheck['pseudo']) {
                if ($existCheck['email']) {
                    $errors['email'] = "Cette adresse email est déjà utilisée.";
                }
                if ($existCheck['pseudo']) {
                    $errors['pseudo'] = "Ce pseudo est déjà utilisé.";
                }

                $_SESSION['errors'] = $errors;
                $_SESSION['old'] = $_POST;
                header('Location: ' . BASE_URL . 'signin');
                exit;
            }

            // Si tout est bon, hacher le mot de passe
            $hashedPassword = password_hash($password, PASSWORD_DEFAULT);

            // Insertion en base de données
            $userModel = new User(Database::getInstance());
            $userModel->create([
                'email' => $email,
                'password' => $hashedPassword,
                'pseudo' => $pseudo,
                'prenom' => null,
                'nom' => null,
                'niveau' => $niveau,
                'bio' => null,
                'github_url' => null,
                'twitter_url' => null,
                'instagram_url' => null,
                'role' => 'user',
                'avatar_id' => null,
                'xp' => 0,
                'date_inscription' => date('Y-m-d H:i:s'),
                'last_login' => date('Y-m-d H:i:s'),
                'is_active' => 0,
                'status' => 'actif',
                'verification_token' => null,
                'reset_token' => null,
                'reset_token_expiry' => null,
            ]);

            // Création de la session utilisateur
            $_SESSION['user'] = [
                'pseudo' => $pseudo,
                'email' => $email,
                'role' => 'user'
            ];

            // Redirection vers le dashboard
            header('Location: '.BASE_URL.'dashboard');
            exit;
        }
    }
}

La méthode register() sécurise les données reçues, crypte le mot de passe, insère l’utilisateur via le modèle, et redirige vers le dashboard en cas de succès. Sinon, elle réaffiche la vue avec un message d’erreur.

Règles de validation à respecter côté PHP :

  • Pseudo : obligatoire, minimum 4 caractères
  • Email : obligatoire, format valide
  • Niveau : obligatoire, valeur dans la liste : ['débutant', 'intermédiaire', 'expérimenté', 'professionnel']
  • Mot de passe : obligatoire, confirmation identique, au moins 8 caractères, 1 majuscule, 1 chiffre

5. Le modèle User

Assurons-nous que le modèle User possède une méthode create() capable d’enregistrer un nouvel utilisateur. Mais avant, pour faciliter le déboggage en mode développement, nous allons ajouter quelques constantes au fichier config.php :

<?php

// Informations de connexion à la base de données (à adapter plus tard)
define('DB_HOST', 'localhost');
define('DB_NAME', 'creacode');
define('DB_USER', 'root');
define('DB_PASS', 'root');

define('BASE_URL', '/public/');

define('APP_NAME', 'Créa-code');

define('DEVELOPMENT', true); // Mettre à false en production
define('APP_VERSION', '1.0.0');
define('APP_AUTHOR', 'Guillier Alban');
define('APP_DESCRIPTION', 'Créa-code est une plateforme de partage de connaissances et de projets de développement web.');

Fichier : app/Models/User.php

<?php
namespace App\Models;

use App\Core\Database;
use PDO;
use PDOException;

class User extends BaseModel
{
    protected string $table = 'users';

    public function checkExistingEmailAndPseudo($email, $pseudo): array
    {
        $sql = "SELECT email, pseudo FROM users WHERE email = :email OR pseudo = :pseudo";
        $stmt = $this->db->prepare($sql);
        $stmt->execute(['email' => $email, 'pseudo' => $pseudo]);
        $results = $stmt->fetchAll(PDO::FETCH_ASSOC);

        $emailExists = false;
        $pseudoExists = false;

        foreach ($results as $row) {
            if ($row['email'] === $email) {
                $emailExists = true;
            }
            if ($row['pseudo'] === $pseudo) {
                $pseudoExists = true;
            }
        }

        return [
            'email' => $emailExists,
            'pseudo' => $pseudoExists
        ];
    }


    public function create($data)
    {
        $db = $this->db;

        $sql = "INSERT INTO users (
            email,
            password,
            pseudo,
            prenom,
            nom,
            niveau,
            bio,
            github_url,
            twitter_url,
            instagram_url,
            role,
            avatar_id,
            xp,
            date_inscription,
            last_login,
            is_active,
            status,
            verification_token,
            reset_token,
            reset_token_expiry
        ) VALUES (
            :email,
            :password,
            :pseudo,
            :prenom,
            :nom,
            :niveau,
            :bio,
            :github_url,
            :twitter_url,
            :instagram_url,
            :role,
            :avatar_id,
            :xp,
            :date_inscription,
            :last_login,
            :is_active,
            :status,
            :verification_token,
            :reset_token,
            :reset_token_expiry
        )";

        $stmt = $db->prepare($sql);

        // Debug
        if (!$stmt && DEVELOPMENT == true) {
            die("Erreur de préparation SQL : " . implode(" | ", $db->errorInfo()));
        }

        try {
            $stmt->execute([
                'email' => $data['email'],
                'password' => $data['password'],
                'pseudo' => $data['pseudo'],
                'prenom' => $data['prenom'],
                'nom' => $data['nom'],
                'niveau' => $data['niveau'],
                'bio' => $data['bio'],
                'github_url' => $data['github_url'],
                'twitter_url' => $data['twitter_url'],
                'instagram_url' => $data['instagram_url'],
                'role' => $data['role'],
                'avatar_id' => $data['avatar_id'],
                'xp' => $data['xp'],
                'date_inscription' => $data['date_inscription'],
                'last_login' => $data['last_login'],
                'is_active' => $data['is_active'],
                'status' => $data['status'],
                'verification_token' => $data['verification_token'],
                'reset_token' => $data['reset_token'],
                'reset_token_expiry' => $data['reset_token_expiry']
            ]);

            $data['id'] = $db->lastInsertId();
            return $data;
        } catch (\PDOException $e) {
            // Pour le debug uniquement, sinon loggez proprement
            if (DEVELOPMENT == true) {
                echo $e->getMessage(); 
            }
            return false;
        }
    }
}

Ce modèle interagit avec la base de données et renvoie les données de l’utilisateur si l’insertion a réussi.

Nous avons déjà mis en place la vue signin, le formulaire, le contrôleur, et le modèle pour insérer les utilisateurs. Il est temps maintenant de finaliser l’inscription avec :

  • la création de la page dashboard accessible uniquement aux inscrits
  • la gestion de session
  • le système de déconnexion
  • et les routes nécessaires

6. Création de la vue dashboard.php

Nous allons créer une nouvelle vue dashboard.php dans app/Views/dashboard/.

Cette page est privée, elle ne s’affiche que si l’utilisateur est connecté. Elle doit afficher un message personnalisé avec le pseudo ou l’email de l’utilisateur inscrit.

Formation web et informatique - Alban Guillier - Formateur

Des formations informatique pour tous !

Débutant ou curieux ? Apprenez le développement web, le référencement, le webmarketing, la bureautique, à maîtriser vos appareils Apple et bien plus encore…

Formateur indépendant, professionnel du web depuis 2006, je vous accompagne pas à pas et en cours particulier, que vous soyez débutant ou que vous souhaitiez progresser. En visio, à votre rythme, et toujours avec pédagogie.

Découvrez mes formations Qui suis-je ?

Fichier : app/Views/dashboard/dashboard.php

<h1>Bienvenue sur votre Dashboard</h1>

<p>Bonjour <?= htmlspecialchars($_SESSION['user']['pseudo']) ?>, vous êtes bien connecté !</p>

Simple, clair, et efficace. On affiche le pseudo depuis la session créée juste après l’inscription. Nous la complèterons dans quelques jours.

7. Création du contrôleur DashboardController

Dans app/Controllers, créons un nouveau contrôleur pour afficher le dashboard, uniquement si l’utilisateur est connecté.

Fichier : app/Controllers/DashboardController.php

<?php

namespace App\Controllers;

use App\Core\Controller;
use App\Core\View;

class DashboardController extends Controller
{
    public function index()
    {
        if (!isset($_SESSION['user'])) {
            header('Location: '.BASE_URL.'signin');
            exit;
        }

        $title = "Dashboard - Créa-code";
        $desc = "Dashboard - Créa-code";

        View::render('dashboard/dashboard', compact('title', 'desc'));

    }
}

Avant d’afficher la vue, on vérifie si la clé user existe dans la session. Si ce n’est pas le cas, on redirige vers la page d’inscription.

Ce petit mécanisme est un début de middleware maison. On pourra le généraliser plus tard avec un AuthMiddleware.

8. Création du contrôleur LogoutController

Encore une fois dans app/Controllers, nous allons créer un fichier LogoutController.php.

Il sera très simple. Il supprimera la session utilisateur, et redirigera vers la page de connexion.

Fichier : app/Controllers/LogoutController.php

<?php

namespace App\Controllers;

class LogoutController
{
    public function index()
    {
        session_unset();
        session_destroy();
        header('Location: '.BASE_URL.'login');
        exit;
    }
}

Ce fichier suffit à nous déconnecter proprement.

9. Création du contrôleur et de la vue de la page connexion

Sans le remplir ou lui donner de réelles fonctions, nous allons créer un contrôleur et une vue pour la page de login. Demain, nous les complèterons.

Fichier : app/Controllers/LoginController.php

<?php
namespace App\Controllers;

use App\Core\Controller;
use App\Core\View;


class LoginController extends Controller
{
    public function index(): void
    {

        if (isset($_SESSION['user'])) {
            header('Location: '.BASE_URL.'dashboard');
            exit;
        }

        $title = "Login - Créa-code";
        $desc = "Login - Créa-code";
        
        View::render('login', compact('title', 'desc'));

    }
}

Fichier : app/Views/login

<h1>Connexion</h1>
<p>Bientôt le formulaire</p>

10. Ajout des routes dans Routes.php et ajoute de la méthode post dans Router.php

Maintenant que tout est prêt, ajoutons les routes nécessaires dans notre fichier app/Route/Routes.php et la méthode post() dans app/Route/Router.php.

Voici un exemple d’ajout si vous utilisez un système de routage basique :

Fichier : app/Route/Routes.php

<?php

use App\Route\Router;

$router = new Router();

// Enregistrement des middlewares
$router->registerMiddleware('auth', function () {
    if (!isset($_SESSION['user'])) {
        header('Location: /creacode/public/login');
        return false;
    }
    return true;
});

$router->registerMiddleware('admin', function () {
    if (!isset($_SESSION['user']) || $_SESSION['user']['role'] !== 'admin') {
        http_response_code(403);
        echo "Accès réservé aux administrateurs.";
        return false;
    }
    return true;
});

// Routes

// Accueil
$router->get('', 'HomeController@index');

// Connexion et inscription
$router->get('/signin', 'SigninController@index');
$router->post('/register', 'SigninController@register');
$router->get('/login', 'LoginController@index');
$router->get('/logout', 'LogoutController@index');

$router->get('/dashboard', 'DashboardController@index')->middleware('auth');

$router->dispatch();

Cela signifie :

  • GET /signin affiche le formulaire,
  • POST /register traite l’inscription,
  • GET /dashboard affiche l’espace privé,
  • GET /logout déconnecte l’utilisateur.

Fichier : app/Route/Router.php

<?php

namespace App\Route;

use App\Core\View;

class Router
{
    protected $routes = [];
    protected $middlewares = [];
    protected $registeredMiddlewares = [];

    protected function addRoute(string $method, string $path, $controllerMethod)
    {
        $path = trim($path, '/');
        $this->routes[$method][] = [
            'path' => $path,
            'controllerMethod' => $controllerMethod,
            'middlewares' => []
        ];
        return $this;
    }

    public function get($path, $controllerMethod)
    {
        return $this->addRoute('GET', $path, $controllerMethod);
    }

    public function post($path, $controllerMethod)
    {
        return $this->addRoute('POST', $path, $controllerMethod);
    }

    public function middleware(...$middlewares)
    {
        $lastIndex = array_key_last($this->routes['GET']);
        foreach ($middlewares as $mw) {
            $this->routes['GET'][$lastIndex]['middlewares'][] = $mw;
        }
        return $this;
    }

    public function registerMiddleware(string $name, callable $callback)
    {
        $this->registeredMiddlewares[$name] = $callback;
    }

    public function dispatch()
    {
        $method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
        $basePath = '/creacode/public'; // Adapter si besoin
        $requestUri = $_SERVER['REQUEST_URI'] ?? '/';
        $path = parse_url($requestUri, PHP_URL_PATH);

        // Retirer le chemin de base (si présent)
        if (strpos($path, $basePath) === 0) {
            $path = substr($path, strlen($basePath));
        }

        $url = trim($path, '/');

        // Éviter d’intercepter les fichiers statiques (CSS, JS, images...)
        $extension = pathinfo($url, PATHINFO_EXTENSION);
        $staticExtensions = ['css', 'js', 'png', 'jpg', 'jpeg', 'gif', 'svg', 'ico', 'webp'];

        if (in_array($extension, $staticExtensions)) {
            return false; // Laisser Apache/Nginx ou le serveur PHP servir le fichier
        }

        if (!isset($this->routes[$method])) {
            return $this->render404();
        }

        foreach ($this->routes[$method] as $route) {
            // Gestion des routes dynamiques
            $pattern = preg_replace('#\{[a-zA-Z0-9_]+\}#', '([a-zA-Z0-9_-]+)', $route['path']);
            $pattern = '#^' . $pattern . '$#';

            if (preg_match($pattern, $url, $matches)) {
                array_shift($matches); // Supprimer l’URL complète de $matches

                // Exécution des middlewares
                foreach ($route['middlewares'] as $mwName) {
                    if (!isset($this->registeredMiddlewares[$mwName])) {
                        throw new \Exception("Middleware '$mwName' non enregistré.");
                    }

                    $result = call_user_func($this->registeredMiddlewares[$mwName]);

                    if ($result === false) {
                        return; // Middleware a bloqué l’accès
                    }
                }

                return $this->callController($route['controllerMethod'], $matches);
            }
        }

        return $this->render404();
    }

    protected function callController(string $controllerMethod, array $params = [])
    {
        [$controllerName, $methodName] = explode('@', $controllerMethod);
        $controllerClass = 'App\\Controllers\\' . $controllerName;

        if (class_exists($controllerClass)) {
            $controller = new $controllerClass();

            if (method_exists($controller, $methodName)) {
                return call_user_func_array([$controller, $methodName], $params);
            }
        }

        return $this->render404();
    }

    protected function render404()
    {
        http_response_code(404);
        return View::render('error', [], 'layout');
    }
}

11. Test final

Prenons un moment pour tester le parcours complet :

  1. Aller sur la page et vérifier qu’elle s’affiche bien /signin
  2. Remplir le formulaire et cliquer sur « S’inscrire » en testant une première fois avec des fautes pour vérifier les messages d’erreur ✔
  3. Si tout est correct, les informations sont enregistrées dans la tables users et nous sommes redirigés vers /dashboard
  4. Notre pseudo s’affiche dans un message personnalisé ✔
  5. Dans le menu, le lien « Déconnexion » apparaît ✔
  6. En cliquant sur « Déconnexion », nous sommes redirigés vers le page de login ✔
  7. Si nous tentons d’accéder à /dashboard sans être connecté, nous sommes redirigés vers /signin

Nous venons de construire un vrai mécanisme d’inscription sécurisé en MVC, avec :

  • des vérifications JavaScript et PHP,
  • le hachage du mot de passe,
  • la gestion de session,
  • la redirection,
  • la protection des pages privées,
  • et une structure claire, modulaire, réutilisable.

Ce quinzième jour est un nouveau cap dans votre apprentissage. Nous ne faisons plus de simples formulaires ou pages statiques. Nous entrons dans le monde des applications web avec une logique de sessions, de rôles et d’accès protégés.

Et surtout, tout est construit proprement, dans une architecture MVC simple mais solide. Notre site commence, petit à petit, à ressembler à une vraie plateforme. Demain, on attaque le formulaire de connexion !

Live on Twitch