samedi, 30 décembre 2006

Surchages de méthodes statiques en PHP

Un tout petit exemple pour exposer le problème :

class A {
  public static function f() {
    echo get_class();
  }
}

class B extends A {}

A::f();
B::f();


A::f() va afficher A, par contre B::f() va aussi afficher A, alors que la réponse attendue serait B.

Ce comportement est connu des développeurs OO, peut s'avérer ennuyeux dès qu'on utilise des fonctions statiques surchargées, car les appels à self::une_fonction() (une_fonction est donc une méthode statique) dans le parent résulteront toujours à un appel de la méthode du parent, et non pas celle surchargée dans le fils. La raison est que self dans la classe parente représente le parent, comme c'est aussi le cas dans d'autres langages OO.
Illustrons les limites de ce comportement avec la mise en place d'une factory toute simple :

class A {
  ...
  public static function create($params) {
    $class = get_class();
    return new $class($params);
  }
}
class B extends A {}
$b = B::create($myparams);


$b contiendra toujours un objet de classe A, car get_class retournera toujours A.

L'idée pour palier à cette limitation est donc simplement d'ajouter un argument à la fonction create de A, qui sera la classe à instancer :

class A {
  ...
  public static function create($params, $class = __CLASS__) {
    return new $class();
  }
}
class B extends A {
  public static function create($params, $class = __CLASS__) {
  return parent::create($params, $class);
  }
}
$b = B::create($myparams);


Avec cette petite adaptation, $b sera bien un objet B.

De manière plus général, si on envisage d'utiliser des fonctions statiques surchargées, PHP nous permet de gérer l'héritage en passant la classe en argument et en appelant la fonction statique au moyen de la macro call_user_func :

class A {
  protected static une_fonction($arg) { return $arg + 1; }
  ...
  public static function create($params, $class = __CLASS__) {
    $res = call_user_func(array($class, 'une_fonction'), $args);
  ...
  }
}
class B extends A {
  protected static une_fonction($arg) { return $arg + 2; }
  public static function create($params, $class = __CLASS__) {
  return parent::create($params, $class);
  }
}
$b = B::create($myparams);


Grâce à cette macro call_user_func, B::create appellera donc bien B::une_fonction.

Aucun commentaire: