La visibilité d’un objet ne se limite pas à son instance

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.

Encore un autre Framework PHP

J’analyse l’API de Ruby on Rails en ce moment. Je cherche à comprendre comment il fonctionne, et pourquoi tel ou tel choix a été fait par les développeurs. J’essaye aussi de déterminer où il est particulièrement lié à Ruby, et ce qu’il n’est pas possible de transposer directement à un autre langage.

Je commence à mieux saisir Rails et son API. Le concept de module m’a dérouté un moment, surtout dans la lecture de l’API Rails. Venant d’un monde où cette notion n’existe pas et où les classes ne se mélangent que par héritage, pouvoir inclure des fonctions dans plein de classes, sans notion d’héritage ou de devoir instancier des objets dans l’objet… c’est déroutant. Pourtant c’est génial : cela distingue encore mieux les logiques et permet de partager beaucoup plus facilement du code.

Pendant le même temps je travaille sur un framework taillé pour PHP 5.2 (en attendant la version 5.3).

Mais pourquoi réinventer la roue ? Franchement, je me suis posé la question depuis Noël dernier ; et puis j’ai lu un article sur le blog de Jeff Atwood : Don’t Reinvent The Wheel, Unless You Plan on Learning More About Wheels. Cela m’a rappelé les raisons premières à l’écriture de mon propre framework : la facilité d’utilisation de CakePHP, mon envie de plonger un peu dans le code, pour comprendre certains aspects… et ma réticence face à sa complexité interne : je ne pigeais pas pourquoi tout y était si complexe et pourquoi continuer à supporter PHP 4.

Sur un coup de tête je me suis lancé dans un brouillon. En une nuit j’avais un truc qui tournait correctement. J’ai beaucoup apprécié cette expérience : j’avais quelque chose de plus simple, léger et rapide (à mes yeux) et j’y ai beaucoup appris ; alors j’ai continué. Voilà pourquoi je réinvente encore la roue : pour apprendre, pour fournir encore un autre framework à la communauté, et pour moi pouvoir utiliser un framework que je maîtrise sur mes projets.

Aujourd’hui je m’y relance : un framework full objet en PHP. La version de développement (alpha) est d’ailleurs disponible. Le code y est relativement stable, le développement étant test-driven, et l’API reprend grosso-modo l’API publique de Rails, minus les appels statiques (eg: Product::find()), qui devront attendre PHP 5.3 et son Late Static Binding pour être faisable.

Si cela vous intéresse, il s’appelle misago, et il est disponible sur github : http://github.com/ysbaddaden/misago/.

Propulsé par WordPress.com.

Retour en haut ↑