Est-il possible d'attraper une exception et de continuer l'exécution de script ?
Réponses
Trop de publicités?Oui.
try {
Somecode();
catch (Exception $e) {
// handle or ignore exception here.
}
Notez toutefois que php a également des codes d'erreur distincts des exceptions, un héritage datant d'avant que php ne dispose de primitives oop. La plupart des bibliothèques intégrées lèvent encore des codes d'erreur, et non des exceptions. Pour ignorer un code d'erreur, appelez la fonction préfixée par @ :
@myfunction();
Un autre angle d'attaque consiste à renvoyer une exception, et NON à en lancer une, à partir du code de traitement.
J'avais besoin de faire cela avec un framework de templating que je suis en train d'écrire. Si l'utilisateur tente d'accéder à une propriété qui n'existe pas dans les données, je retour l'erreur au plus profond de la fonction de traitement, plutôt que de la rejeter.
Ensuite, dans le code d'appel, je peux décider de lancer l'erreur retournée, en faisant en sorte que la fonction try() soit catch(), ou de continuer :
// process the template
try
{
// this function will pass back a value, or a TemplateExecption if invalid
$result = $this->process($value);
// if the result is an error, choose what to do with it
if($result instanceof TemplateExecption)
{
if(DEBUGGING == TRUE)
{
throw($result); // throw the original error
}
else
{
$result = NULL; // ignore the error
}
}
}
// catch TemplateExceptions
catch(TemplateException $e)
{
// handle template exceptions
}
// catch normal PHP Exceptions
catch(Exception $e)
{
// handle normal exceptions
}
// if we get here, $result was valid, or ignored
return $result;
Le résultat est que j'obtiens toujours le contexte de l'erreur originale, même si elle a été lancée en haut de la page.
Une autre option pourrait être de renvoyer un NullObject personnalisé ou un objet UnknownProperty et de le comparer avant de décider de déclencher le catch(), mais comme vous pouvez de toute façon relancer les erreurs, et si vous contrôlez totalement la structure globale, je pense que c'est une bonne façon de contourner le problème de l'impossibilité de poursuivre les try/catches.
C'est une vieille question, mais j'en ai eu une dans le passé lorsque j'ai abandonné les scripts VBA au profit de php, où l'on pouvait utiliser "GoTo" pour réintroduire une boucle "On Error" avec un "Resume" et la boucle continuait à traiter la fonction.
En php, après quelques essais et erreurs, j'utilise maintenant des try{} catch{} imbriqués pour les processus critiques et non critiques, ou même pour les appels de classe interdépendants afin de pouvoir remonter jusqu'au début de l'erreur. Par exemple, si la fonction b dépend de la fonction a, mais que la fonction c est utile mais ne doit pas arrêter le processus, et que je veux quand même connaître les résultats des trois fonctions, voici ce que je fais :
//set up array to capture output of all 3 functions
$resultArr = array(array(), array(), array());
// Loop through the primary array and run the functions
foreach($x as $key => $val)
{
try
{
$resultArr[$key][0][] = a($key);
$resultArr[$key][1][] = b($val);
try
{ // If successful, output of c() is captured
$resultArr[$key][2][] = c($key, $val);
}
catch(Exception $ex)
{ // If an error, capture why c() failed
$resultArr[$key][2][] = $ex->getMessage();
}
}
catch(Exception $ex)
{ // If critical functions a() or b() fail, we catch the reason why
$criticalError = $ex->getMessage();
}
}
Je peux maintenant parcourir en boucle mon tableau de résultats pour chaque clé et évaluer les résultats. S'il y a un échec critique pour a() ou b().
J'ai toujours un point de référence sur le chemin parcouru avant qu'un échec critique ne se produise dans le $resultArr et si le gestionnaire d'exception est défini correctement, je sais si c'est a() ou b() qui a échoué.
Si c() échoue, la boucle continue. Si c() a échoué à différents moments, avec un peu de logique post-boucle supplémentaire, je peux même savoir si c() a fonctionné ou s'il y a eu une erreur à chaque itération en interrogeant $resultArr[$key][2].
Vous pouvez le faire, mais je vous préviens : beaucoup considèrent cette méthode comme diabolique.
// https://stackoverflow.com/a/66377817/578023
function is_same(&$a, &$b): bool {
$_ = [ &$a, &$b ];
return
\ReflectionReference::fromArrayElement($_, 0)->getId() ===
\ReflectionReference::fromArrayElement($_, 1)->getId();
}
function attempt_risky_action($collection){
$cursor=NULL;
$resuming = false;
resume:
try{
foreach($collection as $item){
if($resuming && !is_same($cursor,$item) ){
continue; // some things have better ways to skip ahead, especially an array index
}
else {
$resuming = false;
$cursor=&$item; // main concept is to remember where you are in the iteration
} // in some situation you may have to use references, &item
// your normal loop here
.
.
.
}
} catch( Exception $e){
$resuming = repair_something($e, $collection); // returns false if your repair ran out of ideas
if($resuming)
goto resume;
}
unset($cursor);
}
Idéalement, il serait préférable d'envelopper l'élément unset($cursor);
appel dans un finally{}
mais franchement, je ne sais pas comment cela fonctionne avec goto off hand.
S'il s'exécute parce que goto a interrompu le flux, vous aurez besoin d'une logique conditionnelle, de sorte que le curseur existe toujours. Si vous avez une instruction de retour à l'intérieur de la boucle, vous devez debe utiliser un bloc final pour l'appel à unset($cursor)
-- ou provoquer une fuite de mémoire.
Bien que cela soit moins excitant, vous pouvez faire la même chose en imbriquant votre boucle entière dans un fichier do{ try/catch } while($resuming)
. Bien qu'il ne s'agisse pas LITTERALEMENT d'inverser votre exécution, cela a exactement le même effet sans risquer un goto.
is_same()
de https://stackoverflow.com/a/66377817/578023
function attempt_risky_action($collection){
$cursor=NULL;
$resuming = false;
do{
try{
foreach($collection as $item){
if($resuming && !is_same($cursor,$item) ){
continue;
}
else {
$resuming = false;
$cursor=&$item;
}
// your loop here
}
} catch( Exception $e){
$resuming = repair_something($e, $collection); // returns false if your repair ran out of ideas
}
finally{
if(!$resuming){
unset($cursor);
}
}
} while($resuming);
}
Une dernière méthode, qui n'est pas illustrée, consiste à utiliser la fonction reset()
, prev()
, current()
, next()
, end()
facultés
Cela vous permettra de placer votre bloc try/catch à l'intérieur d'un bloc de code qui itère comme le ferait une boucle -- puis d'utiliser la fonction prev()
dans la capture pour réessayer.
- Réponses précédentes
- Plus de réponses