56 votes

Comment puis-je contourner l'absence de bloc finally en PHP ?

PHP n'a pas de bloc final - c'est-à-dire que, alors que dans la plupart des langages sensibles, vous pouvez faire :

try {
   //do something
} catch(Exception ex) {
   //handle an error
} finally {
   //clean up after yourself
}

PHP n'a pas la notion de bloc final.

Quelqu'un a-t-il l'expérience de solutions à ce trou plutôt irritant dans la langue ?

61voto

Mihai Limbășan Points 17205

Solution, non. Solution de rechange irritante et encombrante, oui :

$stored_exc = null;
try {
    // Do stuff
} catch (Exception $exc) {
    $stored_exc = $exc;
    // Handle an error
}
// "Finally" here, clean up after yourself
if ($stored_exc) {
    throw($stored_exc);
}

C'est dégoûtant, mais ça devrait marcher.

Veuillez noter : PHP 5.5 a enfin (ahem, désolé) ajouté un bloc finally : https://wiki.php.net/rfc/finally (et cela n'a pris que quelques années... disponible dans la RC 5.5 presque quatre ans à la date où j'ai posté cette réponse...)

11voto

Hendy Irawan Points 4635

Pour ceux qui s'y intéressent, c'est le Rapport de bogue n° 32100 en PHP concernant 'finally'. .

9voto

outis Points 39377

Le site RAII offre un substitut au niveau du code pour une fonction finally bloc. Créez une classe qui contient un ou plusieurs appelables. Dans le destructeur, appelez le ou les appelables.

class Finally {
    # could instead hold a single block
    public $blocks = array();

    function __construct($block) {
        if (is_callable($block)) {
            $this->blocks = func_get_args();
        } elseif (is_array($block)) {
            $this->blocks = $block;
        } else {
            # TODO: handle type error
        }
    }

    function __destruct() {
        foreach ($this->blocks as $block) {
            if (is_callable($block)) {
                call_user_func($block);
            } else {
                # TODO: handle type error.
            }
        }
    }
}

Coordination

Notez que PHP n'a pas de portée de bloc pour les variables, donc Finally ne se déclenchera pas avant la sortie de la fonction ou (dans la portée globale) la séquence d'arrêt. Par exemple, ce qui suit :

try {
    echo "Creating global Finally.\n";
    $finally = new Finally(function () {
        echo "Global Finally finally run.\n";
    });
    throw new Exception;
} catch (Exception $exc) {}

class Foo {
    function useTry() {
        try {
            $finally = new Finally(function () {
                echo "Finally for method run.\n"; 
            });
            throw new Exception;
        } catch (Exception $exc) {}
        echo __METHOD__, " done.\n";
    }
}

$foo = new Foo;
$foo->useTry();

echo "A whole bunch more work done by the script.\n";

aura pour résultat la sortie :

Creating global Finally.
Foo::useTry done.
Finally for method run.
A whole bunch more work done by the script.
Global Finally finally run.

$this

Les fermetures de PHP 5.3 ne peuvent pas accéder aux données. $this (corrigé en 5.4), vous aurez donc besoin d'une variable supplémentaire pour accéder aux membres de l'instance dans certains finally-blocks.

class Foo {
    function useThis() {
        $self = $this;
        $finally = new Finally(
            # if $self is used by reference, it can be set after creating the closure
            function () use ($self) {
               $self->frob();
            },
            # $this not used in a closure, so no need for $self
            array($this, 'wibble')
        );
        /*...*/
    }

    function frob() {/*...*/}
    function wibble() {/*...*/}
}

Champs privés et protégés

Le plus gros problème avec cette approche en PHP 5.3 est que la fermeture finale ne peut pas accéder aux champs privés et protégés d'un objet. Comme l'accès à $this Si le problème n'est pas résolu, il le sera dans PHP 5.4. Pour l'instant, propriétés privées et protégées peuvent être accessibles en utilisant des références, comme Artefacto le montre dans son réponse à une question sur ce même sujet ailleurs sur ce site.

class Foo {
    private $_property='valid';

    public function method() {
        $this->_property = 'invalid';
        $_property =& $this->_property;
        $finally = new Finally(function () use (&$_property) {
                $_property = 'valid';
        });
        /* ... */
    }
    public function reportState() {
        return $this->_property;
    }
}
$f = new Foo;
$f->method();
echo $f->reportState(), "\n";

Méthodes privées et protégées peuvent être accessibles en utilisant la réflexion. Vous pouvez en fait utiliser la même technique pour accéder aux propriétés non publiques, mais les références sont plus simples et plus légères. Dans un commentaire sur la page de manuel PHP pour fonctions anonymes Martin Partel donne un exemple d'une FullAccessWrapper qui ouvre les champs non publics à l'accès public. Je ne la reproduirai pas ici (voir les deux liens précédents pour cela), mais voici comment l'utiliser :

class Foo {
    private $_property='valid';

    public function method() {
        $this->_property = 'invalid';
        $self = new FullAccessWrapper($this);
        $finally = new Finally(function () use (&$self) {
                $self->_fixState();
        });
        /* ... */
    }
    public function reportState() {
        return $this->_property;
    }
    protected function _fixState() {
        $this->_property = 'valid';
    }
}
$f = new Foo;
$f->method();
echo $f->reportState(), "\n";

try/finally

try Les blocs nécessitent au moins un catch . Si vous voulez seulement try/finally , ajoutez un catch qui attrape un non- Exception (Le code PHP ne peut pas lancer quelque chose qui n'est pas dérivé de Exception ) ou de relancer l'exception détectée. Dans le premier cas, je suggère d'attraper StdClass comme une expression idiomatique signifiant "n'attrape rien". Dans les méthodes, attraper la classe actuelle pourrait également être utilisé pour signifier "ne rien attraper", mais en utilisant la méthode StdClass est plus simple et plus facile à trouver lors de la recherche de fichiers.

try {
   $finally = new Finally(/*...*/);
   /* ... */
} catch (StdClass $exc) {}

try {
   $finally = new Finally(/*...*/);
   /* ... */
} catch (RuntimeError $exc) {
    throw $exc
}

2voto

bobef Points 347

Voici ma solution au manque de bloc final. Elle permet non seulement de contourner le bloc finally, mais aussi d'étendre le try/catch pour attraper les erreurs PHP (et les erreurs fatales aussi). Ma solution ressemble à ceci (PHP 5.3) :

_try(
    //some piece of code that will be our try block
    function() {
        //this code is expected to throw exception or produce php error
    },

    //some (optional) piece of code that will be our catch block
    function($exception) {
        //the exception will be caught here
        //php errors too will come here as ErrorException
    },

    //some (optional) piece of code that will be our finally block
    function() {
        //this code will execute after the catch block and even after fatal errors
    }
);

Vous pouvez télécharger la solution avec la documentation et les exemples à partir du hub git -. https://github.com/Perennials/travelsdk-core-php/tree/master/src/sys

1voto

Csaba Kétszeri Points 618

Comme il s'agit d'une construction du langage, vous ne trouverez pas de solution facile pour cela. Vous pouvez écrire une fonction et l'appeler à la dernière ligne de votre bloc d'essai et à la dernière ligne avant de relancer l'excepion dans le bloc d'essai.

De bons livres plaident contre l'utilisation de blocs finaux pour toute autre chose que la libération de ressources, car vous ne pouvez pas être sûr qu'ils seront exécutés si quelque chose de désagréable se produit. Appeler cela un trou irritant est une exagération. Croyez-moi, beaucoup de code exceptionnellement bon est écrit dans des langages sans bloc finally :)

Le but de finally est de s'exécuter, peu importe si le bloc try a réussi ou non.

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