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
}