92 votes

php : attraper une exception et continuer l'exécution, est-ce possible ?

Est-il possible d'attraper une exception et de continuer l'exécution de script ?

7voto

Crast Points 6207

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();

1voto

davestewart Points 111

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.

0voto

Roy Points 17

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].

0voto

Garet Claborn Points 396

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.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X