OUJOOD.COM
Pourquoi utiliser set_local_infile_handler()
Importer un fichier CSV dans MySQL avec LOAD DATA LOCAL INFILE, c'est rapide. Mais que faire quand le fichier contient des lignes incomplètes, des valeurs à transformer, ou des entrées à rejeter avant l'insertion ? Sans mécanisme d'interception, vous importez tout, puis vous nettoyez après coup — sur de gros volumes, c'est coûteux.
set_local_infile_handler() règle ce problème directement. Elle vous permet de brancher une fonction de rappel (un callback) dans le mécanisme d'import. PHP appellera cette fonction pour chaque ligne lue dans le fichier. Vous décidez alors : on insère, ou on ignore. La base ne reçoit que ce que vous avez validé.
La fonction est disponible à partir de PHP 5 et s'utilise avec l'extension mysqli.
Ce que fait set_local_infile_handler()
En usage normal, LOAD DATA LOCAL INFILE lit un fichier et envoie chaque ligne directement à MySQL. Avec set_local_infile_handler(), vous intercalez votre propre logique entre la lecture et l'envoi.
La fonction s'écrit de deux façons selon votre style de code. La logique est identique dans les deux cas :
// Approche procédurale set_local_infile_handler(callable $handler, int $handle = null); // Approche orientée objet $db->set_local_infile_handler(callable $handler, int $handle = null);
Le premier paramètre $handler est la fonction que PHP appellera à chaque ligne. Le second paramètre $handle est facultatif : il sert d'identifiant si vous voulez désactiver le callback plus tard via unset_local_infile_handler(). Sans lui, le callback est retiré automatiquement après l'import.
En PHP 8+ avec un projet structuré, l'approche orientée objet est la plus lisible et la plus cohérente avec les frameworks actuels.
La fonction de rappel : comment ça marche
Votre callback reçoit un paramètre : un flux de données ($stream) depuis lequel vous lisez la ligne courante avec $stream->readLine(). Il doit retourner un booléen :
- true → la ligne est transmise à MySQL pour insertion.
- false → la ligne est ignorée, aucune insertion n'a lieu.
C'est cette logique simple qui rend la fonction utile : n'importe quelle règle de validation peut s'écrire ici, sans toucher au reste du code.
Premiers exemples : voir la mécanique en action
Avant d'aborder les cas complets, deux exemples courts pour comprendre comment le callback s'articule avec l'import.
Le premier callback affiche chaque ligne lue sans bloquer l'insertion :
<?php // Callback : affiche chaque ligne avant import function afficherLigne($stream) { $ligne = $stream->readLine(); echo $ligne . PHP_EOL; // true = on autorise l'insertion de cette ligne return true; } // Branchement du callback set_local_infile_handler('afficherLigne'); // Déclenchement de l'import $query = "LOAD DATA LOCAL INFILE 'data.txt' INTO TABLE perso"; mysqli_query($link, $query); ?>
Le second vérifie que la ligne est un JSON valide avec les champs attendus. Si ce n'est pas le cas, elle est rejetée silencieusement :
<?php // Callback : valide que la ligne est un JSON avec les champs "name" et "age" function validerJson($stream) { $donnees = json_decode($stream->readLine(), true); // JSON invalide ou champs manquants : on rejette la ligne if (!is_array($donnees) || !isset($donnees['name'], $donnees['age'])) { return false; } return true; } ?>
Cas d'utilisation concrets
Pour les exemples suivants, on utilise la base teste avec une table perso (champs : id, nom, prenom, email, age). La connexion de base est identique pour les deux approches :
<?php
$serveur = 'localhost';
$utilisateur = 'root';
$motDePasse = '';
$ma_base_de_donnees = 'teste';
// Connexion à la base (approche procédurale)
$link = mysqli_connect($serveur, $utilisateur, $motDePasse, $ma_base_de_donnees);
?>
Cas 1 : importer un fichier CSV dans une table MySQL
Situation classique : un fichier data.txt avec des colonnes séparées par des points-virgules, dont la première ligne contient les en-têtes. On veut insérer les données dans perso en sautant cette première ligne.
Voici à quoi ressemble le fichier :
id;nom;prenom;email;age 1;John;Doe;john.doe@example.com;25 2;Jane;Doe;jane.doe@example.com;23
Le callback doit lire la ligne, la découper, vérifier qu'elle contient bien 5 colonnes, puis construire la requête d'insertion. On utilise un compteur statique pour repérer la ligne d'en-tête — la première ligne reçue par le callback.
Approche procédurale
<?php
$link = mysqli_connect($serveur, $utilisateur, $motDePasse, $ma_base_de_donnees);
function importerDonnees($stream) {
global $link;
static $numLigne = 0;
$numLigne++;
// Première ligne = en-tête, on passe
if ($numLigne === 1) return false;
$ligne = $stream->readLine();
$champs = explode(";", trim($ligne));
// On vérifie qu'on a exactement 5 colonnes avant d'insérer
if (count($champs) !== 5) return false;
$query = "INSERT INTO perso (id, nom, prenom, email, age)
VALUES ($champs[0], '$champs[1]', '$champs[2]', '$champs[3]', $champs[4])";
mysqli_query($link, $query);
return true;
}
set_local_infile_handler('importerDonnees');
$query = "LOAD DATA LOCAL INFILE 'data.txt' INTO TABLE perso";
mysqli_query($link, $query);
?>
Approche orientée objet
La logique est identique, mais on utilise l'objet $db pour la connexion et l'appel du callback :
<?php
$db = new mysqli($serveur, $utilisateur, $motDePasse, $ma_base_de_donnees);
function importerDonnees($stream) {
global $db;
static $numLigne = 0;
$numLigne++;
// Ignorer la ligne d'en-tête
if ($numLigne === 1) return false;
$ligne = $stream->readLine();
$champs = explode(";", trim($ligne));
if (count($champs) !== 5) return false;
$query = "INSERT INTO perso (id, nom, prenom, email, age)
VALUES ($champs[0], '$champs[1]', '$champs[2]', '$champs[3]', $champs[4])";
$db->query($query);
return true;
}
// Branchement du callback via la méthode OO
$db->set_local_infile_handler('importerDonnees');
$db->query("LOAD DATA LOCAL INFILE 'data.txt' INTO TABLE perso");
?>
Cas 2 : valider les données avant insertion
Un champ nom vide ou un age absent, et l'entrée ne sert à rien dans la base. Mieux vaut la rejeter au moment de l'import que de stocker une ligne corrompue à nettoyer plus tard. Le callback retourne false dès qu'un champ obligatoire est absent — MySQL ne voit rien, aucune erreur n'est levée.
Approche procédurale
<?php
$link = mysqli_connect($serveur, $utilisateur, $motDePasse, $ma_base_de_donnees);
function validerDonnees($stream) {
static $numLigne = 0;
$numLigne++;
if ($numLigne === 1) return false; // Ignorer l'en-tête
$ligne = $stream->readLine();
$champs = explode(";", trim($ligne));
// nom (index 1) et age (index 4) sont obligatoires
if (empty($champs[1]) || empty($champs[4])) {
return false;
}
return true;
}
set_local_infile_handler('validerDonnees');
mysqli_query($link, "LOAD DATA LOCAL INFILE 'data.txt' INTO TABLE perso");
?>
Approche orientée objet
<?php
$db = new mysqli($serveur, $utilisateur, $motDePasse, $ma_base_de_donnees);
function validerDonnees($stream) {
static $numLigne = 0;
$numLigne++;
if ($numLigne === 1) return false;
$ligne = $stream->readLine();
$champs = explode(";", trim($ligne));
// Rejet si le nom ou l'âge est vide
if (empty($champs[1]) || empty($champs[4])) {
return false;
}
return true;
}
$db->set_local_infile_handler('validerDonnees');
$db->query("LOAD DATA LOCAL INFILE 'data.txt' INTO TABLE perso");
?>
Bonnes pratiques
- En production, remplacez les insertions directes par des requêtes préparées (
prepare/bind_param) dans le callback. Les valeurs non échappées exposent votre base aux injections SQL. - Utilisez une variable static dans le callback pour compter les lignes et ignorer la première (l'en-tête). C'est plus fiable que de passer par un compteur global.
- Testez toujours votre callback sur un fichier de quelques lignes avant de lancer un import massif. Un bug de validation qui retourne false sur toutes les lignes ne génère aucune erreur MySQL — il passe inaperçu sans log.
- Pour des flux continus (APIs, webhooks), cette approche batch n'est pas la plus adaptée. Des outils ETL ou des queues de messages conviennent mieux à ces cas.
Pour plus de cas d'utilisation voir autres exemples
Par carabde | Mis à jour le 20 avril 2026