Retour sur le Forum PHP 2024
Découvrez un résumé concis des conférences qui nous ont le plus marqué lors du Forum PHP 2024 !
PHP 7 apporte un changement à la façon dont les erreurs sont rapportées.
Désormais la plupart des erreurs sont rapportées en lançant des exceptions Error
.
Les Throwable
vont remonter la pile d'exécution (bubble up) jusqu'à rencontrer un des cas suivants :
catch
qui supporte ce type d'erreur ;set_exception_handler()
;Nous allons donc, plus en détails, définir et voir comment utiliser Throwable
, Error
et Exception
.
Throwable
est une interface PHP 7 qui représente une erreur dans le script.
interface Throwable
{
public function getMessage(): string; // La raison de l'erreur
public function getCode(): int; // Le code de l'erreur
public function getFile(): string; // Le fichier dans lequel l'erreur à débuter
public function getLine(): int; // La ligne à laquel l'erreur à débuter
public function getTrace(): array; // Retourne la stack trace en array comme debug_backtrace()
public function getTraceAsString(): string; // Retourne la stack trace en chaine de caractère
public function getPrevious(): Throwable; // Retourne le `Throwable` précédent
public function __toString(): string; // Convertit en chaine de caractère
}
Errors
et Exceptions
sont les deux types de bases qui l'implémentent.
Voici la hiérarchie des Throwable
:
interface Throwable
|- Error implements Throwable
|- ArithmeticError extends Error
|- DivisionByZeroError extends ArithmeticError
|- AssertionError extends Error
|- ParseError extends Error
|- TypeError extends Error
|- ArgumentCountError extends TypeError
|- Exception implements Throwable
|- ClosedGeneratorException extends Exception
|- DOMException extends Exception
|- ErrorException extends Exception
|- IntlException extends Exception
|- LogicException extends Exception
|- BadFunctionCallException extends LogicException
|- BadMethodCallException extends BadFunctionCallException
|- DomainException extends LogicException
|- InvalidArgumentException extends LogicException
|- LengthException extends LogicException
|- OutOfRangeException extends LogicException
|- PharException extends Exception
|- ReflectionException extends Exception
|- RuntimeException extends Exception
|- OutOfBoundsException extends RuntimeException
|- OverflowException extends RuntimeException
|- PDOException extends RuntimeException
|- RangeException extends RuntimeException
|- UnderflowException extends RuntimeException
|- UnexpectedValueException extends RuntimeException
⚠ Attention ! Vous ne pouvez implémenter
Throwable
qu'à traversError
etException
. Sinon vous obtiendrez une erreur FATALPHP Fatal error: Class MyClass cannot implement interface Throwable, extend Exception or Error instead
Il est quand même possible d'étendre cette interface dans le domaine utilisateur.
interface MyThrowable extends Throwable {
public function myCustomMethod();
}
class MyException extends Exception implements MyThrowable {
public function myCustomMethod()
{
// implement custom method code
}
}
Error
est la classe de base de toutes les erreurs internes de PHP.
Les plus communes sont :
ParseError
, qui est lancée quand on require
ou eval
un code qui contient une erreur de syntax.TypeError
, qui est lancée quand le typehint d'un argument/retour d'une fonction n'est pas respecté. Et également en strict mode
quand on passe un nombre invalid d'arguments à une fonction native de PHP.Vous pourriez être amenés à throw des Error
dans votre code si par exemple vous parsez un fichier et qu'il contient une erreur de syntaxe.
Ou si vous avez une fonction avec un nombre de paramètres variable et que le nombre/type d'arguments n'est pas correct.
Exception
est la classe de base de toutes les exceptions utilisateurs.
Il est très fréquent de lancer ou créer des Exception
.
C'est d'ailleurs sur le fonctionnement et l'utilisation des Exception
que nous allons nous concentrer.
Pour lancer une exception, il suffit d'utiliser le mot clé throw
.
throw new Exception('Mon message d\'erreur.');
echo "Affichage d'un contenu texte.";
Il faut savoir qu'une
Exception
interrompt l'exécution des instructions suivantes. Dans l'exemple leecho
ne sera pas exécuté.
Pour attraper et gérer l'exception, il faut utiliser la structure try
catch
.
try {
if (!$_GET['titre']) {
throw new Exception('Impossible d\'afficher le titre. Le titre est requis.');
}
echo $_GET['titre'];
} catch (Exception $e) {
echo '⚠ Une exception est survenue : ' . $e->getMessage();
}
Dans cet exemple le script affichera le titre s'il est fourni sinon il affichera le message d'erreur comme quoi il est obligatoire.
Vous pouvez également effectuer de multiple catch
afin de séparer les différents types d'Exception
.
Il faut placer les catch
dans l'ordre du plus précis au moins précis.
try {
if (!$_GET['titre']) {
throw new Exception('Impossible d\'afficher le titre. Le titre est requis.');
}
if (!is_string($_GET['titre'])) {
throw new RuntimeException('Le titre doit être une chaîne de caractères.');
}
echo $_GET['titre'];
} catch (RuntimeException $e) {
echo $e->getMessage();
} catch (Exception $e) {
echo '⚠ Une exception est survenue : ' . $e->getMessage();
}
Ici
RuntimeException
étendsException
, il faudra donc catchRuntimeException
avant lesExceptions
.
Depuis PHP 7.1 il est également possible de spécifier plusieurs types d'Exception
dans le catch en utilisant le caractère |
try {
// Code
} catch (OutOfBoundsException | LogicException $e) {
echo '⚠ Une exception est survenue : ' . $e->getMessage();
}
⚠ Il est très important de bien choisir l'Exception
que l'on veut lancer ou attraper, sinon la gestion d'erreur ne sera pas consistante.
Également à savoir
La LogicException
référence une erreur de code qui devrait, la plupart du temps, mener à un correctif sur le code.
Attraper une LogicException
a généralement pour but d'afficher une page d'erreur et de logger en vue d'informer le développeur.
La RuntimeException
représente une erreur survenue durant l'exécution (donnée invalide, erreur d'une source de données).
Attraper une RuntimeException
est très utile pour exécuter un code alternatif qui permettra au script de finir son exécution.
ℹ️ Il est très fortement recommandé d'avoir un exception handler afin d'afficher une page d'erreur au visiteur. Mais aussi pour éviter d'afficher des informations sensibles (url du fichier, stack trace, message d'erreur ...) La bonne pratique étant de ne pas laisser une exception casser le site.
set_exception_handler(function($exception){
echo 'Une erreur est survenue. Veuillez rééssayer ulterieurement.';
// log($exception->getMessage());
// email au developpeur
});
Le code d'erreur est un integer
qui peut être utilisé pour codifier/identifier l'erreur.
Il permet par exemple d'afficher le code de l'erreur plutôt que le message de l'
Exception
au visiteur. Il permet de masquer la raison de l'erreur, qui peut dans certains cas contenir des informations sensibles.
Il est très utile de créer des exceptions personnalisées afin qu'elles puissent identifier un problème plus précisément, mais aussi afin de pouvoir transporter des données supplémentaires (texte, object, array...).
class MyObject
{
public $content;
}
class MyObjectException extends RuntimeException
{
/**
* @var MyObject
*/
private $myObject;
public function __construct(MyObject $myObject, $message = "", $code = 0, Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
$this->myObject = $myObject;
}
/**
* @return MyObject
*/
public function getMyObject()
{
return $this->myObject;
}
}
Quand
MyObjectException
est attrapée, on peut récupérer l'objetMyObject
via la méthodegetMyObject()
ce qui permet de gérer encore plus précisément les erreurs.
Parfois il est utile de tracer/loguer ce qui s'est mal déroulé.
Dans ce cas on va donc attraper l'Exception
, logger un message d'erreur avant de relancer l'Exception
.
try {
// mise à jour d'un contenu
} catch (Exception $e) {
// log('La mise à jour a échoué.');
throw $e;
}
Exemple concret :
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\NullLogger;
class UserFactory implements LoggerAwareInterface
{
use LoggerAwareTrait;
private $passwordGenerator;
public function construct(PasswordGeneratorInterface $passwordGenerator)
{
$this->passwordGenerator = $passwordGenerator;
$this->logger = new NullLogger();
}
public function create()
{
try {
$user = new User();
$password = $this->passwordGenerator->generatePassword();
$user->setPassword($password);
return $user;
} catch (Exception $exception) {
$this->logger->error('Une erreur est survenue pendant la creation d\'un utilisateur. Raison: ' . $exception->getMessage());
throw $exception;
}
}
}
interface PasswordGeneratorInterface
{
public function generatePassword();
}
Ici, on peut remarquer que l'on va seulement logger un message d'erreur et laisser remonter (bubble up) l'exception.
Il existe également l'encapsulation d'une Exception
dans une autre Exception
afin de créer un stack trace complète.
try {
// mise à jour d'un contenu
} catch (RuntimeException $exception) {
throw new UpdateContentException('Erreur de mise a jour du contenu.', 0, $exception);
}
class UpdateContentException extends RuntimeException {}
Peu importe le type d'exception qui serait lancée pendant la mise à jour du contenu, le code renverra toujours une
UpdateContentException
Si on attrape l'UpdateContentException
on peut récupérer l'Exception
précédente grâce à la méthodegetPrevious()
Exemple concret
class UserFactory
{
private $passwordGenerator;
public function construct(PasswordGeneratorInterface $passwordGenerator)
{
$this->passwordGenerator = $passwordGenerator;
}
public function create()
{
try {
$user = new User();
$password = $this->passwordGenerator->generatePassword();
$user->setPassword($password);
return $user;
} catch (RuntimeException $exception) {
throw new UserFactoryException('Une erreur est survenue pendant la creation d\'un utilisateur.', 0, $exception);
}
}
}
class UserFactoryException extends RuntimeException {}
interface PasswordGeneratorInterface
{
public function generatePassword();
}
On peut voir ici que peu importe la
RuntimeException
qui se produit dans$this->passwordGenerator->generatePassword()
l'Exception
qui sera remontée est uneUserFactoryException
qui nous informe que la création a échouée. La séparation des couches logicielles est respectée.
Nous avons vu comment lancer et attraper une exception en PHP ainsi que des notions un peu plus avancées sur la création d'une exception personnalisée pouvant transporter des données supplémentaires utilisables en cas d'erreur. Sans oublier la gestion du logging/tracing grâce au rethrow et à l'encapsulation d'exception.
Les erreurs sont présentes dans notre code, dans le code des librairies externes, ou même en cas de défaillance matérielle, c'est pourquoi la maîtrise des Throwable est indispensable afin d'avoir une gestion d'erreurs de qualité.
Les points positifs :
Les points négatifs
catch
/set_exception_handler
afin qu'aucune information sensible ne soit affichée aux visiteurs.Auteur(s)
Anthony MOUTTE
_Développeur / Concepteur @ ElevenLabs_🚀 je suis très intéressé par la recherche et développement ainsi que les bonnes pratiques.
Vous souhaitez en savoir plus sur le sujet ?
Organisons un échange !
Notre équipe d'experts répond à toutes vos questions.
Nous contacterDécouvrez nos autres contenus dans le même thème
Découvrez un résumé concis des conférences qui nous ont le plus marqué lors du Forum PHP 2024 !
Le composant Symfony ExpressionLanguage : qu'est-ce que c'est ? Quand et comment l'utiliser ? Comment créer des expressions lors de cas plus complexes ?
Découvrez comment réaliser du typage générique en PHP : introduction et définition du concept, conseils et explications pas-à-pas de cas pratique.