Pas de sortie avant d'envoyer des en-têtes!
Les fonctions qui envoient / modifient des en-têtes HTTP doivent être appelées avant toute sortie .
summary ⇊
Sinon, l'appel échoue:
Avertissement: impossible de modifier les informations d'en-tête - en-têtes déjà envoyés (sortie démarrée au script: ligne )
Certaines fonctions modifiant l'en-tête HTTP sont:
La sortie peut être:
Intentionnel:
print
, echo
Et d' autres fonctions fournissant une sortie
- Code
<html>
antérieur des sections brutes <?php
.
Pourquoi cela arrive-t-il?
Pour comprendre pourquoi les en-têtes doivent être envoyés avant la sortie, il est nécessaire de regarder une
réponse HTTP typique . Les scripts PHP génèrent principalement du contenu HTML, mais transmettent également un ensemble d'en-têtes HTTP / CGI au serveur Web:
HTTP/1.1 200 OK
Powered-By: PHP/5.3.7
Vary: Accept-Encoding
Content-Type: text/html; charset=utf-8
<html><head><title>PHP page output page</title></head>
<body><h1>Content</h1> <p>Some more output follows...</p>
and <a href="/"> <img src=internal-icon-delayed> </a>
La page / sortie suit toujours les en-têtes. PHP doit d'abord transmettre les en-têtes au serveur Web. Il ne peut le faire qu'une seule fois. Après le double saut de ligne, il ne pourra plus les modifier.
Lorsque PHP reçoit la première sortie ( print
, echo
, <html>
) il
chasse d' eau tous les en- têtes collectées. Ensuite, il peut envoyer toutes les sorties souhaitées. Mais l'envoi d'autres en-têtes HTTP est alors impossible.
Comment savoir où la sortie prématurée s'est produite?
L' header()
avertissement contient toutes les informations pertinentes pour localiser la cause du problème:
Avertissement: impossible de modifier les informations d'en-tête - en-têtes déjà envoyés par
(sortie démarrée à / www / usr2345 / htdocs / auth.php: 52 ) dans /www/usr2345/htdocs/index.php sur la ligne 100
Ici, «ligne 100» fait référence au script où l' header()
invocation a échoué.
La note " sortie commencée à " entre parenthèses est plus significative. Il dénomme la source de la production précédente. Dans cet exemple, c'est auth.php
et line52
. C'est là que vous avez dû rechercher une sortie prématurée.
Causes typiques:
Impression, écho
La sortie intentionnelle de print
et les echo
instructions mettront fin à la possibilité d'envoyer des en-têtes HTTP. Le flux d'application doit être restructuré pour éviter cela. Utilisez des fonctions
et des modèles de modèles. Assurez header()
- vous que les appels se produisent avant que les messages ne soient écrits.
Les fonctions qui produisent une sortie incluent
print
, echo
, printf
,vprintf
trigger_error
, ob_flush
, ob_end_flush
, var_dump
,print_r
readfile
, passthru
, flush
, imagepng
,imagejpeg
entre autres et des fonctions définies par l'utilisateur.
Zones HTML brutes
Les sections HTML non analysées dans un .php
fichier sont également sorties directement. Les conditions de script qui déclencheront un header()
appel doivent être notées avant tout<html>
bloc brut .
<!DOCTYPE html>
<?php
// Too late for headers already.
Utilisez un modèle de modèle pour séparer le traitement de la logique de sortie.
- Placez le code de traitement des formulaires au-dessus des scripts.
- Utilisez des variables de chaîne temporaires pour différer les messages.
- La logique de sortie réelle et la sortie HTML mélangée doivent suivre en dernier.
Espace avant <?php
pour les avertissements "script.php ligne 1 "
Si l'avertissement fait référence à une sortie en ligne 1
, il s'agit principalement d' espaces , de texte ou de HTML en tête avant le <?php
jeton d' ouverture .
<?php
# There's a SINGLE space/newline before <? - Which already seals it.
De même, cela peut se produire pour des scripts ou des sections de script ajoutés:
?>
<?php
PHP mange en fait un seul saut de ligne après la fermeture des balises. Mais cela ne compensera pas les nouvelles lignes ou les tabulations ou les espaces décalés dans de tels écarts.
BOM UTF-8
Les sauts de ligne et les espaces seuls peuvent être un problème. Mais il y a aussi des séquences de caractères "invisibles" qui peuvent provoquer cela. Le plus connu est la
nomenclature UTF-8 (Byte-Order-Mark)
qui n'est pas affichée par la plupart des éditeurs de texte. Il s'agit de la séquence d'octets EF BB BF
, qui est facultative et redondante pour les documents encodés en UTF-8. PHP doit cependant le traiter comme une sortie brute. Il peut apparaître sous la forme de caractères 
dans la sortie (si le client interprète le document en latin-1) ou des "ordures" similaires.
En particulier, les éditeurs graphiques et les IDE basés sur Java sont inconscients de sa présence. Ils ne le visualisent pas (obligé par la norme Unicode). Cependant, la plupart des éditeurs de programmeurs et de consoles font:
Là, il est facile de reconnaître le problème dès le début. D'autres éditeurs peuvent identifier sa présence dans un menu fichier / paramètres (le Bloc-notes ++ sur Windows peut identifier et
résoudre le problème ). Une autre option pour inspecter la présence de nomenclatures consiste à recourir à un hexeditor . Les systèmes * nix hexdump
sont généralement disponibles, sinon une variante graphique qui simplifie l'audit de ces problèmes et d'autres:
Une solution simple consiste à définir l'éditeur de texte pour enregistrer les fichiers en tant que "UTF-8 (pas de nomenclature)" ou une autre nomenclature similaire. Souvent, les nouveaux arrivants recourent à la création de nouveaux fichiers et copient et collent simplement le code précédent.
Utilitaires de correction
Il existe également des outils automatisés pour examiner et réécrire les fichiers texte ( sed
/awk
ou recode
). Pour PHP en particulier, il y a le phptags
tag tidier . Il réécrit les balises de fermeture et d'ouverture en formes longues et courtes, mais résout également facilement les problèmes d'espaces blancs de début et de fin, Unicode et UTF-x:
phptags --whitespace *.php
Il est raisonnable de l'utiliser sur un répertoire d'inclusion ou de projet.
Espace après ?>
Si la source d'erreur est mentionnée comme derrière la
fermeture,?>
c'est là que des espaces ou du texte brut ont été écrits. Le marqueur de fin PHP ne termine pas l'exécution du script à ce stade. Tous les caractères de texte / espace après cela seront toujours écrits en tant que contenu de page.
Il est généralement conseillé, en particulier aux nouveaux arrivants, que les ?>
balises de fermeture PHP de fin soient omises. Cela évite une petite partie de ces cas. (Très souvent, les include()d
scripts sont le coupable.)
Source d'erreur mentionnée comme "Inconnu sur la ligne 0"
Il s'agit généralement d'une extension PHP ou d'un paramètre php.ini si aucune source d'erreur n'est concrétisée.
- C'est parfois le
gzip
paramètre d'encodage du flux
ou leob_gzhandler
.
- Mais il peut également s'agir de n'importe quel
extension=
module doublement chargé générant un message implicite de démarrage / avertissement PHP.
Messages d'erreur précédents
Si une autre instruction ou expression PHP provoque l'impression d'un message ou d'un avertissement, cela compte également comme une sortie prématurée.
Dans ce cas, vous devez éviter l'erreur, retarder l'exécution de l'instruction ou supprimer le message avec, par exemple,
isset()
ou @()
- lorsque l'un ou l' autre n'entrave pas le débogage plus tard.
Pas de message d'erreur
Si vous avez error_reporting
ou display_errors
désactivé par php.ini
, aucun avertissement ne s'affichera. Mais ignorer les erreurs ne fera pas disparaître le problème. Les en-têtes ne peuvent toujours pas être envoyés après une sortie prématurée.
Ainsi, lorsque les header("Location: ...")
redirections échouent silencieusement, il est très conseillé de rechercher des avertissements. Réactivez-les avec deux commandes simples au sommet du script d'invocation:
error_reporting(E_ALL);
ini_set("display_errors", 1);
Ou set_error_handler("var_dump");
si tout le reste échoue.
En parlant d'en-têtes de redirection, vous devez souvent utiliser un idiome comme celui-ci pour les chemins de code finaux:
exit(header("Location: /finished.html"));
De préférence même une fonction utilitaire, qui imprime un message utilisateur en cas de header()
panne.
Tampon de sortie comme solution de contournement
La mise en mémoire tampon de sortie PHP
est une solution de contournement pour résoudre ce problème. Il fonctionne souvent de manière fiable, mais ne doit pas se substituer à une bonne structuration de l'application et à la séparation de la sortie de la logique de contrôle. Son objectif réel est de minimiser les transferts par blocs vers le serveur Web.
Le output_buffering=
cadre peut néanmoins aider. Configurez-le dans le php.ini
ou via .htaccess
ou même .user.ini sur les configurations FPM / FastCGI modernes.
L'activer permettra à PHP de tamponner la sortie au lieu de la transmettre instantanément au serveur Web. PHP peut donc agréger les en-têtes HTTP.
Il peut également être engagé avec un appel au ob_start();
sommet du script d'invocation. Ce qui est cependant moins fiable pour plusieurs raisons:
Même s'il <?php ob_start(); ?>
démarre le premier script, les espaces ou une nomenclature peuvent être mélangés avant, ce qui le rend inefficace .
Il peut cacher des espaces pour la sortie HTML. Mais dès que la logique d'application tente d'envoyer du contenu binaire (une image générée par exemple), la sortie étrangère en mémoire tampon devient un problème. (Nécessaire ob_clean()
comme solution de contournement supplémentaire.)
Le tampon est de taille limitée et peut facilement dépasser lorsqu'il est laissé aux valeurs par défaut. Et ce n'est pas rare non plus, difficile à retrouver
quand cela se produit.
Les deux approches peuvent donc devenir peu fiables - en particulier lors du basculement entre les configurations de développement et / ou les serveurs de production. C'est pourquoi la mise en mémoire tampon de sortie est largement considérée comme une béquille / strictement une solution de contournement.
Voir également l' exemple d'utilisation de base
dans le manuel, et pour plus d'avantages et d'inconvénients:
Mais cela a fonctionné sur l'autre serveur!?
Si vous n'avez pas reçu d'avertissement d'en-tête auparavant, le paramètre de mise en mémoire tampon de sortie php.ini
a changé. Il est probablement non configuré sur le serveur actuel / nouveau.
Vérification avec headers_sent()
Vous pouvez toujours utiliser headers_sent()
pour sonder s'il est toujours possible d'envoyer des en-têtes. Ce qui est utile pour imprimer conditionnellement une information ou appliquer une autre logique de secours.
if (headers_sent()) {
die("Redirect failed. Please click on this link: <a href=...>");
}
else{
exit(header("Location: /user.php"));
}
Les solutions de rechange utiles sont:
HTML <meta>
tag
Si votre application est structurellement difficile à corriger, un moyen simple (mais quelque peu non professionnel) d'autoriser les redirections consiste à injecter une <meta>
balise HTML
. Une redirection peut être réalisée avec:
<meta http-equiv="Location" content="http://example.com/">
Ou avec un court délai:
<meta http-equiv="Refresh" content="2; url=../target.html">
Cela conduit à un code HTML non valide lorsqu'il est utilisé après la <head>
section. La plupart des navigateurs l'acceptent toujours.
Redirection JavaScript
Comme alternative, une redirection JavaScript
peut être utilisée pour les redirections de page:
<script> location.replace("target.html"); </script>
Bien que cela soit souvent plus conforme à HTML que la <meta>
solution de contournement, cela implique une dépendance à l'égard des clients compatibles JavaScript.
Les deux approches font cependant des solutions de rechange acceptables lorsque les appels HTTP header () authentiques échouent. Idéalement, vous combinez toujours cela avec un message convivial et un lien cliquable en dernier recours. (Ce qui est par exemple ce que fait l' extension http_redirect ()
PECL.)
Pourquoi setcookie()
et session_start()
sont également touchés
Les deux setcookie()
et session_start()
doivent envoyer un Set-Cookie:
en-tête HTTP. Les mêmes conditions s'appliquent donc et des messages d'erreur similaires seront générés pour les situations de sortie prématurées.
(Bien sûr, ils sont en outre affectés par les cookies désactivés dans le navigateur, ou même par des problèmes de proxy. La fonctionnalité de session dépend évidemment aussi de l'espace disque libre et d'autres paramètres php.ini, etc.)
Liens supplémentaires