php

Version de php : à partir de la 5.3-10.

 

accueil | console | VBScript | PowerShell | php | MySQL | documentation | formation  | trucs et astuces | exemples | glossaire

 

 

 

 

Généralités

La gestion d'erreurs dans l'environnement php

Programmation de la gestion des erreurs dans un script php

Error Handler en environnement php

Journalisation des erreurs en environnement php

 

 

I - GENERALITES

 

Le langage php (prononcez péachepé) a été créé en 1994 par un certain danois-canadien génial nommé Rasmus Lerdorf pour gérer son CV sur Internet. Php signifiait au départ "Personal Home Page". Lorsqu'il mit ses sources à la disposition du public, le php fut repris en 1997 par deux israéliens, Andi Gutmans et Zeev Suraski, cofondateurs de ZEND.

 

La particularité de php est que c'est un langage de programmation côté serveur (comme ASP ou CGI ou JSP), contrairement à VBscript (ou C++ ou ce que vous voulez) qui est un langage de programmation côté client (un équivalent de VBScript côté client est Javascript, mais il peut être désactivé dans un navigateur, contrairement à php). Une grosse différence entre un langage de scripts côté serveur et ceux qui tournent sur un ordinateur client réside dans le fait que, pour des raisons évidentes de sécurité, les scripts côté serveur ne peuvent pas techniquement interagir sur la machine qui les héberge. Leur seule mission est de construire des pages HTML et d'accéder à des bases de données, la plus connue et la plus utilisée (côté serveur) étant MySQL.

 

Petit détail : on ne traite pas de PDO dans cette page. La programmation orientée objet en PHP, on verra ça plus tard...

 

[début]


 

II - LA GESTION D'ERREURS DANS L'ENVIRONNEMENT php

 

Php propose deux moyens de gérer les erreurs d'exécution (les erreurs de compilation ne sont pas gérables et leur programmation ne présenterait aucun intérêt puisque le script se "plante" dès qu'il en rencontre une).

 

Ce sont :

  • la méthode directe, qui consiste à tester la valeur d'un code erreur retourné par la fonction système appelée.

 

Exemple :

 

$fp = @fsockopen('ssl://ipnpb.paypal.com', 443, $errno, $errstr, 30);
if ($errno != 0)
{
   // ERREUR HTTP
   echo "%NIP-F-SOCKOPEN, Paypal SSL HTTP error " . $errno . " " . $errstr;
   exit;
}
else

 

et

  • la méthode indirecte, qui consiste à écrire : "s'il se passe quelque chose de pas normal, allons voir ce que c'est".

 

Exemple :

 

$fp = @fsockopen('ssl://ipnpb.paypal.com', 443, $errno, $errstr, 30);
if (!$fp)
{
   // ERREUR HTTP
   echo "%NIP-F-SOCKOPEN, Paypal SSL HTTP error " . $errno . " " . $errstr;
   exit;
}
else

 

A première vue, on dirait bien que l'on a programmé la même chose... et bien pas du tout. Il peut y avoir des cas où un fsockopen (ou un pfsockopen) se plante sans positionner la valeur de son $errno. La doc php dit : "Si la valeur retournée par $errno est 0 et que la fonction retourne FALSE, ce peut être une indication laissant penser que l'erreur est survenue avant l'appel à connect(). La plupart du temps, cela est dû à un problème d'initialisation du socket." (voir ici).

 

Donc, c'est la deuxième programmation qui est à privilégier.

 

La méthode indirecte peut fait l'objet d'une programmation synchrone (via un error handler programmé avec set_error_handler) ou asynchrone  (via un error handler exceptionnel utilisant set_exception_handler. Voir plus loin).

 

On l'a déjà vu dans les pages qui précèdent, la programmation synchrone consiste à tester un "return status" (ici $fp) et à faire ceci ou cela selon sa valeur (en général aller lire le dernier code erreur renvoyé par le système), alors qu'un error handler asynchrone démarre tout seul en cas de problème et exécute son propre code.

 

Quelques mots sur le caractère "@" devant l'appel à la fonction. Ce caractère dit au système de ne pas afficher lui-même l'erreur, sinon on aurait deux fois le message, et le premier est loin d'être sympathique.

 

Jugez-en plutôt :

 

test_toto.php (sans le gastéropode)

<?php
$fp = fsockopen('ssl://www.toto.com', 443, $errno, $errstr, 30);
if (!$fp)
{
   // ERREUR HTTP
   echo "%NIP-F-SOCKOPEN, SSL HTTP error " . $errno . " " . $errstr;
   exit;
}
?>

 

Résultat

Warning: fsockopen() [function.fsockopen]: unable to connect to ssl://www.toto.com:443 (Connection refused) in /home/www/62585963bc9e2a5e92eba0113c714f19/web/vbscript/test_toto.php on line 2
%NIP-F-SOCKOPEN, SSL HTTP error 111 Connection refused

 

 

test_toto.php (avec le gastéropode)

<?php
$fp = @fsockopen('ssl://www.toto.com', 443, $errno, $errstr, 30);
if (!$fp)
{
   // ERREUR HTTP
   echo "%NIP-F-SOCKOPEN, SSL HTTP error " . $errno . " " . $errstr;
   exit;
}
?>

 

Résultat

%NIP-F-SOCKOPEN, SSL HTTP error 111 Connection refused

 

Mieux, non ?

 

 

[début]


 

III - PROGRAMMATION DE LA GESTION DES ERREURS DANS UN SCRIPT php

 

On ne voit pas comment on pourrait mieux expliquer tout ce qu'il y a dans le manuel, donc, c'est par ici.

 

La seule chose à répéter, c'est l'absolue nécessité de tester systématiquement tous les return status.

 


Exemple pour MySQL (que l'on verra plus en détails ici) :

 

$db_server = "mysql.monsite.com";
$db_basename = "mabase";
$db_user = "root";
$db_pwd = "123456789";

$rs = mysql_connect($db_server, $db_user, $db_pwd);
if (!$rs)
{
   echo "%NIP-F-SQLCONNECT, Connect error " . mysql_errno() . " " . mysql_error();
   exit;
}
$rs = mysql_select_db($db_basename);
if (!$rs)
{
   echo "%NIP-F-SQLOPEN, Open error " . mysql_errno() . " " . mysql_error();
   exit;
}
$query = "SELECT * FROM utilisateurs WHERE idTransaction= '$txn_id'";
$rs = mysql_query($query);
if (!$rs)
{
   $err = "%NIP-F-SQLQUERY_1, Query error " . mysql_errno() . " " . mysql_error();
   exit;
}
$data = mysql_fetch_object($query);

if (!$data)

{

 

Le message avec le code d'identification SQLQUERY_1 est très important. En effet, dans le cas de la gestion de bases de données (ou de fichiers classiques, mais ils sont de plus en plus rares), vous pouvez effectuer dans un même source des dizaines d'opérations. Donc, avec un quantième dans le message d'erreur, vous vous facilitez la vie pour retrouver la ligne qui a planté dans votre source.

 

On verra dans la page dédiée à MySQL la différence entre une instruction qui se plante : if (!$rs) et une instruction qui ne renvoie rien : if (!$data).

 

[début]


 

IV - ERROR HANDLER EN ENVIRONNEMENT php

 

Idem que ci-dessus, tout est dans la doc ! Mais il importe de différencier la gestion synchrone de la gestion asynchrone.

 

 

Prenons un exemple :

 

Vous voulez ouvrir une base sur une machine distante. Vous testez votre Open, et s'il ne se passe pas comme prévu, vous exécutez un code de gestion d'erreur correspondant. Ce sera dans ce cas une gestion synchrone : on agit, il y a un problème, on réagit.

 

Mais si, pendant une transaction, le lien réseau tombe, par exemple, il n'existe pas d'instruction disant "tant que le lien est up je fais ceci, et quand il est down je fais cela". Cette situation est gérée par ce que certains appellent un error handler asynchrone ou exception handler. En informatique, une exception est un événement imprévu, il ne peut donc être programmé. D'où l'intérêt des procédures asynchrones. Tous les langages évolués (sauf VBScript) offrent une programmation asynchrone des erreurs.

  • Pour programmer un error handler synchrone, utiliser :

set_error_handler

restore_error_handler

 

  • Pour programmer un error handler asynchrone, ou exception handler, utiliser :

set_exception_handler

restore_exception_handler

 

 

Les erreurs renvoyées (ou non) par php sont programmables avec la fonction

 

error_reporting

 

 

L'affichage (ou non) des erreurs renvoyées par php se gère avec la directive

 

display_errors

 

qui se positionne dans le php.ini

 

 

Et pour tout savoir sur la gestion des erreurs en php, c'est ici ! :-)

 

 

Remarque

 

Pour éviter de donner un bâton pour se faire battre par le hacker de passage, il est plus qu'évident que vous devez garder un contrôle absolu sur les messages d'erreur qui s'affichent. Si votre serveur publie sur la page de votre visiteur des messages comme : "impossible d'ouvrir le fichier [path]my.ini, fichier en cours de mise à jour par un autre process", vous cherchez la bagarre et c'est vous qui perdrez. Donc, autant vous devez (à notre avis) être le plus exhaustif possible en cas d'erreurs fatales (les autres, l'utilisateur n'a pas besoin de les connaître), autant vous devez désactiver la diffusion automatique des erreurs avec le paramètre display_errors OFF.

 

 

 

 

[début]


 

V - JOURNALISATION DES ERREURS EN ENVIRONNEMENT php

 

Au risque de se répéter, rien de mieux que la doc, car php comporte une fonction error_log. Elle est pas belle, la vie ?

 

Cela dit, on peut préférer avoir son propre journal d'événements. Voici donc un exemple :

 

 

a) Subroutine d'écriture dans le journal (à mettre dans tous les scripts non interactifs)

 

//--------------------------------------------------------------------------------
function mylog($desc)
{
   $logFile = "errlog.htm";
   $ligne = "<br>" . date('Y-m-d H:i:s')." : $desc";

// ouverture du fichier de log, le mode "a+" permet d'écrire à la fin
   if($fp = fopen($logFile, "a+"))
   {
//    écriture de la ligne à concurrence de 1024 caractères
      fwrite($fp, $ligne, 1024);
//    fermeture du fichier
      fclose($fp);
   }
}

 

L'intérêt d'ouvrir et de fermer le fichier de log à chaque fois permet à d'autres scripts de faire pareil...

 

 

b) Exemple d'utilisation

 

$header = "POST /cgi-bin/webscr HTTP/1.0\r\n";
$header .= "Host: ipnpb.paypal.com:443\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($req) . "\r\n\r\n";

mylog ("ouverture socket réel SSL");

$fp = fsockopen ('ssl://ipnpb.paypal.com', 443, $errno, $errstr, 30);
if (!$fp)
{
// ERREUR HTTP
   $err = "%NIP-F-SOCKOPEN, Paypal SSL HTTP error " . $errno . " " . $errstr;
   mylog ($err);
   mylog ("Script interrompu anormalement.");
   exit;
}
else
{
   fputs ($fp, $header . $req);
   while (!feof($fp))
   {
      $res = fgets ($fp, 1024);
      mylog ("res = $res"); // debug
      if (strcmp ($res, "VERIFIED") == 0)
      {
         mylog ("Notification validée par Paypal"); // voir doc PP_OrderManagement_IntegrationGuide.pdf

 

 

 

c) Résultat (log réel d'un script de notification de paiement Paypal)

 

Dans cet exemple, les deux scripts appelés par Paypal en fin de paiement (le pdt et le nip) écrivent dans le même journal :

 

errlog.htm

2012-04-02 18:33:59 : pdt.php - === pdt.php version 1.2-0. Début. ===
2012-04-02 18:33:59 : pdt.php - open real ssl
2012-04-02 18:34:00 : pdt.php - read data
2012-04-02 18:34:02 : pdt.php - HTTP/1.1 200 OK
2012-04-02 18:34:02 : pdt.php - Date: Mon, 02 Apr 2012 16:34:00 GMT
2012-04-02 18:34:02 : pdt.php - X-Frame-Options: SAMEORIGIN
2012-04-02 18:34:02 : pdt.php - Set-Cookie: cwrClyrK4LoCV1fydGbAxiNL6iG=fpNm6GtXhKBsRHaofhebFUXn7ka2DVBoeJag8yrKPKw9IuNo2vfZbWfWYDbKyR3fBgRE9v_ccZ-aNkEhQrwu3ADCd9cOIDiOgs6p9lcYowiPtrSzH4AgN0ItuG%7cWYg3h76uVSILudm62UBlSEGwLt9j8_uny4e28vtnMUKEyXBa_2ax-1OYqLIW-eXJLmwQzW%7cjsHEznAx5CWfbm6RQCwcZL7cY76vEhsNLXLeSZF24OIzERQw5N8J6u0XW6UjWwSt5o8PKm%7c1333384441; domain=.paypal.com;

path=/; Secure; HttpOnly
2012-04-02 18:34:02 : pdt.php - Set-Cookie: KHcl0EuY7AKSMgfvHl7J5E7hPtK=DDGa1PCdR-fxuNwUSqSsnURS1SaMF34M233DvKkvhT

QxXiSKnl-bFncUOMPIVva0rE6IIAsRZT2dOtz7; expires=Sun, 28-Mar-2032 16:34:01 GMT; domain=.paypal.com; path=/; Secure;

HttpOnly
2012-04-02 18:34:02 : pdt.php - Set-Cookie: cookie_check=yes; expires=Thu, 31-Mar-2022 16:34:01 GMT; domain=.paypal.com;

path=/; Secure; HttpOnly
2012-04-02 18:34:02 : pdt.php - Set-Cookie: navcmd=_notify-synch; domain=.paypal.com; path=/; Secure; HttpOnly
2012-04-02 18:34:02 : pdt.php - Set-Cookie: navlns=0.0; expires=Sun, 28-Mar-2032 16:34:01 GMT; domain=.paypal.com; path=/;

Secure; HttpOnly
2012-04-02 18:34:02 : pdt.php - Set-Cookie: Apache=10.74.8.47.1333384440879186; path=/; expires=Wed, 26-Mar-42 16:34:00 GMT
2012-04-02 18:34:02 : pdt.php - Vary: Accept-Encoding
2012-04-02 18:34:02 : pdt.php - Strict-Transport-Security: max-age=14400
2012-04-02 18:34:02 : pdt.php - Connection: close
2012-04-02 18:34:02 : pdt.php - Content-Type: text/html; charset=UTF-8
2012-04-02 18:34:02 : pdt.php - Set-Cookie: TSe9a623=5501c5b9892f6bb098088c463413d9a796f03f3254fbcc334f79d4f8a0e24589192e4a1867835503f1634137d160814ba64fcf87682d8f7c;

Path=/
2012-04-02 18:34:02 : pdt.php -
2012-04-02 18:34:02 : pdt.php - SUCCESS
2012-04-02 18:34:02 : pdt.php - mc_gross=12.00
2012-04-02 18:34:02 : pdt.php - protection_eligibility=Ineligible
2012-04-02 18:34:02 : pdt.php - payer_id=AA9DAAAAACV36
2012-04-02 18:34:02 : pdt.php - tax=0.00
2012-04-02 18:34:02 : pdt.php - payment_date=09%3A33%3A50+Apr+02%2C+2012+PDT
2012-04-02 18:34:02 : pdt.php - payment_status=Completed
2012-04-02 18:34:02 : pdt.php - charset=windows-1252
2012-04-02 18:34:02 : pdt.php - first_name=Didier
2012-04-02 18:34:02 : pdt.php - mc_fee=0.66
2012-04-02 18:34:02 : pdt.php - custom=N6qytUlc28
2012-04-02 18:34:02 : pdt.php - payer_status=verified
2012-04-02 18:34:02 : pdt.php - business=toto%40gmail.com
2012-04-02 18:34:02 : pdt.php - quantity=1
2012-04-02 18:34:02 : pdt.php - payer_email=tata%40gmail.com
2012-04-02 18:34:02 : pdt.php - txn_id=72001234567899443P
2012-04-02 18:34:02 : pdt.php - payment_type=instant
2012-04-02 18:34:02 : pdt.php - btn_id=43469576
2012-04-02 18:34:02 : pdt.php - last_name=Morandi
2012-04-02 18:34:02 : pdt.php - receiver_email=toto%40gmail.com
2012-04-02 18:34:02 : pdt.php - payment_fee=
2012-04-02 18:34:02 : pdt.php - shipping_discount=0.00
2012-04-02 18:34:02 : pdt.php - insurance_amount=0.00
2012-04-02 18:34:02 : pdt.php - receiver_id=AF8E123456K4W
2012-04-02 18:34:02 : pdt.php - txn_type=web_accept
2012-04-02 18:34:02 : pdt.php - item_name=Page+Web+JaiTrouveUnJob+perso
2012-04-02 18:34:02 : pdt.php - discount=0.00
2012-04-02 18:34:02 : pdt.php - mc_currency=EUR
2012-04-02 18:34:02 : pdt.php - item_number=
2012-04-02 18:34:02 : pdt.php - residence_country=FR
2012-04-02 18:34:02 : pdt.php - handling_amount=0.00
2012-04-02 18:34:02 : pdt.php - shipping_method=Default
2012-04-02 18:34:02 : pdt.php - transaction_subject=N6qytUlc28
2012-04-02 18:34:02 : pdt.php - payment_gross=
2012-04-02 18:34:02 : pdt.php - shipping=0.00
2012-04-02 18:34:02 : pdt.php - validation status is SUCCESS
2012-04-02 18:34:02 : pdt.php - payment status is Completed
2012-04-02 18:34:02 : pdt.php - connect to SQL server
2012-04-02 18:34:02 : pdt.php - connect to SQL database
2012-04-02 18:34:02 : pdt.php - update database
2012-04-02 18:34:02 : pdt.php - === end of processing ===
2012-04-02 18:34:04 : nip.php - === nip.php version v3.2-0. Début. ===
2012-04-02 18:34:04 : nip.php - lecture des data du Post
2012-04-02 18:34:04 : nip.php - Paypal Transaction ID = >7200123456443P<
2012-04-02 18:34:04 : nip.php - Paiement status = >Completed<
2012-04-02 18:34:04 : nip.php - Paiement date = >09:33:50 Apr 02, 2012 PDT<
2012-04-02 18:34:04 : nip.php - Buyer Last Name = >Morandi<
2012-04-02 18:34:04 : nip.php - Buyer First Name = >Didier<
2012-04-02 18:34:04 : nip.php - SQL unique ID = >N6qytUlc28<
2012-04-02 18:34:04 : nip.php - Paiement amount = >12.00<
2012-04-02 18:34:04 : nip.php - Paiement Currency = >EUR<
2012-04-02 18:34:04 : nip.php - Pending reason (if any) = ><
2012-04-02 18:34:04 : nip.php - Refund reason (if any) = ><
2012-04-02 18:34:04 : nip.php - Buyer email = >tata@gmail.com<
2012-04-02 18:34:04 : nip.php - Vendor email = >toto@gmail.com<
2012-04-02 18:34:04 : nip.php - ouverture socket réel SSL
2012-04-02 18:34:05 : nip.php - res = HTTP/1.1 200 OK
2012-04-02 18:34:05 : nip.php - res = Date: Mon, 02 Apr 2012 16:34:05 GMT
2012-04-02 18:34:05 : nip.php - res = Server: Apache
2012-04-02 18:34:05 : nip.php - res = X-Frame-Options: SAMEORIGIN
2012-04-02 18:34:05 : nip.php - res = Set-Cookie: cwrClyrK4LoCV1fydGbAxiNL6iG=mySlc6iq3FnBdG9GCU6iUtp3L6gV0LzsCleYdcHa0ip5nSUf1q3arH3ZrYYG7WvztKWgK16lflt8bY5R-eoKsnFLjbkyj

GFkD469wm494-4R9QTwz7nkiRbYbtgtoxr3b1QFHm%7cE3fmytBiRZ1j9Wiy0TlohAexgnzW5UVlKs5B3zXv7rGUZUauOo8FMKiehpDYUbWCTsr7gW%7cIEg3S

4oNnH4-Bl20vffhR43c4D4mqBsDE2Qk9tyiHYhhSE_LL7y8l_pCrJ6ABzMRS5I_6m%7c1333384445; domain=.paypal.com; path=/; Secure; HttpOnly
2012-04-02 18:34:05 : nip.php - res = Set-Cookie: cookie_check=yes; expires=Thu, 31-Mar-2022 16:34:05 GMT; domain=.paypal.com;

path=/; Secure; HttpOnly
2012-04-02 18:34:05 : nip.php - res = Set-Cookie: navcmd=_notify-validate; domain=.paypal.com; path=/; Secure; HttpOnly
2012-04-02 18:34:05 : nip.php - res = Set-Cookie: navlns=0.0; expires=Sun, 28-Mar-2032 16:34:05 GMT; domain=.paypal.com;

path=/; Secure; HttpOnly
2012-04-02 18:34:05 : nip.php - res = Set-Cookie: Apache=10.73.8.52.1333384445273661; path=/; expires=Wed, 26-Mar-42 16:34:05

GMT
2012-04-02 18:34:05 : nip.php - res = Vary: Accept-Encoding
2012-04-02 18:34:05 : nip.php - res = Strict-Transport-Security: max-age=14400
2012-04-02 18:34:05 : nip.php - res = Connection: close
2012-04-02 18:34:05 : nip.php - res = Content-Type: text/html; charset=UTF-8
2012-04-02 18:34:05 : nip.php - res =
2012-04-02 18:34:05 : nip.php - res = VERIFIED
2012-04-02 18:34:05 : nip.php - Notification validée par Paypal
2012-04-02 18:34:05 : nip.php - connection au serveur SQL
2012-04-02 18:34:05 : nip.php - ouverture de la base
2012-04-02 18:34:05 : nip.php - payment status : completed
2012-04-02 18:34:05 : nip.php - execute query
2012-04-02 18:34:05 : nip.php - la base a été mise à jour
2012-04-02 18:34:05 : nip.php - === Fin normale du traitement ===

 

Croyez-en l'auteur, c'est un outil in-con-tour-na-ble... :-)

 

Demain, MySQL.

 

 

[début]


 

accueil | console | VBScript | PowerShell | documentation | formation  | trucs et astuces | exemples | glossaire