Maîtrisez les formulaires HTML/PHP pour créer des applications web interactives, de la création basique à la sécurisation avancée.
Comprenez la structure HTML fondamentale des formulaires avant d'ajouter la logique PHP.
Découvrez la différence entre GET et POST et comment récupérer les données envoyées par un formulaire en PHP.
Apprenez à valider et sécuriser les données de vos formulaires pour éviter les failles de sécurité.
Apprenez à gérer l'upload de fichiers de manière sécurisée avec PHP.
Créons un formulaire de contact complet avec validation, sécurisation et persistance des données.
<?php
/**
* Formulaire de Contact Avancé - Projet complet PHP
* Fonctionnalités : validation, sécurisation, CSRF, upload, persistance
*/
session_start();
// CONFIGURATION
$dossier_uploads = 'uploads/';
$fichier_contacts = 'contacts.json';
$extensions_autorisees = ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'doc', 'docx'];
$taille_max = 5 * 1024 * 1024; // 5MB
// FONCTIONS UTILITAIRES
function genererTokenCSRF() {
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
function verifierTokenCSRF($token) {
return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token);
}
function echapper($texte) {
return htmlspecialchars($texte, ENT_QUOTES, 'UTF-8');
}
function validerEmail($email) {
return filter_var($email, FILTER_VALIDATE_EMAIL);
}
function validerNom($nom) {
return preg_match('/^[a-zA-ZÀ-ÿ\s\'-]{2,50}$/', $nom);
}
function validerTelephone($tel) {
$pattern = '/^(?:(?:\+|00)33|0)\s*[1-9](?:[\s.-]*\d{2}){4}$/';
return preg_match($pattern, $tel);
}
// VARIABLES
$erreurs = [];
$succes = '';
$nom = $prenom = $email = $telephone = $sujet = $message = '';
$priorite = 'normale';
$newsletter = false;
$fichier_uploade = '';
// TRAITEMENT DU FORMULAIRE
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Vérification CSRF
$token = $_POST['csrf_token'] ?? '';
if (!verifierTokenCSRF($token)) {
$erreurs[] = "Token CSRF invalide. Veuillez recharger la page.";
} else {
// RÉCUPÉRATION ET NETTOYAGE DES DONNÉES
$nom = trim($_POST['nom'] ?? '');
$prenom = trim($_POST['prenom'] ?? '');
$email = trim($_POST['email'] ?? '');
$telephone = trim($_POST['telephone'] ?? '');
$sujet = trim($_POST['sujet'] ?? '');
$message = trim($_POST['message'] ?? '');
$priorite = $_POST['priorite'] ?? 'normale';
$newsletter = isset($_POST['newsletter']);
// VALIDATIONS
if (empty($nom)) {
$erreurs[] = "Le nom est obligatoire.";
} elseif (!validerNom($nom)) {
$erreurs[] = "Le nom doit contenir 2-50 caractères (lettres, espaces, apostrophes).";
}
if (empty($prenom)) {
$erreurs[] = "Le prénom est obligatoire.";
} elseif (!validerNom($prenom)) {
$erreurs[] = "Le prénom doit contenir 2-50 caractères (lettres, espaces, apostrophes).";
}
if (empty($email)) {
$erreurs[] = "L'email est obligatoire.";
} elseif (!validerEmail($email)) {
$erreurs[] = "L'adresse email n'est pas valide.";
}
if (!empty($telephone) && !validerTelephone($telephone)) {
$erreurs[] = "Le numéro de téléphone n'est pas valide.";
}
if (empty($sujet)) {
$erreurs[] = "Le sujet est obligatoire.";
} elseif (strlen($sujet) < 5 || strlen($sujet) > 100) {
$erreurs[] = "Le sujet doit contenir entre 5 et 100 caractères.";
}
if (empty($message)) {
$erreurs[] = "Le message est obligatoire.";
} elseif (strlen($message) < 20 || strlen($message) > 1000) {
$erreurs[] = "Le message doit contenir entre 20 et 1000 caractères.";
}
if (!in_array($priorite, ['faible', 'normale', 'haute'])) {
$priorite = 'normale';
}
// TRAITEMENT DE L'UPLOAD (OPTIONNEL)
if (isset($_FILES['piece_jointe']) && $_FILES['piece_jointe']['error'] === UPLOAD_ERR_OK) {
$fichier = $_FILES['piece_jointe'];
$nom_fichier = $fichier['name'];
$taille_fichier = $fichier['size'];
$fichier_temp = $fichier['tmp_name'];
// Validation du fichier
$extension = strtolower(pathinfo($nom_fichier, PATHINFO_EXTENSION));
if (!in_array($extension, $extensions_autorisees)) {
$erreurs[] = "Extension de fichier non autorisée. Extensions permises : " . implode(', ', $extensions_autorisees);
} elseif ($taille_fichier > $taille_max) {
$erreurs[] = "Le fichier est trop volumineux. Taille maximum : 5 MB.";
} else {
// Créer le dossier si nécessaire
if (!is_dir($dossier_uploads)) {
mkdir($dossier_uploads, 0755, true);
}
// Nom sécurisé
$nouveau_nom = uniqid('contact_') . '.' . $extension;
$chemin_destination = $dossier_uploads . $nouveau_nom;
if (move_uploaded_file($fichier_temp, $chemin_destination)) {
$fichier_uploade = $nouveau_nom;
} else {
$erreurs[] = "Erreur lors de l'upload du fichier.";
}
}
}
// SAUVEGARDE SI AUCUNE ERREUR
if (empty($erreurs)) {
$contact = [
'id' => uniqid(),
'date' => date('Y-m-d H:i:s'),
'nom' => $nom,
'prenom' => $prenom,
'email' => $email,
'telephone' => $telephone,
'sujet' => $sujet,
'message' => $message,
'priorite' => $priorite,
'newsletter' => $newsletter,
'fichier' => $fichier_uploade,
'ip' => $_SERVER['REMOTE_ADDR'] ?? 'inconnue'
];
// Lire les contacts existants
$contacts = [];
if (file_exists($fichier_contacts)) {
$json = file_get_contents($fichier_contacts);
$contacts = json_decode($json, true) ?: [];
}
// Ajouter le nouveau contact
$contacts[] = $contact;
// Sauvegarder
if (file_put_contents($fichier_contacts, json_encode($contacts, JSON_PRETTY_PRINT))) {
$succes = "Votre message a été envoyé avec succès ! Nous vous répondrons dans les plus brefs délais.";
// Réinitialiser le formulaire
$nom = $prenom = $email = $telephone = $sujet = $message = '';
$priorite = 'normale';
$newsletter = false;
// Régénérer le token CSRF
unset($_SESSION['csrf_token']);
} else {
$erreurs[] = "Erreur lors de la sauvegarde. Veuillez réessayer.";
}
}
}
}
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Formulaire de Contact Avancé - PHP</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 800px;
margin: 0 auto;
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #8b5cf6, #3b82f6);
color: white;
padding: 40px;
text-align: center;
}
.header h1 {
font-size: 2.5em;
margin-bottom: 10px;
}
.form-container {
padding: 40px;
}
.alert {
padding: 15px;
margin-bottom: 20px;
border-radius: 8px;
font-weight: 500;
}
.alert-success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.alert-error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.form-group {
margin-bottom: 25px;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #333;
}
.required {
color: #e74c3c;
}
input, select, textarea {
width: 100%;
padding: 12px 15px;
border: 2px solid #e1e5e9;
border-radius: 8px;
font-size: 16px;
transition: border-color 0.3s, box-shadow 0.3s;
}
input:focus, select:focus, textarea:focus {
outline: none;
border-color: #8b5cf6;
box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.1);
}
.checkbox-group {
display: flex;
align-items: center;
gap: 10px;
}
.checkbox-group input[type="checkbox"] {
width: auto;
}
.priority-group {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
}
.priority-option {
display: flex;
align-items: center;
gap: 8px;
padding: 10px;
border: 2px solid #e1e5e9;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s;
}
.priority-option:hover {
border-color: #8b5cf6;
background: #f8f7ff;
}
.priority-option input[type="radio"] {
width: auto;
}
.priority-option.faible { border-color: #10b981; }
.priority-option.normale { border-color: #3b82f6; }
.priority-option.haute { border-color: #ef4444; }
.file-input {
position: relative;
overflow: hidden;
display: inline-block;
cursor: pointer;
background: #f8f9fa;
border: 2px dashed #dee2e6;
border-radius: 8px;
padding: 30px;
text-align: center;
width: 100%;
transition: all 0.3s;
}
.file-input:hover {
border-color: #8b5cf6;
background: #f8f7ff;
}
.file-input input[type="file"] {
position: absolute;
left: -9999px;
}
.submit-btn {
background: linear-gradient(135deg, #8b5cf6, #3b82f6);
color: white;
padding: 15px 40px;
border: none;
border-radius: 10px;
font-size: 18px;
font-weight: 600;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.3s;
width: 100%;
}
.submit-btn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(139, 92, 246, 0.3);
}
.stats {
background: #f8f9fa;
padding: 20px;
border-radius: 8px;
margin-top: 30px;
text-align: center;
color: #666;
font-size: 14px;
}
@media (max-width: 768px) {
.form-row {
grid-template-columns: 1fr;
}
.priority-group {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>📧 Contact Avancé</h1>
<p>Formulaire complet avec validation, sécurisation et upload</p>
</div>
<div class="form-container">
<?php if ($succes): ?>
<div class="alert alert-success">
✅ <?= echapper($succes) ?>
</div>
<?php endif; ?>
<?php if (!empty($erreurs)): ?>
<div class="alert alert-error">
❌ <strong>Erreurs détectées :</strong>
<ul style="margin-top: 10px; padding-left: 20px;">
<?php foreach ($erreurs as $erreur): ?>
<li><?= echapper($erreur) ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<form method="POST" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="<?= genererTokenCSRF() ?>">
<div class="form-row">
<div class="form-group">
<label for="nom">Nom <span class="required">*</span></label>
<input type="text" id="nom" name="nom" value="<?= echapper($nom) ?>"
placeholder="Votre nom de famille" required>
</div>
<div class="form-group">
<label for="prenom">Prénom <span class="required">*</span></label>
<input type="text" id="prenom" name="prenom" value="<?= echapper($prenom) ?>"
placeholder="Votre prénom" required>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="email">Email <span class="required">*</span></label>
<input type="email" id="email" name="email" value="<?= echapper($email) ?>"
placeholder="votre@email.com" required>
</div>
<div class="form-group">
<label for="telephone">Téléphone</label>
<input type="tel" id="telephone" name="telephone" value="<?= echapper($telephone) ?>"
placeholder="01 23 45 67 89">
</div>
</div>
<div class="form-group">
<label for="sujet">Sujet <span class="required">*</span></label>
<input type="text" id="sujet" name="sujet" value="<?= echapper($sujet) ?>"
placeholder="Objet de votre message" required>
</div>
<div class="form-group">
<label>Priorité</label>
<div class="priority-group">
<label class="priority-option faible">
<input type="radio" name="priorite" value="faible" <?= $priorite === 'faible' ? 'checked' : '' ?>>
🟢 Faible
</label>
<label class="priority-option normale">
<input type="radio" name="priorite" value="normale" <?= $priorite === 'normale' ? 'checked' : '' ?>>
🔵 Normale
</label>
<label class="priority-option haute">
<input type="radio" name="priorite" value="haute" <?= $priorite === 'haute' ? 'checked' : '' ?>>
🔴 Haute
</label>
</div>
</div>
<div class="form-group">
<label for="message">Message <span class="required">*</span></label>
<textarea id="message" name="message" rows="6"
placeholder="Votre message détaillé (20-1000 caractères)" required><?= echapper($message) ?></textarea>
</div>
<div class="form-group">
<label>Pièce jointe (optionnel)</label>
<div class="file-input">
<input type="file" id="piece_jointe" name="piece_jointe"
accept=".jpg,.jpeg,.png,.gif,.pdf,.doc,.docx">
📎 Cliquez pour choisir un fichier<br>
<small>Formats acceptés : JPG, PNG, GIF, PDF, DOC, DOCX (max 5MB)</small>
</div>
</div>
<div class="form-group">
<div class="checkbox-group">
<input type="checkbox" id="newsletter" name="newsletter" value="1"
<?= $newsletter ? 'checked' : '' ?>>
<label for="newsletter">📧 Je souhaite recevoir la newsletter</label>
</div>
</div>
<button type="submit" class="submit-btn">
📤 Envoyer le message
</button>
</form>
<div class="stats">
<strong>Fonctionnalités intégrées :</strong><br>
✅ Validation côté serveur • 🛡️ Protection CSRF • 📎 Upload de fichiers •
💾 Sauvegarde JSON • 🔒 Sécurisation XSS • 📱 Design responsive
</div>
</div>
</div>
<script>
// Affichage du nom du fichier sélectionné
document.getElementById('piece_jointe').addEventListener('change', function(e) {
const fileName = e.target.files[0]?.name || 'Aucun fichier sélectionné';
const fileInput = e.target.closest('.file-input');
fileInput.innerHTML = `📎 ${fileName}<br><small>Cliquez pour changer</small>`;
fileInput.appendChild(e.target);
});
// Compteur de caractères pour le message
document.getElementById('message').addEventListener('input', function(e) {
const count = e.target.value.length;
const max = 1000;
const min = 20;
let color = '#666';
if (count < min) color = '#ef4444';
else if (count > max * 0.9) color = '#f59e0b';
else color = '#10b981';
// Afficher le compteur (si vous voulez l'ajouter)
console.log(`Caractères: ${count}/${max}`);
});
</script>
</body>
</html>
mkdir contact_avance && cd contact_avance
contact_complet.php
mkdir uploads (permissions 755)
php -S localhost:8000
http://localhost:8000/contact_complet.php
Email, nom, téléphone, longueur des messages
Protection CSRF, XSS, validation stricte
Images, PDF, documents avec validation taille/type
Sauvegarde des contacts avec métadonnées
Design responsive, animations, feedback utilisateur
Vous savez maintenant créer des formulaires sécurisés et interactifs. Passez à l'étape suivante : les bases de données !
Structure HTML • Méthodes GET/POST • Validation PHP • Sécurisation XSS/CSRF • Upload de fichiers • Gestion d'erreurs • Persistance des données