web-dev-qa-db-fra.com

Comment puis-je pallier le manque d'un blocage final en PHP?

Avant la version 5.5, PHP n’avait pas de bloc final - c’est-à-dire, 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 aucune notion d'un blocage final. 

Quelqu'un a-t-il déjà expérimenté des solutions à ce trou de langage plutôt irritant?

55
Kazar

Solution, non. Solution de contournement lourde et irritante, 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);
}

Yucky, mais devrait fonctionner.

Veuillez noter que: PHP 5.5 a finalement ajouté (enfin, désolé) un bloc final: https://wiki.php.net/rfc/finally (et cela n'a pris que quelques années. .. disponible dans la version 5.5 RC presque quatre ans après la date d'affichage de cette réponse ...)

59
Mihai Limbășan

L'idiome RAII offre une substitution au niveau du code pour un bloc finally. Créez une classe qui contient callable (s). Dans le destructeur, appelez le ou les appelants.

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. Par conséquent, Finally n'intervenera pas tant que la fonction n'aura pas quitté ou (dans une portée globale) la séquence d'arrêt. Par exemple, les éléments suivants:

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";

entraînera la sortie:

 Créer global Enfin .
 Foo :: useTry done .
 Enfin pour la méthode exécutée .
 Beaucoup plus de travail effectué par le script .
 Global Enfin, enfin exécuter .

$ this

Les fermetures PHP 5.3 ne peuvent pas accéder à $this (corrigé dans la version 5.4), vous aurez donc besoin d'une variable supplémentaire pour accéder aux membres d'instance dans des blocs finally.

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

On peut soutenir que le plus gros problème de cette approche dans PHP 5.3 est que la fermeture-dernière ne peut pas accéder aux champs privés et protégés d'un objet. Comme lors de l'accès à $this, ce problème est résolu dans PHP 5.4. Pour l'instant, les propriétés private et protected sont accessibles à l'aide de références, comme Artefacto le montre dans son answer à une question sur ce sujet même 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";

Les méthodes privées et protégées sont accessibles via réflexion. Vous pouvez réellement utiliser la même technique pour accéder à des 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 les fonctions anonymous , Martin Partel donne un exemple de classe FullAccessWrapper qui ouvre des champs non publics à un accès public. Je ne le reproduirai pas ici (voir les deux liens précédents pour cela), mais voici comment vous l'utiliseriez:

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

Les blocs try nécessitent au moins un catch. Si vous ne voulez que try/finally, ajoutez un bloc catch qui intercepte une variable non -Exception (le code PHP ne peut pas renvoyer quoi que ce soit non dérivé de Exception) ou relancez l'exception interceptée. Dans le premier cas, je suggère d'attraper StdClass comme un idiome signifiant "ne rien attraper". Dans les méthodes, attraper la classe actuelle pourrait aussi signifier "ne rien attraper", mais utiliser 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
}
9
outis

Voici ma solution au manque de blocage final. Il fournit non seulement une solution de contournement pour le bloc finally, mais étend également les tentatives try/catch pour intercepter les erreurs PHP (ainsi que les erreurs fatales également). 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 des exemples à partir de git hub - https://github.com/Perennials/travelsdk-core-php/tree/master/src/sys

2
bobef
function _try(callable $try, callable $catch, callable $finally = null)
{
    if (is_null($finally))
    {
        $finally = $catch;
        $catch = null;
    }

    try
    {
        $return = $try();
    }
    catch (Exception $rethrow)
    {
        if (isset($catch))
        {
            try
            {
                $catch($rethrow);
                $rethrow = null;
            }
            catch (Exception $rethrow) { }
        }
    }

    $finally();

    if (isset($rethrow))
    {
        throw $rethrow;
    }
    return $return;
}

Appel à l'aide de fermetures. Le deuxième paramètre, $catch, est facultatif. Exemples:

_try(function ()
{
    // try
}, function ($ex)
{
    // catch ($ex)
}, function ()
{
    // finally
});

_try(function ()
{
    // try
}, function ()
{
    // finally
});

Gère correctement les exceptions partout:

  • $try: l'exception sera transmise à $catch. $catch sera exécuté en premier, puis $finally. S'il n'y a pas de $catch, l'exception sera réexécutée après l'exécution de $finally.
  • $catch: $finally sera exécuté immédiatement. L'exception sera réexécutée une fois que $finally est terminé.
  • $finally: Exception va décomposer la pile d'appels sans entrave. Toute autre exception dont le renvoi est prévu sera supprimée.
  • None : La valeur renvoyée par $try sera renvoyée.
1
Zenexer

Comme il s’agit d’une construction de langage, vous ne trouverez pas de solution facile à cela . Vous pouvez écrire une fonction et l’appeler en tant que dernière ligne de votre bloc try et dernière ligne avant de relancer l’exception dans le bloc try.

Les bons livres vont à l’encontre de l’utilisation de blocs ultérieurs pour autre chose que la libération des ressources, car vous ne pouvez pas être sûr qu’ils s’exécuteront si quelque chose de méchant se produit. Le qualifier de trou irritant est une surestimation. Croyez-moi, beaucoup de code exceptionnellement bon est écrit dans des langues sans bloc final. :)

Le point final est d’exécuter, peu importe si le bloc try a réussi ou non.

1
Csaba Kétszeri

Si quelqu'un continue de suivre cette question, vous voudrez peut-être consulter le (nouveau) RFC pour une fonctionnalité de langage enfin dans le wiki PHP. L'auteur semble déjà avoir des correctifs de travail, et je suis sûr que la proposition bénéficierait des commentaires des autres développeurs.

0
Tobias Gies

Je viens juste de finir d’écrire un cours Try Catch Finally plus élégant qui pourrait vous être utile. Il y a quelques inconvénients, mais ils peuvent être contournés.

https://Gist.github.com/Zeronights/5518445

0
Ozzy