Tutoriel PHP POO

PHP

Découvez la programmation orienté objet en de PHP.

HTML HTML5 CSS Dreamweaver Bootstrap PHP

Méthodes magiques : __call()

Av

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 !