Méthodes magiques : __call()

Rechercher

Méthodes magiques : __call()

  • Par Palleas
  • 6 commentaires
  • 26839 lectures
  • RSS -  Atom

PHP a fait un grand pas en avant en matière de programmation orientée objet avec sa version 5. Depuis cette version, il permet d'implémenter des méthodes au comportement prédéfini par PHP. Ces méthodes sont nommées « méthodes magiques », __call() est l'une d'entre elles.

Appeler une méthode qui n'existe pas

Prenons l'exemple d'une classe qui modélise un Manchot, que l'on instancie pour ensuite appeler sa méthode « voler ».

<?php
class Manchot
{
}
$georges = new Manchot();
$georges->voler('Afrique');
?>

Ce morceau de code vous lèvera une erreur. Vous ne le saviez peut-être pas mais les manchots ne peuvent pas voler :

Fatal error: Call to undefined method Manchot::voler() in /path/to/Apprendre-php/magic_methods.php on line 4.

Ce petit rappel morphologique vous permet surtout de voir la chose suivante : on ne peut pas appeler une méthode qui n'existe pas. Cependant PHP, grâce à la méthode magique __call(), va vous permettre de violer une loi élémentaire de la nature, à savoir faire voler un manchot ou plus généralement appeler une méthode qui n'a pas été déclarée dans votre classe.

Implémenter la méthode __call()

La méthode __call() prend deux paramètres. Le premier contient le nom de la méthode que vous avez cherché à appeler, le seconde contient les arguments que vous lui avez passés. Le listing ci-après présente le structure globale de cette méthode.

<?php
class MyObject
{
/**
* Methode magique __call()
*
* @param string $method Nom de la méthode à appeler
* @param array $arguments Tableau de paramètres
* @return void
*/
public function __call($method, $arguments)
{
// Code personnalisé à exécuter
}
}
?>

Maintenant reprenons l'exemple du Manchot.

<?php
class Manchot
{
/**
* Methode magique __call()
*
* @param string $method Nom de la méthode à appeler
* @param array $arguments Tableau de paramètres
* @return void
* @access private
*/
private function __call($method,$arguments)
{
echo 'Vous avez appelé la méthode ', $method, 'avec les arguments : ', implode(', ',$arguments);
}
}
$george = new Manchot();
$george->voler('Afrique');
?>

Quelques remarques :

Si vous avez rendu votre méthode __call() publique, vous aurez aussi la possibilité de l'appeler directement en faisant : $georges->__call('voler','Afrique'); mais il y aura une petite différence. En appelant directement la méthode voler(), la variable $arguments sera un array stockant les différents arguments. A contrario, si vous passez par la méthode __call(), le second argument sera du type que vous voudrez.

A l'heure actuelle, il est impossible d'en faire de même avec des méthodes statiques, c'est quelque chose qui est désormais corrigée dans la version 5.3 de PHP qui vient tout juste de sortir en version alpha 1. Une méthode magique nommée « __callStatic() » permet, en PHP 5.3, d'appeler des méthodes statiques qui ne sont pas déclarées dans la classe.

Exemple concret : création d'un moteur de recherche

Vous vous dites que cela n'a pas grand intérêt, et pourtant avec l'exemple suivant vous devriez y voir un peu plus clair.

Nous allons tenter de recréer un moteur de recherche. Vous remarquerez que nous utilisons la classe SPDO présentée dans un précédent tutoriel et qui permet d'accéder à la base de données via l'extension native PDO.

<?php
class SearchEngine
{
/**
* Effectue une recherche dans la base de données à
* partir des critères fournis en argument
*
* @param array $conditions Tableau de critères de recherche
* @return array $return Tableau des résultats
* @see SPDO
*/
public function search($conditions = array())
{
$query = 'SELECT id FROM table';
if(sizeof($conditions) > 0) {
$query.=' WHERE '.implode(' AND ',$conditions);
}
// Exécution de la requête SQL avec une classe PDO
$result = SPDO::getInstance()->query($query);
$return = $result->fetchAll(PDO::FETCH_ASSOC);
return $return;
}
}
?>

Comme vous pouvez le constater, ce moteur de recherche possède une méthode search() qui prend en paramètre un tableau des différentes conditions à appliquer à la requête effectuant la recherche. Ces conditions étant de la forme suivante : nomDuChamp= "valeur".

Vous admettrez comme moi (j'espère !) que cette syntaxe n'est pas des plus pratiques, je ne me vois pas utiliser la requête de cette manière :

<?php
$mySearchEngine = new SearchEngine();
$mySearchEngine->search(array(
'champ1' => 'apprendre-php',
'champ2' => 'palleas'
));

Ce serait vraiment sympa de pouvoir faire $mySearchEngine->searchByName('palleas'); par exemple, ou encore $mySearchEngine->searchByNameAndDate('palleas','25/07/1987'); pas vrai ?

Et c'est là que l'on va pouvoir mettre en application la méthode __call().

<?php
class SearchEngine
{
/**
* Effectue une recherche dans la base de données à
* partir des critères fournis en argument
*
* @param array $conditions Tableau de critères de recherche
* @return array $return Tableau des résultats
* @see SPDO
*/
public function search($conditions = array())
{
$query = 'SELECT id FROM table';
if(sizeof($conditions) > 0) {
$query.=' WHERE '.implode(' AND ',$conditions);
}
// Exécution de la requête SQL avec une classe PDO
$result = SPDO::getInstance()->query($query);
$return = $result->fetchAll(PDO::FETCH_ASSOC);
return $return;
}
/**
* Méthode magique __call() permettant d'appeller une méthode virtuelle
* du type searchByName(), searchByAge() ou searchByNameAndAge()...
*
* @param string $method Nom de la méthode virtuelle appelée
* @param array $args Tableau des critères de recherche
* @return array|null $return Tableau des résultats ou NULL
* @see SearchEngine::search()
*/
public function __call($method, $args)
{
if(preg_match('#^searchBy#i',$method))
{
$searchConditions = str_replace('searchBy','',$method);
$searchCriterias = explode('and',$searchConditions);
$conditions = array();
$nbCriterias = sizeof($searchCriterias);
for($i=0; $i < $nbCriterias; $i++)
{
$conditions[] = strtolower($searchCriterias[$i]).'="'.$args[$i] .'"';
}
return $this->search($conditions);
}
return null;
}
}
?>

Voilà un morceau de code assez conséquent à digérer, nous allons donc le décortiquer étape par étape :

  • Pour commencer, on vérifie que la méthode que l'on a cherché à appeler est une méthode dont le nom commence par « searchBy ». Cette étape n'est pas indispensable, nous appellerons ça une précaution : nous nous assurons ainsi de l'intuitivité du code.
  • On récupère ce qu'il y a après searchBy, dans cet exemple : name
  • Au cas ou nous aurions plusieurs Conditions, par exemple searchByNameAndDate, on récupère chacun des champs à tester, ici NameAndDate.
  • Pour chacun des paramètres, on crée la condition, dans le cas de : $google->searchByNameAndDate('palleas','25/07/1987'); on obtient un tableau avec name=«palleas » et date=«25/07/1987» que l'on va pouvoir passer en paramètre à la méthode search(), comme on en parlait plus haut.

Inconvénients de l'utilisation de la méthode magique __call()

Au même titre que les méthodes magiques __get() et __set(), la méthode magique __call() possède deux inconvénients non négligeables lorsque l'on développe en environnemet professionnel. En effet, l'utilisation de __call() empêche tout d'abord la génération automatique de documentation de code au moyen des APIs (PHPDocumentor par exemple) utilisant les objets d'introspection (Reflection). D'autre part, cela empêche également les IDE tels qu'Eclipse d'introspecter le code de la classe et ainsi proposer l'auto-complétion du code. A utiliser donc avec parcimonie !



Les commentaires

1. Par Mickaël Wolff le 11/08/2008 05:01

J'utilise aussi cette méthode dans une classe proxy (pour accéder à mes données par des méthodes standardisées). Malheureusement, une utilisation intensive de __call rend le code illisible, et empêche de créer une documentation complète de manière aisée (puisque par définition les noms des méthodes sont dynamiques.

Bref, à utiliser avec retenue.

2. Par Palleas le 11/08/2008 09:18

C'est vrai le coup de la doc, je vais le rajouter, merci

3. Par greg le 26/11/2008 15:19

LA POO C'est chaud sérieux...

4. Par Tom le 10/05/2009 21:46

Une grande utilisation aussi:

Savoir qui appel vos fonctions:

Ajouter "echo var_dump(debug_backtrace());"

Dans la méthode __call()

5. Par caccia le 28/07/2009 11:36

On ne pourrait pas passer tout ce qu'il y a dans la méthode _call dans la méthode search?

6. Par boulet_sensei le 25/08/2009 08:56

difficile de faire une doc propre, mais toujours facile a mettre en place pour de grosses classes. Bref, je ne le trouve pas tres pratique, sauf pour creer des pages d erreurs plus propres quand une methodes inexistantes est apellee.
c est bon a savoir, sans pour autant l utiliser.
totalement hors sujet mais je conseille a tous cakephp, un framework php complet et simple !