Projet : Carnet de contacts PHP

Projet : Carnet de contacts

🎯 Ce que tu vas construire

Un carnet de contacts complet, avec :

  • ✅ Site modulaire (header/nav/footer partagés)
  • ✅ Formulaire d'ajout avec validation
  • ✅ Persistance en JSON (sans base de données)
  • ✅ Liste des contacts avec recherche
  • ✅ Suppression d'un contact
  • ✅ Interface Bootstrap 5 propre

Ce projet valide les blocs 1 à 4 du cours.


Structure du projet

carnet-contacts/
├── data/
│   └── contacts.json          ← créé automatiquement
├── includes/
│   ├── header.php
│   ├── nav.php
│   └── footer.php
├── index.php                  ← redirige vers liste.php
├── liste.php                  ← affiche tous les contacts
├── ajouter.php                ← formulaire d'ajout
├── supprimer.php              ← traitement suppression (pas de vue)
└── fonctions.php              ← fonctions partagées

Le fichier de données

data/contacts.json — créé automatiquement au premier ajout :

[
    {
        "id": "1713100800",
        "nom": "Alice Dupont",
        "email": "alice@example.be",
        "telephone": "0474 12 34 56",
        "groupe": "Amis",
        "date_ajout": "14/04/2025 à 16:00"
    }
]

fonctions.php — Fonctions utilitaires

<?php

// Chemin vers le fichier de données
define('FICHIER_CONTACTS', __DIR__ . '/data/contacts.json');

/**
 * Lire tous les contacts depuis le fichier JSON
 * Retourne un tableau vide si le fichier n'existe pas
 */
function lireContacts(): array {
    if (!file_exists(FICHIER_CONTACTS)) {
        return [];
    }
    $contenu = file_get_contents(FICHIER_CONTACTS);
    return json_decode($contenu, true) ?? [];
}

/**
 * Sauvegarder le tableau de contacts dans le fichier JSON
 */
function sauvegarderContacts(array $contacts): void {
    file_put_contents(
        FICHIER_CONTACTS,
        json_encode($contacts, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
    );
}

/**
 * Trouver un contact par son id
 * Retourne le contact ou null si non trouvé
 */
function trouverContact(string $id): ?array {
    foreach (lireContacts() as $contact) {
        if ($contact['id'] === $id) {
            return $contact;
        }
    }
    return null;
}

define() crée une constante globale — accessible partout sans variable.
: array, : void, : ?array → indications de type (PHP 7+), bonne pratique.


liste.php

<?php
require 'fonctions.php';

$pageTitle = "Mes contacts";
require 'includes/header.php';
require 'includes/nav.php';

// Lecture des contacts
$contacts = lireContacts();

// Recherche par nom (filtre si ?q= est dans l'URL)
$recherche = trim($_GET['q'] ?? '');
if (!empty($recherche)) {
    $contacts = array_filter($contacts, function($c) use ($recherche) {
        return stripos($c['nom'], $recherche) !== false
            || stripos($c['email'], $recherche) !== false;
    });
}

// Trier par nom
usort($contacts, fn($a, $b) => strcmp($a['nom'], $b['nom']));
?>

<main class="container mt-5">

    <div class="d-flex justify-content-between align-items-center mb-4">
        <h1>📋 Carnet de contacts
            <span class="badge bg-secondary"><?= count($contacts) ?></span>
        </h1>
        <a href="ajouter.php" class="btn btn-success">+ Ajouter</a>
    </div>

    <!-- Barre de recherche -->
    <form method="GET" action="" class="mb-4">
        <div class="input-group">
            <input type="text"
                   name="q"
                   class="form-control"
                   placeholder="Rechercher par nom ou email..."
                   value="<?= htmlspecialchars($recherche) ?>">
            <button class="btn btn-outline-secondary" type="submit">🔍</button>
            <?php if ($recherche) : ?>
                <a href="liste.php" class="btn btn-outline-danger"></a>
            <?php endif; ?>
        </div>
    </form>

    <!-- Messages flash -->
    <?php if (isset($_GET['added'])) : ?>
        <div class="alert alert-success">✅ Contact ajouté avec succès !</div>
    <?php endif; ?>
    <?php if (isset($_GET['deleted'])) : ?>
        <div class="alert alert-warning">🗑️ Contact supprimé.</div>
    <?php endif; ?>

    <!-- Liste des contacts -->
    <?php if (empty($contacts)) : ?>
        <div class="alert alert-info">
            <?= $recherche ? "Aucun résultat pour "$recherche"." : "Aucun contact. <a href='ajouter.php'>Ajouter le premier !</a>" ?>
        </div>
    <?php else : ?>

        <div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-3">
            <?php foreach ($contacts as $c) : ?>
                <div class="col">
                    <div class="card h-100 shadow-sm">
                        <div class="card-body">
                            <h5 class="card-title">
                                <?= htmlspecialchars($c['nom']) ?>
                                <?php if (!empty($c['groupe'])) : ?>
                                    <span class="badge bg-info text-dark float-end">
                                        <?= htmlspecialchars($c['groupe']) ?>
                                    </span>
                                <?php endif; ?>
                            </h5>
                            <p class="card-text mb-1">
                                📧 <a href="mailto:<?= htmlspecialchars($c['email']) ?>">
                                    <?= htmlspecialchars($c['email']) ?>
                                </a>
                            </p>
                            <?php if (!empty($c['telephone'])) : ?>
                                <p class="card-text mb-1">📞 <?= htmlspecialchars($c['telephone']) ?></p>
                            <?php endif; ?>
                            <small class="text-muted">Ajouté le <?= $c['date_ajout'] ?></small>
                        </div>
                        <div class="card-footer text-end">
                            <!-- Suppression avec confirmation JS -->
                            <a href="supprimer.php?id=<?= $c['id'] ?>"
                               class="btn btn-sm btn-outline-danger"
                               onclick="return confirm('Supprimer <?= htmlspecialchars($c['nom']) ?> ?')">
                               🗑️ Supprimer
                            </a>
                        </div>
                    </div>
                </div>
            <?php endforeach; ?>
        </div>

    <?php endif; ?>
</main>

<?php require 'includes/footer.php'; ?>

ajouter.php

<?php
require 'fonctions.php';

$erreurs = [];

if ($_SERVER["REQUEST_METHOD"] === "POST") {

    // Récupération et nettoyage
    $nom       = trim($_POST['nom']       ?? '');
    $email     = trim($_POST['email']     ?? '');
    $telephone = trim($_POST['telephone'] ?? '');
    $groupe    = trim($_POST['groupe']    ?? '');

    // Validation
    if (empty($nom))                                    $erreurs[] = "Le nom est obligatoire.";
    if (empty($email))                                  $erreurs[] = "L'email est obligatoire.";
    elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) $erreurs[] = "L'email est invalide.";

    // Vérifier les doublons
    $contacts = lireContacts();
    foreach ($contacts as $c) {
        if (strtolower($c['email']) === strtolower($email)) {
            $erreurs[] = "Cet email est déjà dans le carnet.";
            break;
        }
    }

    // Sauvegarde si pas d'erreur
    if (empty($erreurs)) {
        $contacts[] = [
            "id"         => (string)time(),
            "nom"        => $nom,
            "email"      => $email,
            "telephone"  => $telephone,
            "groupe"     => $groupe,
            "date_ajout" => date("d/m/Y à H:i"),
        ];
        sauvegarderContacts($contacts);

        header("Location: liste.php?added=1");
        exit;
    }
}

$pageTitle = "Ajouter un contact";
require 'includes/header.php';
require 'includes/nav.php';
?>

<main class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">

    <h1>➕ Ajouter un contact</h1>

    <?php if (!empty($erreurs)) : ?>
        <div class="alert alert-danger">
            <ul class="mb-0">
                <?php foreach ($erreurs as $e) : ?>
                    <li><?= htmlspecialchars($e) ?></li>
                <?php endforeach; ?>
            </ul>
        </div>
    <?php endif; ?>

    <form method="POST" action="" class="mt-3">

        <div class="mb-3">
            <label class="form-label">Nom complet *</label>
            <input type="text" name="nom" class="form-control"
                   value="<?= htmlspecialchars($_POST['nom'] ?? '') ?>" required>
        </div>

        <div class="mb-3">
            <label class="form-label">Email *</label>
            <input type="email" name="email" class="form-control"
                   value="<?= htmlspecialchars($_POST['email'] ?? '') ?>" required>
        </div>

        <div class="mb-3">
            <label class="form-label">Téléphone</label>
            <input type="tel" name="telephone" class="form-control"
                   value="<?= htmlspecialchars($_POST['telephone'] ?? '') ?>"
                   placeholder="0474 00 00 00">
        </div>

        <div class="mb-3">
            <label class="form-label">Groupe</label>
            <select name="groupe" class="form-select">
                <option value="">— Aucun groupe —</option>
                <?php foreach (["Famille", "Amis", "Travail", "Clients", "Autre"] as $g) : ?>
                    <option value="<?= $g ?>"
                        <?= ($_POST['groupe'] ?? '') === $g ? 'selected' : '' ?>>
                        <?= $g ?>
                    </option>
                <?php endforeach; ?>
            </select>
        </div>

        <div class="d-flex gap-2">
            <button type="submit" class="btn btn-success">Enregistrer</button>
            <a href="liste.php" class="btn btn-outline-secondary">Annuler</a>
        </div>

    </form>
</div>
</div>
</main>

<?php require 'includes/footer.php'; ?>

supprimer.php — Traitement uniquement (pas de vue)

<?php
require 'fonctions.php';

// Récupérer l'id à supprimer
$id = $_GET['id'] ?? '';

if (!empty($id)) {
    // Lire tous les contacts
    $contacts = lireContacts();

    // Filtrer : garder tous sauf celui avec cet id
    $contacts = array_filter($contacts, function($c) use ($id) {
        return $c['id'] !== $id;
    });

    // Renuméroter les clés du tableau (array_filter conserve les anciens index)
    $contacts = array_values($contacts);

    // Sauvegarder
    sauvegarderContacts($contacts);
}

// Rediriger vers la liste dans tous les cas
header("Location: liste.php?deleted=1");
exit;

index.php — Redirection simple

<?php
// La page d'accueil redirige vers la liste des contacts
header("Location: liste.php");
exit;

📋 Grille d'évaluation

Critère Points
Structure modulaire (includes) fonctionnelle /3
Lecture du fichier JSON et affichage correct /3
Formulaire d'ajout avec validation /4
Sauvegarde dans contacts.json /3
Suppression fonctionnelle /3
Recherche par nom/email /2
Interface Bootstrap soignée /2
Total /20

🏆 Extensions possibles (pour aller plus loin)

Extension Compétence
Modifier un contact (page modifier.php?id=xxx) Formulaire pré-rempli
Exporter la liste en CSV fputcsv()
Importer des contacts depuis un fichier CSV fgetcsv()
Paginer la liste (10 contacts par page) array_slice()
Trier par groupe, par date usort()
Migrer vers MySQL + PDO Bloc suivant du cours

Pour aller plus loin