Comment établir une connexion à une base de données en PHP ?

En tant que développeur web, il est souvent nécessaire d’interagir avec une base de données pour stocker et récupérer des données. En utilisant PHP, vous pouvez facilement établir une connexion à une base de données pour accomplir ces tâches. Dans cet article, nous allons explorer les étapes pour établir une connexion à une base de

Comment établir une connexion à une base de données en PHP ?
Réponse rapide

Utilisez PDO avec charset=utf8mb4 dans le DSN, ATTR_ERRMODE en exception et ATTR_EMULATE_PREPARES à false. Depuis PHP 8.0 les erreurs PDO lèvent déjà une exception par défaut, et depuis PHP 8.1 mysqli aussi : le motif if (!$conn) die(...) ne se déclenche plus jamais. Toute valeur venue de l'utilisateur passe par une requête préparée.

Se connecter à une base de données est la première chose que fait la plupart des applications PHP. Le choix de l’extension et la façon d’écrire la connexion décident de deux choses : la lisibilité des erreurs le jour où ça casse, et la résistance aux injections SQL. Cet article montre la connexion telle qu’on l’écrit en 2026, sur PHP 8.5, avec les pièges qui ont changé depuis PHP 8.0.

PDO, la connexion à retenir

PDO est l’interface générique de PHP pour les bases de données. Elle parle à MySQL et MariaDB, mais aussi à PostgreSQL, SQLite ou SQL Server, avec le même code applicatif. C’est le choix par défaut, sauf raison précise de faire autrement.

src/Base.php
<?php

declare(strict_types=1);

$dsn = 'mysql:host=127.0.0.1;port=3306;dbname=ma_base;charset=utf8mb4';

$pdo = new PDO($dsn, 'utilisateur', 'mot_de_passe', [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES   => false,
]);

Trois détails comptent dans ce bloc, et ce sont exactement ceux qu’on oublie.

  • charset=utf8mb4 dans le DSN. Sans lui, la connexion utilise le jeu de caractères par défaut du serveur. Les emoji et une partie des caractères asiatiques se transforment en points d’interrogation, et l’encodage devient un vecteur d’injection sur certains jeux de caractères. Le charset se déclare dans le DSN, pas par une requête SET NAMES.
  • ATTR_EMULATE_PREPARES à false. Par défaut, le pilote MySQL simule les requêtes préparées côté PHP au lieu de les envoyer au serveur. En les désactivant, c’est le serveur qui sépare la requête des données, et les types entiers restent des entiers au retour.
  • ATTR_ERRMODE explicite. Depuis PHP 8.0, ERRMODE_EXCEPTION est déjà la valeur par défaut. Le laisser écrit ne change rien au comportement, mais dit au lecteur du code que les erreurs remontent en exceptions.

Vérification sur la machine de test :

php
$pdo2 = new PDO($dsn, $utilisateur, $motDePasse); // aucune option
var_dump($pdo2->getAttribute(PDO::ATTR_ERRMODE) === PDO::ERRMODE_EXCEPTION);
// bool(true) sur PHP 8.5.10

Le mot de passe ne s’écrit pas dans le fichier

Les identifiants n’ont rien à faire dans un fichier versionné. La forme la plus simple consiste à les lire dans l’environnement, avec un message clair si la variable manque.

src/connexion.php
<?php

declare(strict_types=1);

function connexion(): PDO
{
    static $pdo = null;
    if ($pdo instanceof PDO) {
        return $pdo;
    }

    $hote = getenv('DB_HOST') ?: '127.0.0.1';
    $base = getenv('DB_NAME') ?: throw new RuntimeException('DB_NAME manquant');
    $user = getenv('DB_USER') ?: throw new RuntimeException('DB_USER manquant');
    $pass = getenv('DB_PASS') ?: '';

    return $pdo = new PDO(
        "mysql:host={$hote};dbname={$base};charset=utf8mb4",
        $user,
        $pass,
        [
            PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES   => false,
        ],
    );
}

L’opérateur ?: suivi de throw fonctionne depuis PHP 8.0, où throw est devenu une expression. La connexion est mémorisée dans une variable statique : ouvrir une connexion par requête suffit très largement.

Ne jamais laisser fuiter le message d’erreur

Une exception de connexion contient l’hôte, l’utilisateur et parfois le mot de passe dans la trace. Affichée telle quelle sur une page publique, elle offre la moitié du travail à un attaquant.

php
try {
    $pdo = connexion();
} catch (PDOException $e) {
    // Le détail part dans le journal, jamais dans la réponse HTTP
    error_log('Connexion base impossible : ' . $e->getMessage());
    http_response_code(503);
    exit('Service momentanément indisponible.');
}

Depuis PHP 8.2, les paramètres marqués #[SensitiveParameter] apparaissent comme Object(SensitiveParameterValue) dans les traces. C’est une protection utile, mais elle ne couvre que la trace : le message, lui, reste bavard.

Se connecter n’est que la moitié du travail

Une connexion sans requête préparée ne protège de rien. La règle tient en une phrase : les données ne se concatènent jamais dans la requête.

php
// À ne pas faire : la valeur entre directement dans la requête
$sql = "SELECT * FROM membres WHERE email = '" . $_GET['email'] . "'";

// Requête préparée : la valeur voyage à part
$requete = $pdo->prepare('SELECT id, email, cree_le FROM membres WHERE email = ?');
$requete->execute([$_GET['email'] ?? '']);
$membre = $requete->fetch();

Les marqueurs nommés se lisent mieux dès qu’il y a plus de deux valeurs :

php
$requete = $pdo->prepare(
    'SELECT id, titre FROM articles
     WHERE statut = :statut AND publie_le >= :depuis
     ORDER BY publie_le DESC LIMIT :limite'
);
$requete->bindValue(':statut', 'publie');
$requete->bindValue(':depuis', '2026-01-01');
$requete->bindValue(':limite', 10, PDO::PARAM_INT); // toujours typer le LIMIT
$requete->execute();

foreach ($requete as $ligne) {
    echo $ligne['titre'], "\n";
}

Le PDO::PARAM_INT sur le LIMIT n’est pas décoratif, et son effet dépend d’un réglage qu’on oublie souvent. Voici ce que donne bindValue(':limite', 3) sans typage, selon que l’émulation des requêtes préparées est active ou non :

Serveur EMULATE_PREPARES Sans PARAM_INT Avec PARAM_INT
MariaDB 11.8.9 false accepté accepté
MariaDB 11.8.9 true (défaut) erreur SQL 1064 accepté
MySQL 8.4.11 false accepté accepté
MySQL 8.4.11 true (défaut) erreur SQL 1064 accepté

Avec l’émulation, PDO fabrique la requête lui-même et entoure la valeur de guillemets : LIMIT '3', que le serveur refuse. C’est la configuration par défaut, donc le cas que la plupart des gens rencontrent. Typer la valeur fonctionne dans les quatre situations, c’est la seule forme à retenir.

Un marqueur ne remplace jamais un nom de table ou de colonne. Si le nom de colonne vient de l’utilisateur, il se valide contre une liste fermée :

php
$colonnesTriables = ['titre', 'publie_le', 'vues'];
$tri = in_array($_GET['tri'] ?? '', $colonnesTriables, true) ? $_GET['tri'] : 'publie_le';
$sql = "SELECT id, titre FROM articles ORDER BY {$tri} DESC";

Et mysqli ?

mysqli reste valable si le projet ne parlera jamais qu’à MySQL ou MariaDB. Son comportement a changé sur un point majeur : depuis PHP 8.1, mysqli_report() est réglé sur MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT, et les erreurs lèvent une exception au lieu de renvoyer false.

php
<?php

declare(strict_types=1);

try {
    $mysqli = new mysqli('127.0.0.1', $utilisateur, $motDePasse, 'ma_base');
    $mysqli->set_charset('utf8mb4');
} catch (mysqli_sql_exception $e) {
    error_log('Connexion mysqli impossible : ' . $e->getMessage());
    http_response_code(503);
    exit('Service momentanément indisponible.');
}

$requete = $mysqli->prepare('SELECT id, email FROM membres WHERE email = ?');
$requete->bind_param('s', $email);
$requete->execute();
$membre = $requete->get_result()->fetch_assoc();

Le test de connexion qui ne teste plus rien

Le motif suivant traîne dans énormément de tutoriels, y compris dans les versions précédentes de celui-ci. Il ne fonctionne plus depuis PHP 8.1.

php
$conn = mysqli_connect($hote, $utilisateur, $motDePasse, $base);

if (!$conn) {
    die('La connexion a échoué : ' . mysqli_connect_error());
}

Exécuté sur PHP 8.5.10 avec un mauvais mot de passe, ce code ne produit pas le message prévu. Il produit ceci :

code
PHP Fatal error:  Uncaught mysqli_sql_exception: Access denied for user
'gekkode'@'172.27.0.10' (using password: YES) in /app/test.php:8

La ligne if (!$conn) n’est jamais atteinte : mysqli_connect() a levé l’exception avant. Le die() est du code mort, et l’erreur brute part vers le navigateur si l’affichage des erreurs est actif. La réécriture avec try / catch plus haut est la seule forme correcte aujourd’hui.

Les fonctions mysql_* n’existent plus

Les mysql_connect(), mysql_query() et mysql_real_escape_string() que l’on croise encore dans de vieux scripts ont été retirés de PHP 7.0, sorti en 2015. Sur PHP 8.5 :

php
var_dump(function_exists('mysql_connect'));           // bool(false)
var_dump(function_exists('mysql_real_escape_string')); // bool(false)

Il n’y a pas de compatibilité à assurer, seulement une migration à faire, vers PDO de préférence.

Pour aller plus loin

Pour insérer un gros volume de lignes, la connexion n’est qu’une moitié du sujet : voir lire les gros fichiers avec PHP, qui montre l’insertion par lots dans une transaction. Pour rediriger après un traitement, voir créer une redirection en PHP. Une fois la connexion en place, la suite consiste à ne pas la refaire à chaque fichier. Les autres tutoriels PHP du hub Développement web couvrent les briques voisines : installer Composer pour organiser le projet, et les bases de la POO en PHP pour ranger cette connexion dans une classe plutôt que dans une fonction globale.

Erreurs fréquentes

Le die() qui ne s'exécute jamais Depuis PHP 8.1, mysqli_connect() lève une mysqli_sql_exception avant d'atteindre if (!$conn). Le message d'erreur brut part vers le navigateur au lieu du message prévu.
Charset absent du DSN Sans charset=utf8mb4, les emoji et certains caractères deviennent des points d'interrogation, et l'encodage peut servir de vecteur d'injection.
LIMIT sans PDO::PARAM_INT Avec l'émulation des requêtes préparées, active par défaut, PDO écrit LIMIT '3' et le serveur renvoie une erreur SQL 1064. Vérifié sur MariaDB 11.8.9 et MySQL 8.4.11.
Message d'exception affiché La trace d'une PDOException contient l'hôte et l'utilisateur. Elle va dans le journal, jamais dans la réponse HTTP.
Marqueur pour un nom de colonne Un marqueur ne remplace qu'une valeur. Un nom de table ou de colonne se valide contre une liste fermée.

MySQLPDOPHPSécurité

Damien Flandrin Développeur web depuis 2010, créateur de Gekkode et d’Email Impact. Chaque article est testé sur un projet réel avant publication. Contact
Newsletter

Les nouveaux tests, tutoriels et projets, par e-mail.

Tests reproductibles, code versionné, résultats datés. Jamais de spam.