Base de Données

MySQL & PHP

Maîtrisez MySQL avec PHP, découvrez VSCode pour le développement et Adminer pour l'administration. Du débutant à l'expert en base de données.

🗄️ Base de Données
💻 VSCode
🔧 Adminer
🚀 Performance

Installation et Configuration MySQL

Installation complète de MySQL, configuration PHP et première connexion.

📦 Installation MySQL et Configuration PHP

Installation Windows :

# 1. Télécharger XAMPP/WAMP/MAMP
https://www.apachefriends.org/download.html
# 2. Ou installer MySQL séparément
https://dev.mysql.com/downloads/installer/
# 3. Démarrer les services
net start mysql
net start apache2

Installation Linux :

# Ubuntu/Debian
sudo apt update
sudo apt install mysql-server php php-mysql
# CentOS/RedHat
sudo yum install mysql-server php php-mysql
# Sécuriser MySQL
sudo mysql_secure_installation

Vérification de l'installation PHP-MySQL :

<?php
// test_mysql.php - Vérifier la connexion MySQL

echo "

Test de l'installation MySQL + PHP

"; // 1. Vérifier l'extension MySQL if (extension_loaded('mysqli')) { echo "✅ Extension MySQLi : INSTALLÉE
"; } else { echo "❌ Extension MySQLi : NON INSTALLÉE
"; } if (extension_loaded('pdo_mysql')) { echo "✅ Extension PDO MySQL : INSTALLÉE
"; } else { echo "❌ Extension PDO MySQL : NON INSTALLÉE
"; } // 2. Informations PHP echo "

Informations PHP :

"; echo "Version PHP : " . phpversion() . "
"; echo "Extensions MySQL disponibles :
"; $extensions = get_loaded_extensions(); $mysql_extensions = array_filter($extensions, function($ext) { return stripos($ext, 'mysql') !== false || stripos($ext, 'pdo') !== false; }); foreach ($mysql_extensions as $ext) { echo "- $ext
"; } // 3. Test de connexion basique echo "

Test de connexion :

"; // Configuration de base (à adapter) $host = 'localhost'; $username = 'root'; $password = ''; try { // Test avec MySQLi $mysqli = new mysqli($host, $username, $password); if ($mysqli->connect_error) { echo "❌ MySQLi : " . $mysqli->connect_error . "
"; } else { echo "✅ MySQLi : Connexion réussie
"; echo "Version MySQL : " . $mysqli->server_info . "
"; $mysqli->close(); } // Test avec PDO $pdo = new PDO("mysql:host=$host", $username, $password); echo "✅ PDO : Connexion réussie
"; } catch (Exception $e) { echo "❌ Erreur de connexion : " . $e->getMessage() . "
"; } // 4. Configuration recommandée php.ini echo "

Configuration recommandée php.ini :

"; echo "
extension=mysqli
extension=pdo_mysql
mysql.default_host = localhost
mysql.default_user = root
mysql.default_password = 
mysql.connect_timeout = 60
mysql.trace_mode = Off
"; ?>

Configuration VSCode pour MySQL & PHP

Optimisez VSCode avec les meilleures extensions et configurations pour le développement MySQL/PHP.

🔧 Extensions VSCode Essentielles

PHP Extensions PHP

PHP Intelephense
PHP Debug
PHP DocBlocker
PHP CS Fixer
PHPUnit Test Explorer

SQL Extensions MySQL

MySQL (by Jun Han)
SQLTools
SQL Formatter
Database Client
MySQL Syntax

+ Extensions Utiles

GitLens
Live Server
Bracket Pair Colorizer
Auto Rename Tag
Path Intellisense

Configuration VSCode (settings.json) :

{
    // Configuration PHP
    "php.validate.enable": true,
    "php.validate.executablePath": "/usr/bin/php",
    "php.suggest.basic": false,
    "intelephense.files.maxSize": 5000000,
    
    // Configuration MySQL
    "sqltools.connections": [
        {
            "name": "MySQL Local",
            "driver": "MySQL",
            "server": "localhost",
            "port": 3306,
            "username": "root",
            "password": "",
            "database": "test"
        }
    ],
    
    // Configuration générale
    "editor.fontSize": 14,
    "editor.tabSize": 4,
    "editor.insertSpaces": true,
    "files.associations": {
        "*.php": "php",
        "*.sql": "sql"
    },
    
    // Auto-formatting
    "editor.formatOnSave": true,
    "[php]": {
        "editor.defaultFormatter": "junstyle.php-cs-fixer"
    },
    "[sql]": {
        "editor.defaultFormatter": "bradymholt.pgformatter"
    }
}

Snippets PHP/MySQL Personnalisés

// Fichier : .vscode/snippets/php.json
{
    "PDO Connection": {
        "prefix": "pdo-connect",
        "body": [
            "try {",
            "    \\$pdo = new PDO('mysql:host=${1:localhost};dbname=${2:database}', '${3:username}', '${4:password}');",
            "    \\$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);",
            "    \\$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);",
            "} catch (PDOException \\$e) {",
            "    die('Erreur de connexion : ' . \\$e->getMessage());",
            "}"
        ]
    },
    
    "MySQLi Connection": {
        "prefix": "mysqli-connect",
        "body": [
            "\\$mysqli = new mysqli('${1:localhost}', '${2:username}', '${3:password}', '${4:database}');",
            "if (\\$mysqli->connect_error) {",
            "    die('Erreur de connexion : ' . \\$mysqli->connect_error);",
            "}",
            "\\$mysqli->set_charset('utf8');"
        ]
    },
    
    "SELECT Query PDO": {
        "prefix": "pdo-select",
        "body": [
            "\\$sql = \"SELECT ${1:*} FROM ${2:table} WHERE ${3:condition} = ?\";",
            "\\$stmt = \\$pdo->prepare(\\$sql);",
            "\\$stmt->execute([\\$${4:param}]);",
            "\\$${5:result} = \\$stmt->fetchAll();"
        ]
    },
    
    "INSERT Query PDO": {
        "prefix": "pdo-insert",
        "body": [
            "\\$sql = \"INSERT INTO ${1:table} (${2:columns}) VALUES (${3:placeholders})\";",
            "\\$stmt = \\$pdo->prepare(\\$sql);",
            "\\$stmt->execute([${4:values}]);"
        ]
    }
}

Adminer - Administration MySQL Simplifiée

Découvrez Adminer, l'alternative légère et puissante à phpMyAdmin pour gérer vos bases de données.

🚀 Installation et Configuration Adminer

Installation Rapide :

# 1. Télécharger Adminer
wget https://github.com/vrana/adminer/releases/download/v4.8.1/adminer-4.8.1.php
# 2. Renommer et placer
mv adminer-4.8.1.php adminer.php
sudo mv adminer.php /var/www/html/
# 3. Accéder via navigateur
http://localhost/adminer.php
✨ Avantages d'Adminer :
  • • Un seul fichier PHP (500kb)
  • • Interface moderne et intuitive
  • • Support multi-SGBD
  • • Thèmes personnalisables
  • • Fonctionnalités avancées

Configuration Personnalisée :

// adminer-custom.php
<?php
function adminer_object() {
include_once "adminer.php";
class AdminerCustom extends Adminer {
// Personnalisation CSS
function head() {
echo '<style>body{background:#1f2937}</style>';
}
// Masquer bases système
function databases($flush = true) {
$return = parent::databases($flush);
unset($return['information_schema']);
unset($return['performance_schema']);
return $return;
}
}
return new AdminerCustom;
}
include "adminer.php";
?>

⚙️ Fonctionnalités Avancées d'Adminer

🗄️ Gestion de Base

• Création/suppression de bases
• Import/export SQL
• Gestion des tables
• Index et relations
• Triggers et procédures

📊 Requêtes SQL

• Éditeur SQL avancé
• Historique des requêtes
• Auto-complétion
• Profiling des performances
• Explain des requêtes

🔧 Administration

• Gestion utilisateurs
• Privilèges et permissions
• Variables système
• Processus actifs
• Logs et monitoring

Raccourcis et Astuces Adminer :

Raccourcis Clavier :
Ctrl + Enter : Exécuter requête
Ctrl + S : Sauvegarder
Ctrl + F : Rechercher
Tab : Auto-complétion
URLs Utiles :
?sql= : Lancer une requête
?table= : Ouvrir une table
?edit= : Éditer un enregistrement
?export : Export de données

Connexion à MySQL avec PHP

Maîtrisez PDO et MySQLi pour vous connecter de manière sécurisée à MySQL.

🔌 PDO vs MySQLi - Comparaison et Exemples

PDO (Recommandé) :

<?php
// ✅ PDO - Plus flexible et sécurisé
class DatabasePDO {
    private $pdo;
    private $host = 'localhost';
    private $dbname = 'ma_base';
    private $username = 'root';
    private $password = '';

    public function __construct() {
        try {
            $dsn = "mysql:host={$this->host};dbname={$this->dbname};charset=utf8mb4";
            $this->pdo = new PDO($dsn, $this->username, $this->password, [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                PDO::ATTR_EMULATE_PREPARES => false
            ]);
        } catch (PDOException $e) {
            die('Erreur : ' . $e->getMessage());
        }
    }

    public function getConnection() {
        return $this->pdo;
    }
}

// Utilisation
$database = new DatabasePDO();
$pdo = $database->getConnection();
echo "✅ Connexion PDO réussie !";
?>
✅ Avantages PDO :
  • • Compatible avec 12 SGBD différents
  • • Syntaxe cohérente et moderne
  • • Requêtes préparées par défaut
  • • Gestion d'erreurs robuste
  • • Support des transactions

MySQLi :

<?php
// MySQLi - Spécifique à MySQL
class DatabaseMySQLi {
    private $mysqli;
    private $host = 'localhost';
    private $username = 'root';
    private $password = '';
    private $database = 'ma_base';

    public function __construct() {
        $this->mysqli = new mysqli(
            $this->host,
            $this->username,
            $this->password,
            $this->database
        );

        if ($this->mysqli->connect_error) {
            die('Erreur : ' . $this->mysqli->connect_error);
        }

        $this->mysqli->set_charset('utf8mb4');
    }

    public function getConnection() {
        return $this->mysqli;
    }
}

// Utilisation
$database = new DatabaseMySQLi();
$mysqli = $database->getConnection();
echo "✅ Connexion MySQLi réussie !";
?>
ℹ️ Caractéristiques MySQLi :
  • • Spécifique à MySQL uniquement
  • • Interface procédurale et objet
  • • Fonctionnalités MySQL avancées
  • • Performance légèrement supérieure
  • • Support des requêtes multiples

Connexion Simple (sans classe) :

<?php
// Connexion PDO simple pour débuter
$host = 'localhost';
$dbname = 'ma_base';
$username = 'root';
$password = '';

try {
    $pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8mb4", $username, $password, [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_EMULATE_PREPARES => false
    ]);
    
    echo "✅ Connexion réussie !";
    
} catch (PDOException $e) {
    die("❌ Erreur de connexion : " . $e->getMessage());
}

// Maintenant vous pouvez utiliser $pdo pour vos requêtes
?>

Exemples Pratiques à Copier-Coller

Code prêt à l'emploi pour démarrer rapidement avec MySQL et PHP.

🔌 1. Connexion Simple PDO

<?php
// connexion.php - Fichier de connexion à recopier
$host = 'localhost';
$dbname = 'ma_base';
$username = 'root';
$password = '';

try {
    $pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8mb4", $username, $password, [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_EMULATE_PREPARES => false
    ]);
    
    echo "✅ Connexion réussie !";
    
} catch (PDOException $e) {
    die("❌ Erreur de connexion : " . $e->getMessage());
}
?>

📊 2. SELECT - Lire des Données

<?php
// select.php - Exemples de SELECT
include 'connexion.php';

// 1. SELECT simple - tous les utilisateurs
$sql = "SELECT * FROM users";
$stmt = $pdo->query($sql);
$users = $stmt->fetchAll();

echo "<h3>Tous les utilisateurs :</h3>";
foreach ($users as $user) {
    echo "- " . $user['nom'] . " " . $user['prenom'] . " (" . $user['email'] . ")<br>";
}

// 2. SELECT avec WHERE - utilisateur spécifique
$email = 'jean.dupont@email.com';
$sql = "SELECT * FROM users WHERE email = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$email]);
$user = $stmt->fetch();

if ($user) {
    echo "<h3>Utilisateur trouvé :</h3>";
    echo "Nom : " . $user['nom'] . "<br>";
    echo "Email : " . $user['email'] . "<br>";
} else {
    echo "Aucun utilisateur trouvé avec cet email.";
}

// 3. SELECT avec LIMIT - pagination
$page = 1;
$parPage = 10;
$offset = ($page - 1) * $parPage;

$sql = "SELECT * FROM users ORDER BY created_at DESC LIMIT ? OFFSET ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$parPage, $offset]);
$users = $stmt->fetchAll();

echo "<h3>Utilisateurs (page $page) :</h3>";
foreach ($users as $user) {
    echo "- " . $user['nom'] . "<br>";
}

// 4. SELECT avec COUNT
$sql = "SELECT COUNT(*) as total FROM users";
$stmt = $pdo->query($sql);
$total = $stmt->fetch()['total'];

echo "<p>Total utilisateurs : $total</p>";
?>

3. INSERT - Ajouter des Données

<?php
// insert.php - Exemples d'INSERT
include 'connexion.php';

// 1. INSERT simple
$nom = 'Martin';
$prenom = 'Pierre';
$email = 'pierre.martin@email.com';
$age = 25;

$sql = "INSERT INTO users (nom, prenom, email, age, created_at) VALUES (?, ?, ?, ?, NOW())";
$stmt = $pdo->prepare($sql);
$stmt->execute([$nom, $prenom, $email, $age]);

$userId = $pdo->lastInsertId();
echo "✅ Utilisateur créé avec l'ID : $userId<br>";

// 2. INSERT avec tableau associatif
$userData = [
    'nom' => 'Durand',
    'prenom' => 'Marie',
    'email' => 'marie.durand@email.com',
    'age' => 28
];

$columns = implode(', ', array_keys($userData));
$placeholders = ':' . implode(', :', array_keys($userData));

$sql = "INSERT INTO users ($columns, created_at) VALUES ($placeholders, NOW())";
$stmt = $pdo->prepare($sql);

// Lier les valeurs
foreach ($userData as $key => $value) {
    $stmt->bindValue(":$key", $value);
}

if ($stmt->execute()) {
    echo "✅ Utilisateur " . $userData['prenom'] . " créé !<br>";
} else {
    echo "❌ Erreur lors de la création<br>";
}

// 3. INSERT multiple (batch)
$users = [
    ['nom' => 'Petit', 'prenom' => 'Paul', 'email' => 'paul.petit@email.com', 'age' => 22],
    ['nom' => 'Grand', 'prenom' => 'Sophie', 'email' => 'sophie.grand@email.com', 'age' => 35],
    ['nom' => 'Blanc', 'prenom' => 'Luc', 'email' => 'luc.blanc@email.com', 'age' => 41]
];

$sql = "INSERT INTO users (nom, prenom, email, age, created_at) VALUES (?, ?, ?, ?, NOW())";
$stmt = $pdo->prepare($sql);

$pdo->beginTransaction();
try {
    foreach ($users as $user) {
        $stmt->execute([$user['nom'], $user['prenom'], $user['email'], $user['age']]);
    }
    $pdo->commit();
    echo "✅ " . count($users) . " utilisateurs créés en lot !<br>";
} catch (Exception $e) {
    $pdo->rollback();
    echo "❌ Erreur lors de la création en lot : " . $e->getMessage() . "<br>";
}

// 4. INSERT avec vérification d'existence
$emailCheck = 'test@email.com';
$sql = "SELECT COUNT(*) FROM users WHERE email = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$emailCheck]);

if ($stmt->fetchColumn() == 0) {
    // L'email n'existe pas, on peut insérer
    $sql = "INSERT INTO users (nom, prenom, email, age, created_at) VALUES (?, ?, ?, ?, NOW())";
    $stmt = $pdo->prepare($sql);
    $stmt->execute(['Test', 'User', $emailCheck, 30]);
    echo "✅ Nouvel utilisateur créé !<br>";
} else {
    echo "⚠️ Un utilisateur avec cet email existe déjà.<br>";
}
?>

✏️ 4. UPDATE - Modifier des Données

<?php
// update.php - Exemples d'UPDATE
include 'connexion.php';

// 1. UPDATE simple - modifier un utilisateur
$userId = 1;
$nouvelAge = 26;
$nouvelEmail = 'nouveau.email@example.com';

$sql = "UPDATE users SET age = ?, email = ?, updated_at = NOW() WHERE id = ?";
$stmt = $pdo->prepare($sql);
$result = $stmt->execute([$nouvelAge, $nouvelEmail, $userId]);

if ($stmt->rowCount() > 0) {
    echo "✅ Utilisateur $userId mis à jour !<br>";
} else {
    echo "⚠️ Aucun utilisateur trouvé avec l'ID $userId<br>";
}

// 2. UPDATE conditionnel - modifier par email
$email = 'pierre.martin@email.com';
$nouveauNom = 'Martin-Dupont';

$sql = "UPDATE users SET nom = ?, updated_at = NOW() WHERE email = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$nouveauNom, $email]);

echo "Nombre de lignes modifiées : " . $stmt->rowCount() . "<br>";

// 3. UPDATE multiple - modifier plusieurs champs
$userId = 2;
$updates = [
    'prenom' => 'Marie-Claire',
    'age' => 29,
    'email' => 'marie-claire.durand@email.com'
];

// Construire la requête dynamiquement
$setParts = [];
$params = [];
foreach ($updates as $column => $value) {
    $setParts[] = "$column = ?";
    $params[] = $value;
}
$params[] = $userId; // Pour le WHERE

$sql = "UPDATE users SET " . implode(', ', $setParts) . ", updated_at = NOW() WHERE id = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute($params);

if ($stmt->rowCount() > 0) {
    echo "✅ Utilisateur $userId mis à jour avec " . count($updates) . " champs !<br>";
}

// 4. UPDATE avec vérification d'existence
$email = 'utilisateur@inexistant.com';
$newAge = 35;

// Vérifier si l'utilisateur existe
$sql = "SELECT id FROM users WHERE email = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$email]);
$user = $stmt->fetch();

if ($user) {
    $sql = "UPDATE users SET age = ?, updated_at = NOW() WHERE email = ?";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([$newAge, $email]);
    echo "✅ Âge mis à jour pour $email<br>";
} else {
    echo "❌ Utilisateur non trouvé : $email<br>";
}

// 5. UPDATE avec CASE (conditionnel SQL)
$sql = "UPDATE users SET 
           statut = CASE 
               WHEN age < 18 THEN 'mineur'
               WHEN age >= 18 AND age < 65 THEN 'actif'
               ELSE 'senior'
           END,
           updated_at = NOW()";
$stmt = $pdo->prepare($sql);
$stmt->execute();

echo "✅ Statuts mis à jour pour " . $stmt->rowCount() . " utilisateurs<br>";

// 6. UPDATE avec JOIN (avancé)
$sql = "UPDATE users u 
        INNER JOIN user_profiles p ON u.id = p.user_id 
        SET u.last_login = NOW(), 
            p.login_count = p.login_count + 1 
        WHERE u.email = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute(['user@example.com']);

echo "✅ Dernière connexion mise à jour<br>";
?>

🗑️ 5. DELETE - Supprimer des Données

<?php
// delete.php - Exemples de DELETE
include 'connexion.php';

// 1. DELETE simple - supprimer un utilisateur par ID
$userId = 1;

$sql = "DELETE FROM users WHERE id = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$userId]);

if ($stmt->rowCount() > 0) {
    echo "✅ Utilisateur $userId supprimé !<br>";
} else {
    echo "⚠️ Aucun utilisateur trouvé avec l'ID $userId<br>";
}

// 2. DELETE par email
$email = 'user.to.delete@email.com';

$sql = "DELETE FROM users WHERE email = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$email]);

echo "Nombre d'utilisateurs supprimés : " . $stmt->rowCount() . "<br>";

// 3. DELETE avec vérification préalable
$userIdToDelete = 5;

// Vérifier si l'utilisateur existe
$sql = "SELECT nom, prenom FROM users WHERE id = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$userIdToDelete]);
$user = $stmt->fetch();

if ($user) {
    // L'utilisateur existe, on peut le supprimer
    $sql = "DELETE FROM users WHERE id = ?";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([$userIdToDelete]);
    
    echo "✅ Utilisateur " . $user['prenom'] . " " . $user['nom'] . " supprimé !<br>";
} else {
    echo "❌ Utilisateur avec l'ID $userIdToDelete introuvable<br>";
}

// 4. DELETE conditionnel - supprimer les anciens comptes
$dateLimit = date('Y-m-d', strtotime('-1 year'));

$sql = "DELETE FROM users WHERE created_at < ? AND last_login IS NULL";
$stmt = $pdo->prepare($sql);
$stmt->execute([$dateLimit]);

echo "✅ " . $stmt->rowCount() . " comptes inactifs supprimés<br>";

// 5. DELETE avec LIMIT - supprimer en lot
$sql = "DELETE FROM users WHERE statut = 'inactive' LIMIT 10";
$stmt = $pdo->prepare($sql);
$stmt->execute();

echo "✅ " . $stmt->rowCount() . " utilisateurs inactifs supprimés (max 10)<br>";

// 6. Soft DELETE - marquer comme supprimé au lieu de supprimer
$userId = 8;

// Au lieu de DELETE, on met un flag deleted_at
$sql = "UPDATE users SET deleted_at = NOW() WHERE id = ? AND deleted_at IS NULL";
$stmt = $pdo->prepare($sql);
$stmt->execute([$userId]);

if ($stmt->rowCount() > 0) {
    echo "✅ Utilisateur $userId marqué comme supprimé (soft delete)<br>";
} else {
    echo "⚠️ Utilisateur déjà supprimé ou introuvable<br>";
}

// 7. DELETE avec sauvegarde dans table d'archivage
$userId = 10;

$pdo->beginTransaction();
try {
    // 1. Copier dans la table d'archive
    $sql = "INSERT INTO users_deleted SELECT *, NOW() as deleted_date FROM users WHERE id = ?";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([$userId]);
    
    // 2. Supprimer de la table principale
    $sql = "DELETE FROM users WHERE id = ?";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([$userId]);
    
    $pdo->commit();
    echo "✅ Utilisateur $userId archivé et supprimé !<br>";
    
} catch (Exception $e) {
    $pdo->rollback();
    echo "❌ Erreur lors de l'archivage : " . $e->getMessage() . "<br>";
}

// 8. DELETE avec relations (CASCADE simulé)
$userId = 12;

$pdo->beginTransaction();
try {
    // Supprimer d'abord les données liées
    $sql = "DELETE FROM user_posts WHERE user_id = ?";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([$userId]);
    $postsDeleted = $stmt->rowCount();
    
    $sql = "DELETE FROM user_comments WHERE user_id = ?";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([$userId]);
    $commentsDeleted = $stmt->rowCount();
    
    // Puis supprimer l'utilisateur
    $sql = "DELETE FROM users WHERE id = ?";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([$userId]);
    
    if ($stmt->rowCount() > 0) {
        $pdo->commit();
        echo "✅ Utilisateur $userId et ses données supprimés ($postsDeleted posts, $commentsDeleted commentaires)<br>";
    } else {
        $pdo->rollback();
        echo "❌ Utilisateur $userId introuvable<br>";
    }
    
} catch (Exception $e) {
    $pdo->rollback();
    echo "❌ Erreur lors de la suppression : " . $e->getMessage() . "<br>";
}
?>

🔄 6. TRANSACTION - Opérations Atomiques

<?php
// transaction.php - Exemples de TRANSACTIONS
include 'connexion.php';

// 1. Transaction simple - transfert d'argent
$compteSource = 1;
$compteDestination = 2;
$montant = 100.00;

$pdo->beginTransaction();
try {
    // Vérifier le solde du compte source
    $sql = "SELECT solde FROM comptes WHERE id = ? FOR UPDATE";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([$compteSource]);
    $soldeSource = $stmt->fetchColumn();
    
    if ($soldeSource < $montant) {
        throw new Exception("Solde insuffisant");
    }
    
    // Débiter le compte source
    $sql = "UPDATE comptes SET solde = solde - ? WHERE id = ?";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([$montant, $compteSource]);
    
    // Créditer le compte destination
    $sql = "UPDATE comptes SET solde = solde + ? WHERE id = ?";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([$montant, $compteDestination]);
    
    // Enregistrer la transaction
    $sql = "INSERT INTO transactions (compte_source, compte_destination, montant, date_transaction) 
            VALUES (?, ?, ?, NOW())";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([$compteSource, $compteDestination, $montant]);
    
    $pdo->commit();
    echo "✅ Transfert de $montant € réussi !<br>";
    
} catch (Exception $e) {
    $pdo->rollback();
    echo "❌ Transfert échoué : " . $e->getMessage() . "<br>";
}

// 2. Transaction complexe - création utilisateur complet
$userData = [
    'nom' => 'Nouveau',
    'prenom' => 'Utilisateur',
    'email' => 'nouveau@exemple.com',
    'age' => 25
];

$pdo->beginTransaction();
try {
    // 1. Créer l'utilisateur
    $sql = "INSERT INTO users (nom, prenom, email, age, created_at) VALUES (?, ?, ?, ?, NOW())";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([$userData['nom'], $userData['prenom'], $userData['email'], $userData['age']]);
    $userId = $pdo->lastInsertId();
    
    // 2. Créer son profil
    $sql = "INSERT INTO user_profiles (user_id, bio, avatar, created_at) VALUES (?, ?, ?, NOW())";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([$userId, 'Nouveau membre', 'default.jpg']);
    
    // 3. Créer ses paramètres
    $sql = "INSERT INTO user_settings (user_id, notifications, theme, language) VALUES (?, ?, ?, ?)";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([$userId, 1, 'dark', 'fr']);
    
    // 4. L'ajouter au groupe par défaut
    $sql = "INSERT INTO user_groups (user_id, group_id, joined_at) VALUES (?, ?, NOW())";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([$userId, 1]); // Groupe "Utilisateurs"
    
    $pdo->commit();
    echo "✅ Utilisateur complet créé avec l'ID : $userId<br>";
    
} catch (Exception $e) {
    $pdo->rollback();
    echo "❌ Création utilisateur échouée : " . $e->getMessage() . "<br>";
}

// 3. Transaction avec savepoint (point de sauvegarde)
$pdo->beginTransaction();
try {
    // Opération 1
    $sql = "INSERT INTO categories (nom) VALUES (?)";
    $stmt = $pdo->prepare($sql);
    $stmt->execute(['Nouvelle Catégorie']);
    $categoryId = $pdo->lastInsertId();
    
    // Point de sauvegarde
    $pdo->exec("SAVEPOINT sp1");
    
    try {
        // Opération 2 (peut échouer)
        $sql = "INSERT INTO products (nom, category_id, prix) VALUES (?, ?, ?)";
        $stmt = $pdo->prepare($sql);
        $stmt->execute(['Produit Test', $categoryId, 'prix_invalide']); // Erreur volontaire
        
    } catch (Exception $e2) {
        // Retour au point de sauvegarde
        $pdo->exec("ROLLBACK TO SAVEPOINT sp1");
        echo "⚠️ Erreur produit, mais catégorie conservée : " . $e2->getMessage() . "<br>";
    }
    
    $pdo->commit();
    echo "✅ Transaction partielle réussie (catégorie créée)<br>";
    
} catch (Exception $e) {
    $pdo->rollback();
    echo "❌ Transaction complètement échouée : " . $e->getMessage() . "<br>";
}

// 4. Transaction avec gestion d'erreurs avancée
function transfererArgent($pdo, $compteA, $compteB, $montant) {
    $pdo->beginTransaction();
    
    try {
        // Vérifications préalables
        $sql = "SELECT id, solde, statut FROM comptes WHERE id IN (?, ?) FOR UPDATE";
        $stmt = $pdo->prepare($sql);
        $stmt->execute([$compteA, $compteB]);
        $comptes = $stmt->fetchAll(PDO::FETCH_ASSOC);
        
        if (count($comptes) != 2) {
            throw new Exception("Un ou plusieurs comptes introuvables");
        }
        
        $soldeA = null;
        $soldeB = null;
        foreach ($comptes as $compte) {
            if ($compte['statut'] != 'actif') {
                throw new Exception("Compte {$compte['id']} inactif");
            }
            if ($compte['id'] == $compteA) $soldeA = $compte['solde'];
            if ($compte['id'] == $compteB) $soldeB = $compte['solde'];
        }
        
        if ($soldeA < $montant) {
            throw new Exception("Solde insuffisant sur le compte $compteA");
        }
        
        // Effectuer le transfert
        $sql = "UPDATE comptes SET solde = solde - ?, updated_at = NOW() WHERE id = ?";
        $stmt = $pdo->prepare($sql);
        $stmt->execute([$montant, $compteA]);
        
        $sql = "UPDATE comptes SET solde = solde + ?, updated_at = NOW() WHERE id = ?";
        $stmt = $pdo->prepare($sql);
        $stmt->execute([$montant, $compteB]);
        
        // Historique
        $sql = "INSERT INTO transferts (compte_source, compte_destination, montant, statut, created_at) 
                VALUES (?, ?, ?, 'completed', NOW())";
        $stmt = $pdo->prepare($sql);
        $stmt->execute([$compteA, $compteB, $montant]);
        
        $pdo->commit();
        return ['success' => true, 'message' => "Transfert de $montant € réussi"];
        
    } catch (Exception $e) {
        $pdo->rollback();
        
        // Logger l'erreur
        $sql = "INSERT INTO error_logs (operation, error_message, created_at) VALUES (?, ?, NOW())";
        $stmt = $pdo->prepare($sql);
        $stmt->execute(['transfert', $e->getMessage()]);
        
        return ['success' => false, 'message' => $e->getMessage()];
    }
}

// Utilisation de la fonction
$result = transfererArgent($pdo, 1, 2, 50.00);
if ($result['success']) {
    echo "✅ " . $result['message'] . "<br>";
} else {
    echo "❌ " . $result['message'] . "<br>";
}

// 5. Transaction avec retry automatique
function executerAvecRetry($pdo, $callback, $maxRetries = 3) {
    $attempts = 0;
    
    while ($attempts < $maxRetries) {
        try {
            $pdo->beginTransaction();
            $result = $callback($pdo);
            $pdo->commit();
            
            return ['success' => true, 'result' => $result, 'attempts' => $attempts + 1];
            
        } catch (Exception $e) {
            $pdo->rollback();
            $attempts++;
            
            // Si c'est un deadlock, on peut retry
            if (strpos($e->getMessage(), 'Deadlock') !== false && $attempts < $maxRetries) {
                sleep(1); // Attendre avant de réessayer
                continue;
            }
            
            return ['success' => false, 'error' => $e->getMessage(), 'attempts' => $attempts];
        }
    }
}

// Exemple d'utilisation du retry
$result = executerAvecRetry($pdo, function($pdo) {
    // Opération complexe qui peut causer un deadlock
    $sql = "UPDATE stock SET quantite = quantite - 1 WHERE product_id = ? AND quantite > 0";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([1]);
    
    if ($stmt->rowCount() == 0) {
        throw new Exception("Stock insuffisant");
    }
    
    return "Stock mis à jour";
});

if ($result['success']) {
    echo "✅ " . $result['result'] . " (tentatives: " . $result['attempts'] . ")<br>";
} else {
    echo "❌ " . $result['error'] . " après " . $result['attempts'] . " tentatives<br>";
}
?>

Opérations CRUD Complètes

Create, Read, Update, Delete - Maîtrisez toutes les opérations de base de données.

📝 Classe CRUD Complète avec PDO

<?php
/**
 * Classe CRUD Complète avec PDO
 * Gestion sécurisée des opérations de base de données
 */

class CRUDManager {
    
    private $pdo;
    
    public function __construct($pdo) {
        $this->pdo = $pdo;
    }
    
    /**
     * CREATE - Insérer des données
     */
    public function create($table, $data) {
        try {
            // Préparer les colonnes et placeholders
            $columns = implode(', ', array_keys($data));
            $placeholders = ':' . implode(', :', array_keys($data));
            
            $sql = "INSERT INTO {$table} ({$columns}) VALUES ({$placeholders})";
            $stmt = $this->pdo->prepare($sql);
            
            // Lier les valeurs
            foreach ($data as $key => $value) {
                $stmt->bindValue(":{$key}", $value);
            }
            
            $stmt->execute();
            return $this->pdo->lastInsertId();
            
        } catch (PDOException $e) {
            throw new Exception("Erreur lors de l'insertion : " . $e->getMessage());
        }
    }
    
    /**
     * READ - Sélectionner des données
     */
    public function read($table, $conditions = [], $options = []) {
        try {
            $sql = "SELECT * FROM {$table}";
            $params = [];
            
            // Ajouter les conditions WHERE
            if (!empty($conditions)) {
                $whereClause = [];
                foreach ($conditions as $key => $value) {
                    $whereClause[] = "{$key} = :{$key}";
                    $params[":{$key}"] = $value;
                }
                $sql .= " WHERE " . implode(' AND ', $whereClause);
            }
            
            // Ajouter ORDER BY
            if (isset($options['order_by'])) {
                $sql .= " ORDER BY " . $options['order_by'];
                if (isset($options['order_dir'])) {
                    $sql .= " " . strtoupper($options['order_dir']);
                }
            }
            
            // Ajouter LIMIT
            if (isset($options['limit'])) {
                $sql .= " LIMIT " . (int)$options['limit'];
                if (isset($options['offset'])) {
                    $sql .= " OFFSET " . (int)$options['offset'];
                }
            }
            
            $stmt = $this->pdo->prepare($sql);
            $stmt->execute($params);
            
            // Retourner un seul résultat ou tous
            if (isset($options['single']) && $options['single']) {
                return $stmt->fetch();
            }
            
            return $stmt->fetchAll();
            
        } catch (PDOException $e) {
            throw new Exception("Erreur lors de la lecture : " . $e->getMessage());
        }
    }
    
    /**
     * UPDATE - Mettre à jour des données
     */
    public function update($table, $data, $conditions) {
        try {
            // Préparer la clause SET
            $setClause = [];
            foreach ($data as $key => $value) {
                $setClause[] = "{$key} = :set_{$key}";
            }
            
            // Préparer la clause WHERE
            $whereClause = [];
            foreach ($conditions as $key => $value) {
                $whereClause[] = "{$key} = :where_{$key}";
            }
            
            $sql = "UPDATE {$table} SET " . implode(', ', $setClause) . 
                   " WHERE " . implode(' AND ', $whereClause);
            
            $stmt = $this->pdo->prepare($sql);
            
            // Lier les valeurs SET
            foreach ($data as $key => $value) {
                $stmt->bindValue(":set_{$key}", $value);
            }
            
            // Lier les valeurs WHERE
            foreach ($conditions as $key => $value) {
                $stmt->bindValue(":where_{$key}", $value);
            }
            
            $stmt->execute();
            return $stmt->rowCount();
            
        } catch (PDOException $e) {
            throw new Exception("Erreur lors de la mise à jour : " . $e->getMessage());
        }
    }
    
    /**
     * DELETE - Supprimer des données
     */
    public function delete($table, $conditions) {
        try {
            $whereClause = [];
            $params = [];
            
            foreach ($conditions as $key => $value) {
                $whereClause[] = "{$key} = :{$key}";
                $params[":{$key}"] = $value;
            }
            
            $sql = "DELETE FROM {$table} WHERE " . implode(' AND ', $whereClause);
            $stmt = $this->pdo->prepare($sql);
            $stmt->execute($params);
            
            return $stmt->rowCount();
            
        } catch (PDOException $e) {
            throw new Exception("Erreur lors de la suppression : " . $e->getMessage());
        }
    }
    
    /**
     * MÉTHODES UTILITAIRES
     */
    
    // Compter les enregistrements
    public function count($table, $conditions = []) {
        try {
            $sql = "SELECT COUNT(*) as total FROM {$table}";
            $params = [];
            
            if (!empty($conditions)) {
                $whereClause = [];
                foreach ($conditions as $key => $value) {
                    $whereClause[] = "{$key} = :{$key}";
                    $params[":{$key}"] = $value;
                }
                $sql .= " WHERE " . implode(' AND ', $whereClause);
            }
            
            $stmt = $this->pdo->prepare($sql);
            $stmt->execute($params);
            
            return $stmt->fetch()['total'];
            
        } catch (PDOException $e) {
            throw new Exception("Erreur lors du comptage : " . $e->getMessage());
        }
    }
    
    // Vérifier l'existence
    public function exists($table, $conditions) {
        return $this->count($table, $conditions) > 0;
    }
    
    // Requête personnalisée
    public function query($sql, $params = []) {
        try {
            $stmt = $this->pdo->prepare($sql);
            $stmt->execute($params);
            
            // Retourner selon le type de requête
            if (stripos($sql, 'SELECT') === 0) {
                return $stmt->fetchAll();
            } elseif (stripos($sql, 'INSERT') === 0) {
                return $this->pdo->lastInsertId();
            } else {
                return $stmt->rowCount();
            }
            
        } catch (PDOException $e) {
            throw new Exception("Erreur lors de l'exécution : " . $e->getMessage());
        }
    }
    
    // Commencer une transaction
    public function beginTransaction() {
        return $this->pdo->beginTransaction();
    }
    
    // Valider une transaction
    public function commit() {
        return $this->pdo->commit();
    }
    
    // Annuler une transaction
    public function rollback() {
        return $this->pdo->rollback();
    }
}

// EXEMPLES D'UTILISATION

// Initialisation de la connexion
try {
    $pdo = new PDO('mysql:host=localhost;dbname=ma_base;charset=utf8mb4', 'root', '', [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_EMULATE_PREPARES => false
    ]);
} catch (PDOException $e) {
    die('Erreur de connexion : ' . $e->getMessage());
}

$crud = new CRUDManager($pdo);

// CREATE - Créer un utilisateur
$userId = $crud->create('users', [
    'nom' => 'Dupont',
    'prenom' => 'Jean',
    'email' => 'jean.dupont@email.com',
    'age' => 30,
    'created_at' => date('Y-m-d H:i:s')
]);

echo "Utilisateur créé avec l'ID : " . $userId . "\n";

// READ - Lire des utilisateurs
$users = $crud->read('users', 
    ['age' => 30], // Conditions
    ['order_by' => 'nom', 'order_dir' => 'ASC', 'limit' => 10] // Options
);

foreach ($users as $user) {
    echo "Utilisateur : " . $user['prenom'] . " " . $user['nom'] . "\n";
}

// READ - Lire un seul utilisateur
$user = $crud->read('users', 
    ['id' => $userId], 
    ['single' => true]
);

// UPDATE - Mettre à jour un utilisateur
$rowsUpdated = $crud->update('users', 
    ['age' => 31, 'updated_at' => date('Y-m-d H:i:s')], // Nouvelles données
    ['id' => $userId] // Conditions
);

echo "Nombre de lignes mises à jour : " . $rowsUpdated . "\n";

// COUNT - Compter les utilisateurs
$totalUsers = $crud->count('users');
$adultUsers = $crud->count('users', ['age >=' => 18]);

echo "Total utilisateurs : " . $totalUsers . "\n";
echo "Utilisateurs majeurs : " . $adultUsers . "\n";

// EXISTS - Vérifier l'existence
if ($crud->exists('users', ['email' => 'jean.dupont@email.com'])) {
    echo "L'email existe déjà\n";
}

// TRANSACTION - Exemple avec transaction
try {
    $crud->beginTransaction();
    
    $userId1 = $crud->create('users', ['nom' => 'Test1', 'email' => 'test1@email.com']);
    $userId2 = $crud->create('users', ['nom' => 'Test2', 'email' => 'test2@email.com']);
    
    $crud->commit();
    echo "Transaction réussie\n";
    
} catch (Exception $e) {
    $crud->rollback();
    echo "Transaction échouée : " . $e->getMessage() . "\n";
}

// DELETE - Supprimer un utilisateur
$rowsDeleted = $crud->delete('users', ['id' => $userId]);
echo "Nombre de lignes supprimées : " . $rowsDeleted . "\n";

// REQUÊTE PERSONNALISÉE
$results = $crud->query(
    "SELECT nom, COUNT(*) as count FROM users GROUP BY nom HAVING count > 1",
    []
);

foreach ($results as $result) {
    echo "Nom en doublon : " . $result['nom'] . " (" . $result['count'] . " fois)\n";
}
?>

Requêtes MySQL Avancées

Jointures, sous-requêtes, fonctions d'agrégation et optimisation des performances.

🔗 Jointures et Requêtes Complexes

Jointures SQL :

-- INNER JOIN (intersection)
SELECT u.nom, u.email, p.titre
FROM users u
INNER JOIN posts p ON u.id = p.user_id;
-- LEFT JOIN (tous les users)
SELECT u.nom, COUNT(p.id) AS nb_posts
FROM users u
LEFT JOIN posts p ON u.id = p.user_id
GROUP BY u.id;
-- RIGHT JOIN (tous les posts)
SELECT p.titre, u.nom
FROM users u
RIGHT JOIN posts p ON u.id = p.user_id;

Implémentation PHP :

// Méthode pour jointures
public function joinQuery($tables, $joins, $conditions = []) {
$sql = "SELECT * FROM {$tables[0]}";
foreach ($joins as $join) {
$sql .= " {$join['type']} JOIN {$join['table']}";
$sql .= " ON {$join['condition']}";
}
if (!empty($conditions)) {
$sql .= " WHERE " . implode(' AND ', $conditions);
}
return $this->query($sql);
}

Exemples de Requêtes Avancées :

-- Sous-requête corrélée
SELECT u.nom, u.email,
(SELECT COUNT(*) FROM posts p WHERE p.user_id = u.id) AS nb_posts
FROM users u
WHERE u.id IN (SELECT DISTINCT user_id FROM posts);
-- Fonction de fenêtrage (Window Functions)
SELECT nom, email, created_at,
ROW_NUMBER() OVER (ORDER BY created_at) AS rang,
RANK() OVER (PARTITION BY age ORDER BY created_at) AS rang_par_age
FROM users;
-- CTE (Common Table Expression)
WITH user_stats AS (
SELECT user_id, COUNT(*) AS post_count
FROM posts GROUP BY user_id
)
SELECT u.nom, us.post_count
FROM users u
JOIN user_stats us ON u.id = us.user_id
WHERE us.post_count > 5;

Optimisation et Performance

Index, cache, monitoring et meilleures pratiques pour des performances optimales.

Optimisation des Performances

📊 Index et Structure

• Index sur les colonnes fréquemment utilisées
• Index composites pour requêtes multi-colonnes
• Éviter les index inutiles
• Normalisation appropriée
• Types de données optimaux

🚀 Requêtes Optimisées

• SELECT uniquement les colonnes nécessaires
• LIMIT pour paginer les résultats
• WHERE avec index pour filtrer
• Éviter SELECT * dans les jointures
• Utiliser EXPLAIN pour analyser

💾 Cache et Pool

• Pool de connexions persistantes
• Cache des requêtes fréquentes
• Redis/Memcached pour le cache
• Invalidation intelligente
• Compression des données

Classe d'Optimisation Avancée :

<?php
class OptimizedDatabase extends CRUDManager {
    
    private $cache = [];
    private $queryStats = [];
    
    // Pool de connexions
    private static $connections = [];
    
    public function getOptimizedConnection($config) {
        $key = md5(serialize($config));
        
        if (!isset(self::$connections[$key])) {
            self::$connections[$key] = new PDO(
                $config['dsn'], 
                $config['username'], 
                $config['password'],
                [
                    PDO::ATTR_PERSISTENT => true,
                    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                    PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false
                ]
            );
        }
        
        return self::$connections[$key];
    }
    
    // Cache intelligent
    public function cachedQuery($sql, $params = [], $ttl = 3600) {
        $cacheKey = 'query_' . md5($sql . serialize($params));
        
        // Vérifier le cache
        if (isset($this->cache[$cacheKey])) {
            $data = $this->cache[$cacheKey];
            if (time() - $data['time'] < $ttl) {
                return $data['result'];
            }
        }
        
        // Exécuter la requête
        $result = $this->query($sql, $params);
        
        // Mettre en cache
        $this->cache[$cacheKey] = [
            'result' => $result,
            'time' => time()
        ];
        
        return $result;
    }
    
    // Monitoring des performances
    public function profiledQuery($sql, $params = []) {
        $start = microtime(true);
        $result = parent::query($sql, $params);
        $duration = microtime(true) - $start;
        
        // Stocker les statistiques
        $this->queryStats[] = [
            'sql' => $sql,
            'duration' => $duration,
            'timestamp' => time()
        ];
        
        // Alerter si requête lente
        if ($duration > 1.0) {
            error_log("Requête lente détectée: {$duration}s - {$sql}");
        }
        
        return $result;
    }
    
    // Pagination optimisée
    public function paginatedRead($table, $page = 1, $perPage = 20, $conditions = []) {
        $offset = ($page - 1) * $perPage;
        
        // Compter le total (avec cache)
        $countSql = "SELECT COUNT(*) as total FROM {$table}";
        if (!empty($conditions)) {
            $whereClause = implode(' AND ', array_keys($conditions));
            $countSql .= " WHERE {$whereClause}";
        }
        
        $total = $this->cachedQuery($countSql, $conditions, 300)[0]['total'];
        
        // Récupérer les données
        $data = $this->read($table, $conditions, [
            'limit' => $perPage,
            'offset' => $offset
        ]);
        
        return [
            'data' => $data,
            'pagination' => [
                'page' => $page,
                'perPage' => $perPage,
                'total' => $total,
                'pages' => ceil($total / $perPage)
            ]
        ];
    }
    
    // Optimisation automatique des index
    public function analyzeTable($table) {
        // Analyser l'utilisation des index
        $indexUsage = $this->query("SHOW INDEX FROM {$table}");
        
        // Suggestions d'optimisation
        $suggestions = [];
        
        // Vérifier les colonnes sans index dans les WHERE
        foreach ($this->queryStats as $stat) {
            if (strpos($stat['sql'], "WHERE") !== false && $stat['duration'] > 0.5) {
                $suggestions[] = "Considérer un index pour: {$table}";
            }
        }
        
        return $suggestions;
    }
    
    // Rapport de performance
    public function getPerformanceReport() {
        $slowQueries = array_filter($this->queryStats, function($stat) {
            return $stat['duration'] > 0.1;
        });
        
        $avgDuration = array_sum(array_column($this->queryStats, 'duration')) / count($this->queryStats);
        
        return [
            'total_queries' => count($this->queryStats),
            'slow_queries' => count($slowQueries),
            'average_duration' => $avgDuration,
            'cache_hits' => count($this->cache),
            'slowest_queries' => array_slice(
                array_reverse(array_sort($this->queryStats, 'duration')), 
                0, 5
            )
        ];
    }
}
?>

Meilleures Pratiques MySQL/PHP

✅ À FAIRE :

Utiliser PDO avec requêtes préparées
Valider et échapper toutes les entrées
Gérer les erreurs avec try/catch
Utiliser des transactions pour la cohérence
Indexer les colonnes des WHERE et JOIN
Limiter les résultats avec LIMIT
Mettre en cache les requêtes fréquentes

❌ À ÉVITER :

Concaténation directe dans les requêtes
SELECT * sur de grandes tables
Requêtes dans des boucles (N+1)
Oublier de fermer les connexions
Stocker des mots de passe en clair
Ignorer les erreurs de base de données
Index excessifs qui ralentissent les INSERT

MySQL & PHP Maîtrisés !

Vous maîtrisez maintenant MySQL avec PHP, VSCode pour le développement et Adminer pour l'administration. Passez aux sujets avancés !

🎯 Compétences acquises :

Installation MySQL • Configuration VSCode • Administration Adminer • Connexions PDO/MySQLi • CRUD Sécurisé • Requêtes Avancées • Optimisation