retour
01/03
section 01

Validation cote serveur

Connectez-vous pour sauvegarder votre progression et acceder aux quiz
Se connecter

La validation cote serveur est la seule vraie validation. La validation JavaScript (cote client) ameliore l'experience utilisateur mais peut etre contournee. Le serveur est le dernier rempart.

Classe de validation

php<?php
namespace App\Service;

class Validator
{
    private array $errors = [];

    public function validate(array $data, array $rules): bool
    {
        $this->errors = [];

        foreach ($rules as $field => $fieldRules) {
            $value = $data[$field] ?? null;

            foreach ($fieldRules as $rule) {
                $error = $this->checkRule($field, $value, $rule);
                if ($error !== null) {
                    $this->errors[$field][] = $error;
                }
            }
        }

        return empty($this->errors);
    }

    private function checkRule(string $field, mixed $value,
        string $rule): ?string
    {
        // required
        if ($rule === 'required' && (is_null($value)
            || trim((string)$value) === '')) {
            return "Le champ $field est obligatoire.";
        }

        // email
        if ($rule === 'email' && $value !== null
            && !filter_var($value, FILTER_VALIDATE_EMAIL)) {
            return "Le champ $field doit etre un email valide.";
        }

        // min:N
        if (str_starts_with($rule, 'min:')) {
            $min = (int) substr($rule, 4);
            if ($value !== null && strlen((string)$value) < $min) {
                return "Le champ $field doit faire au moins $min caracteres.";
            }
        }

        // max:N
        if (str_starts_with($rule, 'max:')) {
            $max = (int) substr($rule, 4);
            if ($value !== null && strlen((string)$value) > $max) {
                return "Le champ $field ne doit pas depasser $max caracteres.";
            }
        }

        // numeric
        if ($rule === 'numeric' && $value !== null
            && !is_numeric($value)) {
            return "Le champ $field doit etre un nombre.";
        }

        return null;
    }

    public function getErrors(): array
    {
        return $this->errors;
    }

    public function getFirstError(string $field): ?string
    {
        return $this->errors[$field][0] ?? null;
    }
}

Utilisation dans un formulaire d'inscription

php<?php
session_start();

$validator = new Validator();
$errors = [];
$old = [];

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $old = $_POST;  // Garder les valeurs pour re-remplir le formulaire

    $isValid = $validator->validate($_POST, [
        'nom'      => ['required', 'min:2', 'max:100'],
        'email'    => ['required', 'email'],
        'password' => ['required', 'min:8'],
        'confirm'  => ['required'],
    ]);

    // Validation supplementaire
    if ($_POST['password'] !== $_POST['confirm']) {
        $errors['confirm'] = 'Les mots de passe ne correspondent pas.';
        $isValid = false;
    }

    if ($isValid && empty($errors)) {
        // Nettoyer les donnees
        $nom = htmlspecialchars(trim($_POST['nom']));
        $email = filter_var($_POST['email'], FILTER_SANITIZE_EMAIL);
        $password = password_hash($_POST['password'], PASSWORD_DEFAULT);

        // Verifier que l'email n'existe pas deja
        $stmt = $pdo->prepare(
            "SELECT id FROM users WHERE email = :email"
        );
        $stmt->execute(['email' => $email]);
        if ($stmt->fetch()) {
            $errors['email'] = 'Cet email est deja utilise.';
        } else {
            // Inserer en BDD
            $stmt = $pdo->prepare(
                "INSERT INTO users (nom, email, password)
                 VALUES (:nom, :email, :password)"
            );
            $stmt->execute([
                'nom' => $nom,
                'email' => $email,
                'password' => $password,
            ]);

            $_SESSION['success'] = 'Inscription reussie !';
            header('Location: login.php');
            exit;
        }
    } else {
        $errors = array_merge($errors, $validator->getErrors());
    }
}
?>

<form method="POST" novalidate>
    <div>
        <label for="nom">Nom</label>
        <input type="text" name="nom" id="nom"
               value="<?= htmlspecialchars($old['nom'] ?? '') ?>"
               required minlength="2">
        <?php if (isset($errors['nom'])): ?>
            <p class="error">
                <?= htmlspecialchars(
                    is_array($errors['nom'])
                        ? $errors['nom'][0]
                        : $errors['nom']
                ) ?>
            </p>
        <?php endif; ?>
    </div>

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

    <div>
        <label for="password">Mot de passe</label>
        <input type="password" name="password" id="password"
               required minlength="8">
    </div>

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

    <button type="submit">S'inscrire</button>
</form>
Pattern PRG : Apres un POST reussi, redirigez toujours avec header('Location: ...') suivi de exit. C'est le pattern Post/Redirect/Get qui empeche la re-soumission du formulaire en cas de rechargement de page (F5).