Mis à jour le 08/01/2018
  • 30 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

Ce cours existe en livre papier.

Vous pouvez obtenir un certificat de réussite à l'issue de ce cours.

Vous pouvez être accompagné et mentoré par un professeur particulier par visioconférence sur ce cours.

J'ai tout compris !

Les méthodes magiques

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Nous allons terminer cette partie par un chapitre assez simple. Dans ce chapitre, nous allons nous pencher sur une possibilité que nous offre le langage : il s'agit des méthodes magiques. Ce sont de petites bricoles bien pratiques dans certains cas. :)

Le principe

Vous devez sans doute vous poser une grande question à la vue du titre du chapitre : mais qu'est-ce que c'est qu'une méthode magique ? o_O

Une méthode magique est une méthode qui, si elle est présente dans votre classe, sera appelée lors de tel ou tel évènement. Si la méthode n'existe pas et que l’évènement est exécuté, aucun effet « spécial » ne sera ajouté, l’évènement s'exécutera normalement. Le but des méthodes magiques est d'intercepter un évènement, dire de faire ceci ou cela et retourner une valeur utile pour l’évènement si besoin il y a.

Bonne nouvelle : vous connaissez déjà une méthode magique ! :D

Si si, cherchez bien au fond de votre tête… Et oui, la méthode__constructest magique ! Comme nous l'avons vu plus haut, chaque méthode magique s'exécute au moment où un évènement est lancé. L’évènement qui appelle la méthode__constructest la création de l'objet.

Dans le même genre que__constructon peut citer__destructqui, elle, sera appelée lors de la destruction de l'objet. Assez intuitif, mais voici un exemple au cas où :

<?php
class MaClasse
{
  public function __construct()
  {
    echo 'Construction de MaClasse';
  }
  
  public function __destruct()
  {
    echo 'Destruction de MaClasse';
  }
}

$obj = new MaClasse;

Ainsi, vous verrez les deux messages écrits ci-dessus à la suite. Au même titre que__construct ,__destruct a lui aussi un petit nom : il s'agit du destructeur.

La surcharge magique des attributs et méthodes

Parlons maintenant des méthodes liées à la surcharge magique des attributs et méthodes.

Euh, deux secondes là… C'est quoi la « surcharge magique des attributs et méthodes » ?

Vous avez raison, il serait d'abord préférable d'expliquer ceci. La surcharge magique d'attributs ou méthodes consiste à créer dynamiquement des attributs et méthodes. Cela est possible lorsque l'on tente d'accéder à un élément qui n'existe pas ou auquel on n'a pas accès (s'il est privé et qu'on tente d'y accéder depuis l'extérieur de la classe par exemple). Dans ce cas-là, on a… voyons… 6 méthodes magiques à notre disposition !

« __set » et « __get »

Commençons par étudier ces deux méthodes magiques. Leur principe est le même, leur fonctionnement est à peu près semblable, c'est juste l'événement qui change.

Commençons par__set. Cette méthode est appelée lorsque l'on essaye d'assigner une valeur à un attribut auquel on n'a pas accès ou qui n'existe pas. Cette méthode prend deux paramètres : le premier est le nom de l'attribut auquel on a tenté d'assigner une valeur, le second paramètre est la valeur que l'on a tenté d'assigner à l'attribut. Cette méthode ne retourne rien. Vous pouvez simplement faire ce que bon vous semble !

Exemple :

<?php
class MaClasse
{
  private $unAttributPrive;
  
  public function __set($nom, $valeur)
  {
    echo 'Ah, on a tenté d\'assigner à l\'attribut <strong>', $nom, '</strong> la valeur <strong>', $valeur, '</strong> mais c\'est pas possible !<br />';
  }
}

$obj = new MaClasse;

$obj->attribut = 'Simple test';
$obj->unAttributPrive = 'Autre simple test';

À la sortie s'affichera :

Résultat affiché par le script
Résultat affiché par le script

Tenez, petit exercice, stockez dans un tableau tous les attributs (avec leurs valeurs) que nous avons essayé de modifier ou créer. :)

Solution :

<?php
class MaClasse
{
  private $attributs = [];
  private $unAttributPrive;
  
  public function __set($nom, $valeur)
  {
    $this->attributs[$nom] = $valeur;
  }
  
  public function afficherAttributs()
  {
    echo '<pre>', print_r($this->attributs, true), '</pre>';
  }
}

$obj = new MaClasse;

$obj->attribut = 'Simple test';
$obj->unAttributPrive = 'Autre simple test';

$obj->afficherAttributs();

Pas compliqué à faire, mais cela permet de pratiquer un peu.

Parlons maintenant de__get. Cette méthode est appelée lorsque l'on essaye d'accéder à un attribut qui n'existe pas ou auquel on n'a pas accès. Elle prend un paramètre : le nom de l'attribut auquel on a essayé d'accéder. Cette méthode peut retourner ce qu'elle veut (ce sera, en quelque sorte, la valeur de l'attribut inaccessible).

Exemple :

<?php
class MaClasse
{
  private $unAttributPrive;
  
  public function __get($nom)
  {
    return 'Impossible d\'accéder à l\'attribut <strong>' . $nom . '</strong>, désolé !<br />';
  }
}

$obj = new MaClasse;

echo $obj->attribut;
echo $obj->unAttributPrive;

Ce qui va afficher :

Résultat affiché par le script
Résultat affiché par le script

Encore un exercice.
Combinez l'exercice précédent en vérifiant si l'attribut auquel on a tenté d'accéder est contenu dans le tableau de stockage d'attributs. Si tel est le cas, on l'affiche, sinon, on ne fait rien.

Solution :

<?php
class MaClasse
{
  private $attributs = [];
  private $unAttributPrive;
  
  public function __get($nom)
  {
    if (isset($this->attributs[$nom]))
    {
      return $this->attributs[$nom];
    }
  }
  
  public function __set($nom, $valeur)
  {
    $this->attributs[$nom] = $valeur;
  }
  
  public function afficherAttributs()
  {
    echo '<pre>', print_r($this->attributs, true), '</pre>';
  }
}

$obj = new MaClasse;

$obj->attribut = 'Simple test';
$obj->unAttributPrive = 'Autre simple test';

echo $obj->attribut;
echo $obj->autreAtribut;

« __isset » et « __unset »

La première méthode__issetest appelée lorsque l'on appelle la fonctionissetsur un attribut qui n'existe pas ou auquel on n'a pas accès. Étant donné que la fonction initialeissetrenvoietrueoufalse, la méthode magique__issetdoit renvoyer un booléen. Cette méthode prend un paramètre : le nom de l'attribut que l'on a envoyé à la fonctionisset. Vous pouvez par exemple utiliser la classe précédente en implémentant la méthode__isset, ce qui peut nous donner :

<?php
class MaClasse
{
  private $attributs = [];
  private $unAttributPrive;
  
  public function __set($nom, $valeur)
  {
    $this->attributs[$nom] = $valeur;
  }
  
  public function __get($nom)
  {
    if (isset($this->attributs[$nom]))
    {
      return $this->attributs[$nom];
    }
  }
  
  public function __isset($nom)
  {
    return isset($this->attributs[$nom]);
  }
}

$obj = new MaClasse;

$obj->attribut = 'Simple test';
$obj->unAttributPrive = 'Autre simple test';

if (isset($obj->attribut))
{
  echo 'L\'attribut <strong>attribut</strong> existe !<br />';
}
else
{
  echo 'L\'attribut <strong>attribut</strong> n\'existe pas !<br />';
}

if (isset($obj->unAutreAttribut))
{
  echo 'L\'attribut <strong>unAutreAttribut</strong> existe !';
}
else
{
  echo 'L\'attribut <strong>unAutreAttribut</strong> n\'existe pas !';
}

Ce qui affichera :

Résultat affiché par le script
Résultat affiché par le script

Pour__unset, le principe est le même. Cette méthode est appelée lorsque l'on tente d'appeler la fonctionunsetsur un attribut inexistant ou auquel on n'a pas accès. On peut facilement implémenter__unsetà la classe précédente de manière à supprimer l'entrée correspondante dans notre tableau$attributs. Cette méthode ne doit rien retourner.

<?php
class MaClasse
{
  private $attributs = [];
  private $unAttributPrive;
  
  public function __set($nom, $valeur)
  {
    $this->attributs[$nom] = $valeur;
  }
  
  public function __get($nom)
  {
    if (isset($this->attributs[$nom]))
    {
      return $this->attributs[$nom];
    }
  }
  
  public function __isset($nom)
  {
    return isset($this->attributs[$nom]);
  }
  
  public function __unset($nom)
  {
    if (isset($this->attributs[$nom]))
    {
      unset($this->attributs[$nom]);
    }
  }
}

$obj = new MaClasse;

$obj->attribut = 'Simple test';
$obj->unAttributPrive = 'Autre simple test';

if (isset($obj->attribut))
{
  echo 'L\'attribut <strong>attribut</strong> existe !<br />';
}
else
{
  echo 'L\'attribut <strong>attribut</strong> n\'existe pas !<br />';
}

unset($obj->attribut);

if (isset($obj->attribut))
{
  echo 'L\'attribut <strong>attribut</strong> existe !<br />';
}
else
{
  echo 'L\'attribut <strong>attribut</strong> n\'existe pas !<br />';
}

if (isset($obj->unAutreAttribut))
{
  echo 'L\'attribut <strong>unAutreAttribut</strong> existe !';
}
else
{
  echo 'L\'attribut <strong>unAutreAttribut</strong> n\'existe pas !';
}

Ce qui donnera :

Résultat affiché par le script
Résultat affiché par le script

Finissons par « __call » et « __callStatic »

 

Laissons de côté les attributs pour le moment et parlons cette fois-ci des méthodes que l'on appelle alors qu'on n'y a pas accès (soit elle n'existe pas, soit elle est privée). La méthode__callsera appelée lorsque l'on essayera d'appeler une telle méthode. Elle prend deux arguments : le premier est le nom de la méthode que l'on a essayé d'appeler et le second est la liste des arguments qui lui ont été passés (sous forme de tableau).

Exemple :

<?php
class MaClasse
{
  public function __call($nom, $arguments)
  {
    echo 'La méthode <strong>', $nom, '</strong> a été appelée alors qu\'elle n\'existe pas ! Ses arguments étaient les suivants : <strong>', implode($arguments, '</strong>, <strong>'), '</strong>';
  }
}

$obj = new MaClasse;

$obj->methode(123, 'test');

Résultat :

Résultat affiché par le script
Résultat affiché par le script

Et si on essaye d'appeler une méthode qui n'existe pas statiquement ? Et bien, erreur fatale ! Sauf si vous utilisez__callStatic. Cette méthode est appelée lorsque vous appelez une méthode dans un contexte statique alors qu'elle n'existe pas. La méthode magique__callStaticdoit obligatoirement êtrestatic!

<?php
class MaClasse
{
  public function __call($nom, $arguments)
  {
    echo 'La méthode <strong>', $nom, '</strong> a été appelée alors qu\'elle n\'existe pas ! Ses arguments étaient les suivants : <strong>', implode ($arguments, '</strong>, <strong>'), '</strong><br />';
  }
  
  public static function __callStatic($nom, $arguments)
  {
    echo 'La méthode <strong>', $nom, '</strong> a été appelée dans un contexte statique alors qu\'elle n\'existe pas ! Ses arguments étaient les suivants : <strong>', implode ($arguments, '</strong>, <strong>'), '</strong><br />';
  }
}

$obj = new MaClasse;

$obj->methode(123, 'test');

MaClasse::methodeStatique(456, 'autre test');

Résultat :

Résultat affiché par le script
Résultat affiché par le script

Linéariser ses objets

Voici un point important de ce chapitre sur lequel je voudrais m'arrêter un petit instant : la linéarisation des objets. Pour suivre cette partie, je vous recommande chaudement ce tutoriel qui vous expliquera de manière générale ce qu'est la linéarisation et vous fera pratiquer sur des exemples divers. Lisez le jusqu'à la partie Encore plus fort !, et je vais mieux vous expliquer à partir de là !

Posons le problème

Vous avez un système de sessions sur votre site avec une classeConnexion. Cette classe, comme son nom l'indique, aura pour rôle d'établir une connexion à la BDD. Vous aimeriez bien stocker l'objet créé dans une variable$_SESSIONmais vous ne savez pas comment faire.

Ben si ! On fait$_SESSION['connexion'] = $objetConnexionet puis voilà !

Oui, ça fonctionne, mais savez-vous vraiment ce qui se passe quand vous effectuez une telle opération ? Ou plutôt, ce qui se passe à la fin du script ? En fait, à la fin du script, le tableau de session est linéarisé automatiquement. Linéariser signifie que l'on transforme une variable en chaîne de caractères selon un format bien précis. Cette chaîne de caractères pourra, quand on le souhaitera, être transformée dans l'autre sens (c'est-à-dire qu'on va restituer son état d'origine). Pour bien comprendre ce principe, on va linéariser nous-mêmes notre objet. Voici ce que nous allons faire :

  • Création de l'objet ($objetConnexion = new Connexion;) ;

  • transformation de l'objet en chaîne de caractères ($_SESSION['connexion'] = serialize($objetConnexion);) ;

  • changement de page ;

  • transformation de la chaîne de caractères en objet ($objetConnexion = unserialize($_SESSION['connexion']);).

Des explications s'imposent !

Les nouveautés rencontrées ici sont l'apparition de deux nouvelles fonctions :serializeetunserialize.

La première fonction,serialize, retourne l'objet passé en paramètre sous forme de chaîne de caractères. Vous vous demandez sans doutes comment on peut transformer un objet en chaîne de caractères : la réponse est toute simple. Quand on y réfléchit, un objet c'est quoi ? C'est un ensemble d'attributs, tout simplement. Les méthodes ne sont pas stockées dans l'objet, c'est la classe qui s'en occupe. Notre chaîne de caractères contiendra donc juste quelque chose comme : « ObjetMaClassecontenant les attributsunAttributqui vaut "Hello world !",autreAttributqui vaut "Vive la linéarisation",dernierAttributqui vaut "Et un dernier pour la route !" ». Ainsi, vous pourrez conserver votre objet dans une variable sous forme de chaîne de caractères. Si vous affichez cette chaîne par unechopar exemple, vous n'arriverez sans doute pas à déchiffrer l'objet, c'est normal, ce n'est pas aussi simple que la chaîne que j'ai montrée à titre d'exemple. Cette fonction est automatiquement appelée sur l'array$_SESSIONà la fin du script, notre objet est donc automatiquement linéarisé à la fin du script. C'est uniquement dans un but didactique que nous linéarisons manuellement.

La seconde fonction,unserialize, retourne la chaîne de caractères passée en paramètre sous forme d'objet. En gros, cette fonction lit la chaîne de caractères, crée une instance de la classe correspondante et assigne à chaque attribut la valeur qu'ils avaient. Ainsi, vous pourrez utiliser l'objet retourné (appel de méthodes, attributs et diverses opérations) comme avant. Cette fonction est automatiquement appelée dès le début du script pour restaurer le tableau de sessions précédemment enregistré dans le fichier. Sachez toutefois que si vous avez linéarisé un objet manuellement, il ne sera jamais restauré automatiquement.

Et quel est le rapport avec tes méthodes magiques ?

En fait, les fonctions citées ci-dessus (serializeetunserialize) ne se contentent pas de transformer le paramètre qu'on leur passe en autre chose : elles vérifient si, dans l'objet passé en paramètre (pourserialize), il y a une méthode__sleep, auquel cas elle est exécutée. Si c'estunserializequi est appelée, la fonction vérifie si l'objet obtenu comporte une méthode__wakeup, auquel cas elle est appelée.

« serialize » et « __sleep »

La méthode magique__sleepest utilisée pour nettoyer l'objet ou pour sauver des attributs. Si la méthode magique__sleepn'existe pas, tous les attributs seront sauvés. Cette méthode doit renvoyer un tableau avec les noms des attributs à sauver. Par exemple, si vous voulez sauver$serveuret$login, la fonction devra retourner['serveur', 'login'];.

Voici ce que pourrait donner notre classeConnexion:

<?php
class Connexion
{
  protected $pdo, $serveur, $utilisateur, $motDePasse, $dataBase;
  
  public function __construct($serveur, $utilisateur, $motDePasse, $dataBase)
  {
    $this->serveur = $serveur;
    $this->utilisateur = $utilisateur;
    $this->motDePasse = $motDePasse;
    $this->dataBase = $dataBase;
    
    $this->connexionBDD();
  }
  
  protected function connexionBDD()
  {
    $this->pdo = new PDO('mysql:host='.$this->serveur.';dbname='.$this->dataBase, $this->utilisateur, $this->motDePasse);
  }
  
  public function __sleep()
  {
    // Ici sont à placer des instructions à exécuter juste avant la linéarisation.
    // On retourne ensuite la liste des attributs qu'on veut sauver.
    return ['serveur', 'utilisateur', 'motDePasse', 'dataBase'];
  }
}

Ainsi, vous pourrez faire ceci :

<?php
$connexion = new Connexion('localhost', 'root', '', 'tests');

$_SESSION['connexion'] = serialize($connexion);

« unserialize » et « __wakeup »

Maintenant, nous allons simplement implémenter la fonction__wakeup. Qu'allons-nous mettre dedans ?

Rien de compliqué… Nous allons juste appeler la méthodeconnexionBDDqui se chargera de nous connecter à notre base de données puisque les identifiants, serveur et nom de la base ont été sauvegardés et ainsi restaurés à l'appel de la fonctionunserialize!

<?php
class Connexion
{
  protected $pdo, $serveur, $utilisateur, $motDePasse, $dataBase;
  
  public function __construct($serveur, $utilisateur, $motDePasse, $dataBase)
  {
    $this->serveur = $serveur;
    $this->utilisateur = $utilisateur;
    $this->motDePasse = $motDePasse;
    $this->dataBase = $dataBase;
    
    $this->connexionBDD();
  }
  
  protected function connexionBDD()
  {
    $this->pdo = new PDO('mysql:host='.$this->serveur.';dbname='.$this->dataBase, $this->utilisateur, $this->motDePasse);
  }
  
  public function __sleep()
  {
    return ['serveur', 'utilisateur', 'motDePasse', 'dataBase'];
  }
  
  public function __wakeup()
  {
    $this->connexionBDD();
  }
}

Pratique, hein ? :)

Maintenant que vous savez ce qui se passe quand vous enregistrez un objet dans une entrée de session, je vous autorise à ne plus appelerserializeetunserialize.

Ainsi, ce code fonctionne parfaitement :

<?php
session_start();

if (!isset($_SESSION['connexion']))
{
  $connexion = new Connexion('localhost', 'root', '', 'tests');
  $_SESSION['connexion'] = $connexion;
  
  echo 'Actualisez la page !';
}

else
{
  echo '<pre>';
  var_dump($_SESSION['connexion']); // On affiche les infos concernant notre objet.
  echo '</pre>';
}

Vous voyez donc, en testant ce code, que notre objet a bel et bien été sauvegardé comme il fallait, et que tous les attributs ont été sauvés. Bref, c'est magique !

Autres méthodes magiques

Voici les dernières méthodes magiques que vous n'avez pas vues. Je parlerai ici de__toString,__set_state__invoke et__debugInfo.

« __toString »

La méthode magique__toStringest appelée lorsque l'objet est amené à être converti en chaîne de caractères. Cette méthode doit retourner la chaîne de caractères souhaitée.

Exemple :

<?php
class MaClasse
{
  protected $texte;
  
  public function __construct($texte)
  {
    $this->texte = $texte;
  }
  
  public function __toString()
  {
    return $this->texte;
  }
}

$obj = new MaClasse('Hello world !');

// Solution 1 : le cast

$texte = (string) $obj;
var_dump($texte); // Affiche : string(13) "Hello world !".

// Solution 2 : directement dans un echo
echo $obj; // Affiche : Hello world !

Pas mal, hein ?

« __set_state »

La méthode magique__set_stateest appelée lorsque vous appelez la fonctionvar_exporten passant votre objet à exporter en paramètre. Cette fonctionvar_exporta pour rôle d'exporter la variable passée en paramètre sous forme de code PHP (chaîne de caractères). Si vous ne spécifiez pas de méthode__set_statedans votre classe, une erreur fatale sera levée.

Notre méthode__set_stateprend un paramètre, la liste des attributs ainsi que leur valeur dans un tableau associatif (['attribut' => 'valeur']). Notre méthode magique devra retourner l'objet à exporter. Il faudra donc créer un nouvel objet et lui assigner les valeurs qu'on souhaite, puis le retourner.

Puisque la fonctionvar_exportretourne du code PHP valide, on peut utiliser la fonctionevalqui exécute du code PHP sous forme de chaîne de caractères qu'on lui passe en paramètre.

Par exemple, pour retourner un objet en sauvant ses attributs, on pourrait faire :

<?php
class Export
{
  protected $chaine1, $chaine2;
  
  public function __construct($param1, $param2)
  {
    $this->chaine1 = $param1;
    $this->chaine2 = $param2;
  }
  
  public function __set_state($valeurs) // Liste des attributs de l'objet en paramètre.
  {
    $obj = new Export($valeurs['chaine1'], $valeurs['chaine2']); // On crée un objet avec les attributs de l'objet que l'on veut exporter.
    return $obj; // on retourne l'objet créé.
  }
}

$obj1 = new Export('Hello ', 'world !');

eval('$obj2 = ' . var_export($obj1, true) . ';'); // On crée un autre objet, celui-ci ayant les mêmes attributs que l'objet précédent.

echo '<pre>', print_r($obj2, true), '</pre>';

Le code affichera donc :

Résultat affiché par le script
Résultat affiché par le script

« __invoke »

 

Que diriez-vous de pouvoir utiliser l'objet comme fonction ? Vous ne voyez pas ce que je veux dire ? Je comprends !

Voici un code qui illustrera bien le tout :

<?php
$obj = new MaClasse;
$obj('Petit test'); // Utilisation de l'objet comme fonction.

Essayez ce code et… BAM ! Une erreur fatale. Pour résoudre ce problème, nous allons devoir utiliser la méthode magique__invoke. Elle est appelée dès qu'on essaye d'utiliser l'objet comme fonction (comme on vient de faire). Cette méthode comprend autant de paramètres que d'arguments passés à la fonction.

Exemple :

<?php
class MaClasse
{
  public function __invoke($argument)
  {
    echo $argument;
  }
}

$obj = new MaClasse;

$obj(5); // Affiche « 5 ».

« __debugInfo »

Cette méthode magique est invoquée sur notre objet lorsqu'on appelle la fonction var_dump. Pour ceux qui ne connaissent pas son principe, cette fonction permet d'obtenir des informations sur la variable qu'on lui donne. Si on lui donne un objet, var_dump va afficher les détails de tous les attributs de l'objet, qu'ils soient publics, protégés ou privés. La méthode magique __debugInfo permet de modifier ce comportement en ne sélectionnant que les attributs à afficher ainsi que ce qu'il faut afficher. Pour ce faire, cette méthode renverra sous forme de tableau associatif la liste des attributs à afficher avec leurs valeurs. Voici un exemple d'utilisation (je n'ai volontairement pas implémenté beaucoup de méthodes pour ne pas encombrer l'exemple) :

<?php
class FileReader
{
    protected $f;

	public function __construct($path)
	{
		$this->f = fopen($path, 'c+');
	}

	public function __debugInfo()
	{
		return ['f' => fstat($this->f)];
	}
}

$f = new FileReader('fichier.txt');
var_dump($f); // Affiche les informations retournées par fstat.

On a ici un gestionnaire de fichiers qui nous permet de gérer facilement la lecture et l'écriture d'un fichier (du moins, on imagine que ça le fait). Unvar_dump sur notre objet ne serait pas très révélateur. Par contre, obtenir les informations sur le fichier actuellement ouvert le serait plus, et c'est précisément ce que l'on fait en écrivant la méthode__debugInfo  avec l'appel defstat.

Ce chapitre portant sur les méthodes magiques s'arrête ici. Je parlerai de la méthode__clonelors du clonage d'objets en deuxième partie.

En résumé

  • Les méthodes magiques sont des méthodes qui sont appelées automatiquement lorsqu'un certain évènement est déclenché.

  • Toutes les méthodes magiques commencent par deux underscores, évitez donc d'appeler vos méthodes suivant ce même modèle.

  • Les méthodes magiques dont vous vous servirez le plus souvent sont__construct,__set,__getet__call. Les autres sont plus « gadget » et vous les rencontrerez moins souvent.

Vous êtes demandeur d'emploi ?
Sans diplôme post-bac ?

Devenez Développeur web junior

Je postule
Formation
en ligne
Financée
à 100%
Exemple de certificat de réussite
Exemple de certificat de réussite