PHP - comment muter les erreurs et notice hors du watchdogs

26/06/2018

Avant toute chose il est nécessaire de se poser la question pourquoi, ou plutôt, dans quels cas devrait-on muter les erreurs php. Effectivement, la notion de ne pas afficher une erreur peut vous donner l'impression de vous simplifier la vie mais en réalité, elles sont là pour vous donner une indication sur le fait que certaines choses ne sont pas correctement pensées ou implémentées.

Les cas où vous devez impérativement régler le souci en profondeur

En effet, il faut bien se dire que les erreurs remontées par les interpreteurs, ne sont pas juste là pour vous pourrir la vie. PHP est un langage (trop) permissif notamment à cause de l'absence de typage strict des variables. Ne serait-ce que ce petit point va vous générer quelques warnings si vous ne prenez pas attention.

L'autre point qui génère potentiellement de nombreuses notices sont la gestion des index de tableaux. Si vous appelez directement une valeur d'un tableau par son index et que celui-ci n'est pas défini, vous générez une notice. Beaucoup d'entre vous seront probablement tentés de changer leurs paramètres d'affichage d'erreurs pour ne pas être ennuyés par ces notices. C'est à mon avis une mauvaise pratique. Ajouter des conditions et parfaire son code est moins coûteux en performance. Surtout que si vous avez lu mes articles concernant la conception, vous devez penser à coder indépendamment de l'environnement pour une réutilisation potentielle. 

En prenant ça en compte il faut se demander ce qui est plus coûteux à PHP : faire un test pour éviter la notice ou laisser PHP gérer celles-ci, les ajouter aux logs, les afficher.... Sans aucun doute la première solution est moins coûteuse.

 

Les cas où il peut être intéressant de muter les logs PHP localement.

Dans ce cas pourquoi faire un article sur la possibilité de supprimer des notices? J'ai rencontré plusieurs fois le cas où j'ai eu besoin de muter les erreurs php momentanément car ils gêneraient beaucoup trop de logs inutilement. Évidemment c'est en dernier recours que l'on doit se poser cette question. Et en effet ce sont souvent des modules contributeur qui dans votre cas particulier génèrent des nnotices.Le module n'est pas forcément mal codé, pas de méprise, mais il y a  potentiellement un cas qui n'a pas été envisagé, et c'est ce cas precis le votre malheureusement, qui génère une notice.

Il faut bien comprendre qu'un log inutil parmis 1000 logs intéressants n'est pas important. Là où ça devient gênant c'est quand le ratio s'inverse et qu'on trouve 1 info intéressante parmis 1000 logs inutiles identiques. Ça peut arriver notamment sur des tâches volumineuses comme des migrations, à partir de données mal formatées sur lesquelles vous n'avez pas la main. Il est donc intéressant dans ces cas de désactiver ou du moins de traiter différemment les erreurs php de base très localement.

 

La méthode pour muter les logs php localement

On va prendre l'exemple d'une méthode qui va générer un peu de logs. Dans la méthode suivante, notre ami PHP va générer quelques warnings car la source parsée en XML n'est pas correctement formatée (c'est un exemple).

class GoogleLoader{

  /**
  * Retourne le code source de la page https://www.google.com
  *
  * @return string
  **/
  public function getGoogleSource() {
    $client = \Drupal::httpClient();
    $url = 'https://www.google.com';
    $request = $client->request('GET', $url, array(
      'exceptions' => FALSE
    ));
    $body = $request->getBody();
  
  
    $dom = new \DOMDocument();
    // Cet appel va générer quelques warnings.
    $dom->loadHTML($body);
  
  }
}

 

La méthode ici va consister à redéfinir momentanément le gestionnaire d'erreur. Ainsi, toutes les erreurs vont atterrir dans votre gestionnaire et vous pourrez les traiter spécifiquement, ou les abandonner lâchement...

Pour cela on utilise la fonction native set_error_handler. Cette fonction permet de redéfinir le processus à utiliser par PHP en cas d'erreur. Il faut bien différencier cette méthode et une gestion d'exception. Dans le gestionnaire d'erreur on récupère toutes les erreurs / warnings / notices et pas uniquement les processus qui génèrent des exceptions.

class GoogleLoader{

  /**
  * Retourne le code source de la page https://www.google.com
  *
  * @return string
  **/
  public function getGoogleSource() {
    $client = \Drupal::httpClient();
    $url = 'https://www.google.com';
    $request = $client->request('GET', $url, array(
      'exceptions' => FALSE
    ));
    $body = $request->getBody();
  
  
    $dom = new \DOMDocument();

    // On modifie l'error handler juste avant l'appel à la méthode qui pose problème 
    set_error_handler(array($this, 'muteError'));
    try{
      $dom->loadHTML($body);
    }
    catch(\Exception $e){
      // Mute error.
    }
    // Et on restaure l'error handler par défault: 
    restore_error_handler();
  }
  
 /**
  * Fonction de récupération des erreurs
  **/ 
  protected function muteError($errno, $errstr, $errfile, $errline){
    // Mute error.
  }
}

Là, on passe bien dans la fonction muteError, et comme le nom de la fonction l'indique, on ne traite pas l'erreur. Ainsi les warnings ne vont pas être traités. Ici c'est suffisant, mais dans certains cas, on va vouloir aller plus loin et traiter les erreurs sur la masse, compter le nombre de warnings ou toute autre action.

 

 

Aller plus loin en passant par une classe ErrorTrap

Une autre manière de gérer les erreurs est de passer par une classe décontextualisée. Ceci devient utile lorsqu'on a plusieurs actions à "muter" de la même manière. Ici on va avoir une classe qui va renvoyer une exception avec le nombre d'erreurs que l'appel qu'une méthode peut générer.

class ErrorTrap {

  protected $errors = array();

  /**
  * Appel la fonction qui génère les logs. Le premier paramètre est la callback
  **/
  function call() {
    $args=  func_get_args();

    // On récupère la fonction à appeler.
    $callback = reset($args);

    // On récupère les paramètre  de la fonction
    $params = array_shift($args);

    $result = NULL;
    set_error_handler(array($this, 'onError'));
    try {
      $result = call_user_func_array($callback, $params);
    }
    catch (\Exception $ex) {
      // On déclenche une exception si nécessaire.
      throw $ex;
    }
    restore_error_handler();

    // On lance une exception si des erreurs ont été générées.
    if( $this->hasError() ){
      throw new \Exception('La fonction a généré ' . count($this->errors) . ' erreurs');
    } 
    return $result;
  }

  /**
  * Stocke les erreurs générées.
  **/
  function onError($errno, $errstr, $errfile, $errline) {
    $this->errors[] = array($errno, $errstr, $errfile, $errline);
  }

  /**
  * Retourne vrai si des erreurs sont présentes.
  *
  * @params bool
  **/
  function hasError() {
    return count($this->errors) > 0;
  }
}

Et on maintenant on appelle la méthode qui génère les logs de la manière suivante : 

class GoogleLoader{

  /**
  * Retourne le code source de la page https://www.google.com
  *
  * @return string
  **/
  public function getGoogleSource() {
    $client = \Drupal::httpClient();
    $url = 'https://www.google.com';
    $request = $client->request('GET', $url, array(
      'exceptions' => FALSE
    ));
    $body = $request->getBody();
  
    $dom = new \DOMDocument();

    try{
      $errorTrap =  new ErrorTrap();
      $errorTrap->call([$dom, 'loadHTML'], $body);
    }
    catch(\Exception $e){
       echo $e->getMessage();
    }
  }
 
}

Voilà, on a wrappé en 2 lignes notre méthode qui posait problème.
A noter qu'il y a un inconvénient de passer par cet ErrorTrap. En effet on ne peut pas passer de paramètre par référence et dans certains cas c'est un vrai manque.
 

Ajouter un commentaire

HTML restreint

  • Balises HTML autorisées : <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • Les lignes et les paragraphes vont à la ligne automatiquement.
  • Les adresses de pages web et les adresses courriel se transforment en liens automatiquement.
Votre email ne sera pas publié mais permettra à l'administrateur de vous recontacter en cas de problème