logo oujood
🔍

set_local_infile_handler() en PHP : contrôler l'import de données MySQL ligne par ligne

Branchez un callback sur LOAD DATA LOCAL INFILE pour filtrer, valider ou transformer chaque ligne avant qu'elle atteigne votre base MySQL. Exemples concrets, approches procédurale et OO.

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 :

  📋 Copier le code

// 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 :

  📋 Copier le code

<?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 :

  📋 Copier le code

<?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 :

  📋 Copier le code

<?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 :

  📋 Copier le code

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

  📋 Copier le code

<?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 :

  📋 Copier le code

<?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

  📋 Copier le code

<?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

  📋 Copier le code

<?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