Latest article: Exporter des données depuis Excel

Les meta-objets en PHP

Tutoriel sur les meta-objets en PHP

Pour inaugurer la nouvelle version du site, cet article parlera des meta-objets en PHP.

Les meta-objets sont utilisés pour représenter des objets génériques, dont les attributs et méthodes peuvent être définit lors de l’exécution.

Nous verrons donc leur utilité, et leur utilisation en PHP.

Les meta-objets

Tout d’abord, revenons sur une définition d’un objet : un ensemble d’attributs et de méthodes qui agissent sur ces attributs.

Un objet est une instance d’une classe. La classe est le patron, le gabarit, et l’objet instancié suit strictement ce gabarit.

Or ce modèle peut ne pas convenir à certains cas, car il est résolument statique. Avant de déclarer une classe, il faut connaitre ce qu’elle contiendra.

Je vais prendre un exemple : la récupération de données en provenance d’une base de donnée (MySQL pour l’exemple, mais le principe reste bien entendu le même avec n’importe quelle base de donnée) :

// Classe user, reflet d'une table MySQL
class User
{
    public var $Nom;
    public var $Prenom;
    public var $Age;
}

// On suppose que l'on est déjà connecté à la base de donnée
$result = mysql_query ( 'SELECT * FROM `User`' );
foreach ( $array = mysql_fetch_assoc ( $result ) )
{
    $user = new User();
    $user->Nom = $array['Nom'];
    $user->Prenom = $array['Prenom'];
    $user->Age = $array['Age'];
}

Vous voyez à quel point ça peut être laborieux.

Heureusement il existe un moyen d’automatiser tout ça : les meta-objets, des objets PHP qui ne sont pas créer à partir d’une classe.

Les méthodes __get, __set et __call

Et voilà que viennent à notre secours trois fonctions “magiques” du PHP : __get, __set et __call.

Elles vont nous servir à construire notre meta-objet.

Commençons par créer une classe :

class Object
{
    function __call( $name , $arguments )
    {
    }

    function __get( $name )
    {
    }

    function __set( $name , $value )
    {
    }

}

Les prototypes de ces 3 méthodes devraient déjà vous mettre la puce à l’oreille.

En effet, elles sont appelées lorsque vous tentez d’accéder à un attribut (__get, __set) ou une méthode (__call) qui n’existent pas dans l’objet.

Généralement on s’en sert pour afficher un message d’erreur personnalisé, mais nous allons voir ici que l’on peut aller beaucoup plus loin.

Les attributs

Au fond, que sont des attributs ? Une simple hashmap faisant correspondre un nom à une valeur.

En PHP on pourrait les modéliser ainsi :

protected $Attributes = array();

Voilà, rien qu’en ajoutant cette ligne à notre classe Objet, nous l’avons dotée d’autant d’attributs qu’elle le voudra.

PHP étant faiblement typé, nous pouvons stocker n’importe quel type de valeurs dans ce tableau. Les clés seront des chaines de caractère se référant au nom de l’attribut.

Implémentons maintenant les méthodes __get et __set :

class Object
{
    function __call( $name , $arguments )
    {
    }

    function __get( $name )
    {
        if ( isset( $this->Attributes[ $name ] ) )
        {
            return  $this->Attributes[ $name ];
        }
        else
        {
            echo "L'attribut " . $name . " n'existe pas dans l'objet !";
        }
    }

    function __set( $name , $value )
    {
        $this->Attributes[ $name ] = $value;
    }

    protected $Attributes = array();

}

Et voici comme ça se déroule à l’utilisation :

$user = new Object();
$user->Nom = 'Aranoth'; // fait appel en réalité à $user->__set ( 'Nom' , 'Aranoth' );
echo $user->Nom; // fait appel à $user->__get ( 'Nom' ), qui renvoi 'Aranoth' (ou une erreur si Nom n'a pas été définit avec __set avant)

On a donc un objet tout ce qu’il y a de plus classique… si ce n’est qu’il n’instancie aucune classe. L’objet est construit au fur et à mesure des appels à __set. C’est un meta-objet, un objet qui se créer lui même.

Reprenons notre exemple avec MySQL :

// Plus besoin de classe User

// On suppose que l'on est déjà connecté à la base de donnée
$result = mysql_query ( 'SELECT * FROM `User`' );
foreach ( $array = mysql_fetch_assoc ( $result ) )
{
    $user = new Object();
    $user->Nom = $array['Nom'];
    $user->Prenom = $array['Prenom'];
    $user->Age = $array['Age'];
}

L’intérêt ici ne se trouve pas vraiment dans la concision du code, mais plutôt dans son caractère générique, car c’est bien là le but de la méta-programmation. En effet il va désormais être possible de récupérer sous forme d’objets des enregistrements quelconques, d’une base de donnée tout aussi quelconque.

Cela peut être très utile pour factoriser le code en vue de faire une console d’admin générique permettant de voir ou d’éditer les enregistrements d’une base de donnée.

Les méthodes

Manipuler les attributs, c’est très bien. Ce sera parfois amplement suffisant. Mais peut être souhaitons nous réaliser des traitements sur ces données en utilisant des méthodes.

Cette fois, nous auront recours à la méthode __call.

Nous l’utiliseront de la même façon que __get : nous récupérerons le nom de la fonction à appeler, puis vérifieront si il elle nous est connue, et enfin l’appelleront avec les arguments récupérés.

Et parce qu’un peu de code vaut plus qu’un long discours :

class Object
{
    function __call( $name , $arguments )
    {
        if ( isset( $this->Methods[ $name ] ) )
        {
            // On ajoute un nouvel argument au tout début : $this
            array_unshift ( $arguments , $this );

            // Enfin on appelle la fonction
            return call_user_func_array  ( $this->Methods[ $name ]  , $arguments  );
        }
        else
        {
            echo "La méthdode " . $name . " n'existe pas dans l'objet !";
        }
    }

    function __get( $name )
    {
        if ( isset( $this->Attributes[ $name ] ) )
        {
            return  $this->Attributes[ $name ];
        }
        else
        {
            echo "L'attribut " . $name . " n'existe pas dans l'objet !";
        }
    }

    function __set( $name , $value )
    {
        $this->Attributes[ $name ] = $value;
    }

    protected $Attributes = array();
    protected $Methods = array();

}

Ici il y a plusieurs choses à noter.

Le PHP permet d’appeler des fonctions rien qu’avec leur nom, c’est ce à quoi sert la fonction call_user_func_array. Elle prend en paramètre le nom de la fonction à appeler et un tableau contenant les arguments fournis.

Pour que la méthode puisse agir sur l’objet qui l’appelle, elle doit avoir une référence vers lui. C’est le rôle de l’argument $this qui est automatiquement passé en paramètre quand vous appelez une méthode en PHP.

Ici nous devons reproduire ce comportement. Malheureusement, $this étant un mot-clé réservé du langage, on ne peut pas l’utiliser. On se contentera de $self, par exemple.

Autre point important, il n’y a rien pour spécifier une méthode. Il nous faudra donc ajouter notre propre fonction pour cela :

function SetMethod( $name , $function )
{
    $this->Methods[ $name ] = $function;
}

Avouons que c’est assez trivial.

Maintenant que nous avons fait le tour du mécanisme, voilà un exemple complet :

class Object
{

    function SetMethod( $name , $function )
    {
        $this->Methods[ $name ] = $function;
    }

    function __call( $name , $arguments )
    {
        if ( isset( $this->Methods[ $name ] ) )
        {
            // On ajoute un nouvel argument au tout début : $this
            array_unshift ( $arguments , $this );

            // Enfin on appelle la fonction
            return call_user_func_array  ( $this->Methods[ $name ]  , $arguments  );
        }
        else
        {
            echo "La méthdode " . $name . " n'existe pas dans l'objet !";
        }
    }

    function __get( $name )
    {
        if ( isset( $this->Attributes[ $name ] ) )
        {
            return  $this->Attributes[ $name ];
        }
        else
        {
            echo "L'attribut " . $name . " n'existe pas dans l'objet !";
        }
    }

    function __set( $name , $value )
    {
        $this->Attributes[ $name ] = $value;
    }

    protected $Attributes = array();
    protected $Methods = array();

}

// Utilisation :

function AfficherUser ( $self , $message )
{
    echo $self->Nom . ' ' . $self->Prenom . ' ( ' . $self->Age . ' ) à dit : ' . $message;
}

$user = new Object();
$user->Nom = 'Dupont';
$user->Prenom = 'Dupond';
$user->Age = 24;

$user->SetMethod ( 'Afficher' , 'AfficherUser' );

$user->Afficher ( 'Je dirais même plus...' ); // revient à appeler AfficherUser ( $user , 'Je dirais même plus...' )

C’est un exemple assez simple en fin de compte.

C’est désormais à vous de voir si vous pouvez tirer parti des meta-objets dans vos applications PHP. Cela vous servira d’entraînement.

Les commentaires sont bien entendu ouverts à toutes remarques, critiques, remerciements, insultes, demande d’aide, etc.

Ils sont là pour ça 😉

L’inauguration, c’est fait, donc.

Je vous donne rendez-vous pour un prochain article !

Discussion

Reply