Proxifier un objet PHP

Cet article explique comment proxifier une objet PHP sans toucher au code des méthodes de l'objet. Un cas d'utilisation est l'enregistrement dans un fichier de log des appels des méthodes de l'objet. Un autre cas d'utilisation est la modification du comportement de l'objet en fonction de paramètres externes par exemple suivant si on est connecté ou pas.

Il faut tout d'abord créer un objet proxy qui permettra de capter tous les appels des différentes méthodes au moyen de la méthode spéciale __call (disponible depuis PHP5).

// cette classe permet de tracker les méthode appelées
// de l'objet passé en paramètre
class ProxyCallLogger {
    private $obj = null;
    public function __construct($obj)
    {
        $this->obj = $obj;
    }
    public function __call($m, $a)
    {
        echo "Méthode $m appelée (on peut la logguer dans un fichier)<br/>";
        call_user_func_array(array($this->obj,$m),$a);
    }
}

La classe ProxyCallLogger prend en argument l'objet à surveiller ($obj). Sa méthode __call se charge de faire une opération puis éventuellement d'appeler la méthode de l'objet précédemment passé en paramètre. C'est un proxy.

Il faut ensuite déterminer quel objet php nous souhaitons surveiller. Nous prendrons un exemple très simple :

// ceci est la classe que dont on souhaite surveiller l'exécution
// on peut imaginer rajouter un paramètre $with_tracking dans sa
// méthode factory pour désactiver ou non le logger
class MyClass {
    static function factory($params = null)
    {
        return new ProxyCallLogger(new MyClass($params));
    }
    public function m1($p1, $p2)
    {
        var_dump(__CLASS__,__METHOD__,$p1,$p2);
    }
    public function m2($p1, $p2)
    {
        var_dump(__CLASS__,__METHOD__,$p1,$p2);
    }
}

On voit que la méthode factory permet d'instancier de façon transparente l'imbrication des classes ProxyCallLogger et MyClass. Ensuite les méthode m1 et m2 peuvent être appelées directement sans aucune modification dans leur code.

$o = MyClass::factory();
$o->m1('p1','p2');
$o->m2('p3','p4');

Cet exemple affichera :

Méthode m1 appelée (on peut la logguer dans un fichier)

string 'MyClass' (length=7)

string 'MyClass::m1' (length=11)

string 'p1' (length=2)

string 'p2' (length=2)

Méthode m2 appelée (on peut la logguer dans un fichier)

string 'MyClass' (length=7)

string 'MyClass::m2' (length=11)

string 'p3' (length=2)

string 'p4' (length=2)