mageekblog - Mot-clé - includeLe blog personnel de Frédéric Hardy. Au menu, PHP, agilité, FreeBSD, cuisine et photographies.2021-12-02T08:20:54+01:00Frédéric Hardyurn:md5:26874ca5b8cd4cac8d08b0e68e64f63aDotclearinclude et exceptionurn:md5:4caad2efaf4648cfef8c11aabb204a982011-10-31T12:00:00+01:002011-10-31T13:34:40+01:00mageekguyPHPexceptiongestionnaire d erreursincludePHP<p>Parfois, il peut être utile d'obtenir une exception au lieu d'une erreur de type <code>WARNING</code> lorsque l'on tente en <a href="http://www.php.net">PHP</a> d'utiliser un fichier inexistant à l'aide des instructions <code><a href="http://fr2.php.net/manual/en/function.include.php">include</a></code> ou <code><a href="http://fr2.php.net/manual/en/function.include-once.php">include_once</a></code>.</p>
<p>Pour y parvenir, le développeur a souvent recours à l'une des deux solutions suivantes :</p>
<ol><li>Soit il teste l'existence du fichier avant de l'inclure et lance une exception si le fichier demandé n'existe pas ;</li>
<li>Soit il défini un gestionnaire d'erreurs qui transforme toutes les erreurs en exception ;</li>
</ol>
<p>J'avoue ne pas être fan de la première, qui a l'inconvénient de ne pas gérer le cas ou le fichier est supprimé entre le moment ou son existence est vérifié et le moment ou il est effectivement inclus.</p>
<p>Certes, dans la grande majorité des cas, cette <a href="http://fr.wikipedia.org/wiki/Situation_de_compétition">situation de compétition</a> n'arrivera jamais, mais la probabilité existe pourtant bel et bien et devrait donc être gérée correctement.</p>
<p>Je ne suis pas non plus un adepte de la seconde car je ne désire pas forcément que toutes les erreurs générées par mon code soit transformées en exception.</p>
<p>Et en l'occurrence, je ne veux générer une exception uniquement que lorsque le fichier devant être inclus est introuvable.</p>
<p>J'ai donc cherché une alternative à ces deux solutions prenant en compte à la fois la situation de compétition et le fait que je ne veux une exception que lorsque le fichier demandé n'existe pas. </p> <p>Ma première idée a été d'utiliser l'opérateur de contrôle d'erreur <a href="http://fr2.php.net/manual/en/language.operators.errorcontrol.php"><code>@</code></a> sur l'instruction concernée afin que l'erreur de type <code>WARNING</code> générée par cette dernière ne soit pas gérée par le gestionnaire d'erreur installé, puis ensuite de vérifier que le fichier demandé existait bien parmi ceux renvoyés par la fonction <code><a href="http://fr2.php.net/manual/en/function.get-included-files.php">get_included_files()</a></code>.</p>
<blockquote><pre><code><?php
...
public function includeFile($path)
{
@include_once $path;
if (in_array($path, get_included_files()) === false)
{
throw new exceptions\runtime\file('Unable to include file \'' . $path . '\'');
}
}
...
?>
</code></pre></blockquote>
<p>Cela semblait relativement bien marché, au détail près que si le fichier inclus contenait des erreurs, par exemple à cause d'un appel à require sur un fichier inexistant, l'exécution du script s'arrêtait sans que l'utilisateur n'ait le moindre message d'erreur lui en expliquant la raison.</p>
<p>En effet, l'opérateur <a href="http://fr2.php.net/manual/en/language.operators.errorcontrol.php"><code>@</code></a> s'applique non seulement à l'instruction sur laquelle il est appliqué, mais aussi à l'intégralité du code appelé par cette instruction.</p>
<p>En clair, ma solution était de la merde, et j'ai donc été obligé d'en chercher une autre.</p>
<p>Dans le cas d'un appel à <code><a href="http://fr2.php.net/manual/en/function.include.php">include</a></code> ou <code><a href="http://fr2.php.net/manual/en/function.include-once.php">include_once</a></code>, le gestionnaire d'erreur peut être appelé pour deux raisons :</p>
<ol><li>Soit le code inclus génère une erreur ;</li>
<li>Soit le fichier demandé n'existe pas ;</li>
</ol>
<p>J'ai donc eu l'idée de définir un gestionnaire d'erreur juste avant mon appel à <code><a href="http://fr2.php.net/manual/en/function.include-once.php">include_once</a></code> qui lance une exception uniquement si le fichier n'a pas été inclus et qui dans le cas contraire, demande au gestionnaire d'erreur par défaut d'intervenir.</p>
<p>Et une fois l'appel à <code><a href="http://fr2.php.net/manual/en/function.include-once.php">include_once</a></code> effectué, je restaure le gestionnaire d'erreur précédent à l'aide de la fonction <code><a href="http://fr2.php.net/manual/en/function.restore-error-handler.php">restore_error_handler()</a></code>.</p>
<blockquote><pre><code><?php
...
public function includeFile($path)
{
set_error_handler(function($error, $message, $file, $line) use ($path) {
$pathLength = strlen($path);
foreach (get_included_files() as $includedFile)
{
if (strrpos($includedFile, $path) + $pathLength === strlen($includedFile))
{
return false; // appel au gestionnaire d'erreur par défaut
}
}
throw new exceptions\runtime\file('Unable to include \'' . $path . '\'');
}
);
include_once $path;
restore_error_handler();
}
...
?></code></pre></blockquote>
<p>Toute l'astuce consiste ici à utiliser une fermeture lexicale, définie par <code>use ($path)</code> pour injecter dans le gestionnaire d'erreur le chemin du fichier devant être inclus et pouvoir ainsi tester qu'il l'a bien été.</p>
<p>Au passage, j'en ai profité pour modifier le code afin qu'il prenne en compte le fait que le fichier peut avoir été inclus à partir de l'un des répertoires définis par la directive de configuraiton <code><a href="http://fr2.php.net/manual/en/ini.core.php#ini.include-path">include_path</a></code> lors de la vérification du fait qu'il a bien été inclus.</p>http://blog.mageekbox.net/?post/2011/10/31/include-et-exception#comment-formhttp://blog.mageekbox.net/?feed/atom/comments/301