# « Cannot modify header information – headers already sent » : trouver la cause

> Le message le plus mal compris de PHP dans une extension WordPress : il ne pointe presque jamais vers l'endroit réel du problème. Voici la méthode pour remonter à la source.

- Auteur : Clément Hadrot
- Publié le : 2021-02-12
- Mis à jour le : 2021-02-12
- Catégorie : Extensions
- URL : https://wpmoderne.dev.wordpress-developpement.fr/extensions/headers-already-sent-trouver-la-cause/

## L’essentiel

- Le message indique où PHP a tenté d'envoyer un en-tête, pas où la sortie a commencé
- Un BOM invisible ou un espace avant <?php suffit à déclencher l'erreur
- output_buffering peut masquer temporairement le symptôme sans corriger la cause

**Symptôme.** Une extension personnalisée développée pour un client du secteur associatif provoquait, de façon intermittente, le message `Warning: Cannot modify header information - headers already sent by (output started at ...)` au moment de sauvegarder un formulaire d'adhésion. La redirection censée suivre la sauvegarde ne se produisait jamais, laissant l'utilisateur face à un écran d'erreur PHP en haut de page.

## Comprendre ce que dit réellement le message

La première confusion à dissiper : le message d'erreur indique où PHP a tenté d'envoyer un en-tête HTTP en échec (généralement un appel à `wp_redirect()`, `wp_safe_redirect()` ou `setcookie()`), pas où la sortie parasite a commencé. La mention « output started at » donne heureusement cette seconde information, mais elle pointe souvent vers un fichier totalement éloigné du code qu'on soupçonne en premier.

```
Warning: Cannot modify header information - headers already sent
by (output started at /wp-content/plugins/kaolin-adhesions/kaolin-adhesions.php:1)
in /wp-includes/pluggable.php on line 1408
```

Ici, le numéro de ligne 1 du fichier principal de l'extension est le vrai indice : une sortie s'est produite dès la première ligne du fichier, bien avant que le code métier n'ait eu la moindre chance de s'exécuter.

## Diagnostic : traquer la sortie parasite

Dans l'immense majorité des cas que j'ai rencontrés, la cause se limite à quatre possibilités, classées par fréquence décroissante.

> L'essentiel à retenir : Le message indique où PHP a tenté d'envoyer un en-tête, pas où la sortie a commencé ; Un BOM invisible ou un espace avant <?php suffit à déclencher l'erreur ; output_buffering peut masquer temporairement le symptôme sans corriger la cause

- Un espace, une tabulation ou un retour à la ligne avant la balise ouvrante `<?php` du fichier concerné
- Une balise fermante `?>` en fin de fichier, suivie d'un retour à la ligne invisible dans l'éditeur
- Un caractère BOM (byte order mark) inséré silencieusement par certains éditeurs de texte lors de l'enregistrement en UTF-8
- Un appel de débogage oublié (`echo`, `print_r`, `var_dump`) exécuté avant la tentative de redirection

Pour ce projet précis, la cause était la troisième : le fichier principal avait été réenregistré par un éditeur de texte configuré en « UTF-8 avec BOM » après une intervention rapide d'un collègue, ajoutant trois octets invisibles en tout début de fichier. Ces trois octets suffisent à constituer une sortie, même si aucun caractère visible n'apparaît à l'écran.

## Correctif : localiser et supprimer la sortie

La commande `grep` en ligne de commande permet de repérer rapidement les balises fermantes suspectes dans l'ensemble des fichiers d'une extension :

```
grep -rn '?>$' wp-content/plugins/kaolin-adhesions/
```

Pour un BOM, un éditeur de texte capable d'afficher les caractères invisibles (ou une commande comme `file` sous Linux) permet de le confirmer :

```
file wp-content/plugins/kaolin-adhesions/kaolin-adhesions.php
# renvoie : ... UTF-8 Unicode (with BOM) text
```

Le correctif consiste à réenregistrer le fichier en UTF-8 sans BOM, une option disponible dans la quasi-totalité des éditeurs modernes, puis à supprimer toute balise fermante `?>` en fin de fichier PHP — elle n'est jamais requise par l'interpréteur et sa seule utilité est de créer ce genre de bug.

```
// Avant, en fin de fichier :
function kaolin_traiter_adhesion() { /* ... */ }
?>


// Après :
function kaolin_traiter_adhesion() { /* ... */ }
```

## Prévention : éviter que le problème ne revienne

Trois habitudes ont éliminé ce type d'incident pour de bon sur les projets suivants de cette agence.

- Bannir systématiquement la balise fermante `?>` en fin de fichier PHP, dans tous les fichiers de l'extension
- Configurer l'éditeur de l'équipe en UTF-8 sans BOM par défaut pour tous les fichiers PHP
- Ajouter une vérification automatique dans le processus d'intégration continue, via un script simple qui détecte les BOM sur l'ensemble du dépôt avant chaque déploiement

> Un dépannage utile en urgence, mais à ne jamais considérer comme un correctif définitif : activer `ob_start()` en tout début de fichier avale la sortie parasite et fait disparaître le symptôme, sans supprimer sa cause. Le problème resurgit inévitablement, souvent dans un contexte où `ob_start()` n'a pas été pensé.

## En résumé

« Headers already sent » n'est jamais une erreur aléatoire : c'est toujours une sortie physique, même invisible, qui s'est produite avant l'appel qui échoue. La méthode reste constante d'un projet à l'autre : lire attentivement la mention « output started at », vérifier balise fermante, espaces parasites et BOM dans le fichier indiqué, puis remonter au code de débogage oublié si les trois premières pistes ne donnent rien.
