J’ai découvert aujourd’hui un truc génial en PHP : la visibilité restreinte (private et protected) d’un attribut ou d’une méthode dans un objet dépasse le cadre stricte de son instance, et est visible et accessible au niveau de la classe. Soit depuis toutes les autres instances !
Ce matin je suis tombé sur ce bout de code, extrait de misago. Comment pouvait-il fonctionner ? Mes tests me prouvaient qu’il n’y avait aucun bug et que tout fonctionnait correctement, contre toute attente.
class ActiveRecord
{
function create()
{
$class = get_class($this);
$record = new $class($attributes);
if ($record->_create()) {
return $record;
}
}
private function _create()
{
// ...
}
}
Si vous n’avez pas remarqué ce qui cloche : la méthode ActiveRecord::create()
crée un nouvel objet (instance de ActiveRecord) et appelle une méthode sur ce nouvel objet. Problème : ActiveRecord::_create()
est une méthode privée… comment puis-je l’appeler depuis l’extérieur ?!
Prenons les choses dans l’ordre. Dans le cas d’une définition publique, attributs et méthodes sont accessibles à partir de tout endroit. Dans le cas d’une définition restreinte, soit privée ou protégée, la visibilité des attributs et méthodes s’en retrouve elle aussi restreinte. C’est cette restriction qui m’intéresse dans le cas présent.
Une méthode privée n’est accessible qu’à partir de la classe qui l’a définie. Une méthode protégée n’est accessible qu’à partir de la classe qui l’a définie, mais est aussi accessible aux classes parentes et héritées. On s’attend ainsi à ne pouvoir appeler une méthode privée de la classe A que depuis des méthodes de A. Si B hérite de A, alors les méthodes de B n’auront pas accès aux méthodes privées de A. Cependant il aura accès aux méthodes publiques et protégées de A. L’inverse est tout aussi vrai : A a accès aux méthodes publiques et protégées de B, mais pas à ses méthodes privées. Par contre une classe C, qui n’hérite ni de A ni de B, n’aura accès qu’aux méthodes publiques de A et de B.
Prenons maintenant une instance de B ($b1). Depuis cette instance nous pensons avoir accès aux méthodes privées et protégées de B ainsi qu’aux méthodes protégées de A. C’est logique on est à l’intérieur de notre objet, c’est visible et accessible. Mais quid d’une autre instance de B ($b2) ? Étant une autre instance on pense ne pas pouvoir y accéder, car on se trouve en dehors de l’objet et que l’on n’a donc accès qu’à ses méthodes publiques.
Cela est à la fois vrai… et faux, cela dépend du contexte d’appel !
En effet quand on parle de méthodes privées et protégées on parle de visibilité dans la classe ! Oui, dans la classe et pas dans l’instance. La visibilité d’une méthode privée ne se limite à une instance de la classe, mais s’applique à la classe, et donc à toutes ses instances.
Appeller directement une méthode protégée de $b1 (définie dans A ou dans B) déclenchera une erreur fatale, mais si on l’appele depuis $b2… ça marche et c’est le comportement attendu. Idem pour une méthode privée, tant qu’on reste dans une logique de classe. Ainsi $b2 peut appeler une méthode privée de $b1 définie dans B, mais pas une méthode privée définie dans A.
Qu’est-ce que cela change ? Peu de choses ou beaucoup de choses. Pour moi cela veut dire un code encore plus propre, quoiqu’un peu étrange, le temps que je m’y habitue. En attendant le code présenté en exemple fonctionne, et je trouve cela génial. C’est propre, c’est beau, c’est fonctionnel, ça a tout pour plaire.