Analyser un objet en PHP

Ruby est un langage qui s’auto documente. On peut analyser en permanence n’importe quel objet. Le motto étant de ne pas forcer un type, mais de vérifier si un objet répond à ce qu’on demande.

En Java ou PHP on force généralement le type lors de l’appel d’une fonction. Par exemple :

function do_something(Integer $num) {
  $num->do_another_thing();
}

Alors qu’en ruby on va plutôt faire ceci :

def do_something(num)
  if num.respond_to? :do_another_thing  
    num.do_another_thing
  else
    raise Exception.new "num can't do another thing!"
  end
end

Qu’importe la classe de num, tant qu’elle répond à la méthode do_another_thing.

Est-ce possible en PHP5 ? La réponse est oui, on peut analyser un objet. Pour reprendre l’exemple ci-dessus :

function do_something($num)
  if (is_callable(array($num, 'do_another_thing')) {  
    $num->do_another_thing();
  }  
  else {
    throw new Exception("num can't do another thing!")
  }
}

Ce n’est pas aussi simple, notamment à cause de la syntaxe. et on ne peut pas analyser la visibilité des méthodes par exemple. Ruby propose plusieurs méthodes, dont methods, public_methods et private_methods. Qu’on soit dans le cotexte de l’objet ou en dehors, ces méthodes retourneront toujours la même liste de méthodes.

PHP5 propose quelques fonctions : is_callable et get_class_methods. Cependant elles ne retourneront pas les mêmes informations suivant qu’on les appelle dans le contexte de l’objet ou en dehors. Prenons par exemple la classe suivante :

class A
{
  public function __construct()
  {
    print_r(get_class_methods($this));
    var_dump(method_exists($this, 'a_private'));
    var_dump(is_callable(array($this, 'a_private')));
  }
  private function a_private() { }
  protected function a_protected() { }
  public function a_public() { }
}

Si on crée un nouvel objet A, nous allons avoir le retour suivant : toutes les méthodes sont listées, existantes et appelables. Après tout, cela est logique : dans le contexte d’appel les fonctions sont appelables.

Array
(
    [0] => __construct
    [1] => a_private
    [2] => a_protected
    [3] => a_public
)
bool(true)
bool(true)

Que se passe-t-il en dehors du contexte ? Reprenons nos trois appels, sur l’objet $a cette fois :

print_r(get_class_methods($a));
var_dump(method_exists($a, 'a_private'));
var_dump(is_callable(array($a, 'a_private')));

Cette fois nous avons le retour suivant :

Array
(
    [0] => __construct
    [1] => a_public
)
bool(true)
bool(false)

get_class_methods ne retourne plus que la liste des méthodes publiques, nous savons qu’une méthode privée existe bel et bien, mais nous savons aussi que nous ne pouvons pas l’appeler.

PHP propose une analyse des objets, mais les fonctions ne retournant pas la même chose en fonction du contexte d’appel rendent les choses confuses. Il faut voir que PHP s’occupe d’informer sur ce qui est appelable à un moment donné, et qu’il ne s’occupe pas d’analyser les données internes d’un objet, comme le permet ruby.

C’est dommage car il est très agréable de pouvoir analyser un objet en ruby. On lance irb, on crée un objet, on l’analyse, on teste, etc. Tout ça sans avoir à aller fouiller la doc. Ce n’est cependant pas une fonctionnalité nécessaire et on peut vivre sans. Quoiqu’il est des cas où il serait pratique de pouvoir connaître la visibilité d’une méthode, même dans le contexte de l’objet.

Prenons par exemple la situation suivante : je souhaite créer un controlleur générique avec une méthode run_action qui s’occupe de préparer l’action, de l’exécuter, de générer la vue, etc. Il est logique que cela se passe dans le controlleur, car c’est son boulot. Selon un point de vue de sécurité, comment vérifier qu’il s’agit bien d’une action publique et pas d’une méthode privée ? Dans le contexte du controlleur c’est impossible. Il faut sortir du controlleur pour pouvoir réaliser cette analyse :

$controlleur = new ActionController();
if (is_callable(array($controlleur, $action))) {
  $controller->run_action($action);
}
else {
  throw new Exception("Not a public action.");
}

Il est dommage de ne pas pouvoir faire entrer le test dans ActionController, mais c’est la seule manière d’y arriver, à moins d’inventer des règles (eg: toutes les méthodes qui ne sont pas des actions doivent commencer par un underscore).

About these ads

Une Réponse to “Analyser un objet en PHP”

  1. Julien Says:

    À noter que ceci est le comportement attendu de toutes les fonctions d’analyses d’un objet ou d’une classe. get_class_vars() par exemple, va retourner la liste des variables de classe en fonction de la visibilité que l’on a d’elle actuellement, tout comme get_class_methods() détaillé dans l’article ci-dessus.

    Au sein d’une classe unique, ces fonctions vont renvoyer la liste complète sans aucune possibilité de faire la distinction entre privé, protégé et public.

    En dehors d’une classe, on ne reçoit que la liste des méthodes ou attributs publics. Impossible en revanche de connaître la liste des attributs et méthodes privés ou protégés.

    Reste une situation que je n’ai pas testé. Depuis une classe qui hérite d’une classe parente, reçoit-on la liste des méthodes protégées (certainement) ? Reçoit-on les méthodes privés aussi (logiquement non, sinon c’est un bug) ?

    À l’inverse, la fonction lancée dans la classe parente renvoie-t-elle la liste des méthodes protégées de la classe enfant ? Logiquement oui, on s’attend à recevoir les méthodes publiques, privées et protégées de la classe parente, ainsi que la liste des méthodes publiques et protégées de la classe enfant.

    Cela reste cependant à vérifier.

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s


Suivre

Recevez les nouvelles publications par mail.

%d blogueurs aiment cette page :