Aller au contenu
M07 · B. Programmer en PHP

Les méthodes magiques de PHP

Les méthodes que PHP appelle tout seul à des moments précis — __construct, __destruct, __toString, __get, __set, __isset, __unset, __call, __callStatic, __clone, __invoke — et la sérialisation avec __sleep, __wakeup, __serialize et __unserialize.

1Situer

Afficher un produit avec un simple echo, accepter des propriétés « à la demande » pour une fiche client, garder un panier entre deux visites en le transformant en texte : PHP prévoit des points d'accroche, les méthodes magiques, que la classe peut définir pour réagir à ces situations. Elles commencent toutes par deux tirets bas.

2Comprendre

Les méthodes que PHP appelle tout seul
ce qu'écrit le code ce que PHP appelle new Fiche('Amina Benali') $f->ville = 'Fès' echo $f->fax echo $f $f->appeler() serialize($f) __construct()

new Fiche('Amina Benali') : PHP appelle __construct.

1 / 6

Qu’est-ce qu’une méthode magique ?

MéthodePHP l’appelle quand…
__construct()l’objet est créé (new)
__destruct()l’objet est détruit (plus utilisé, ou fin du script)
__toString()l’objet est utilisé comme une chaîne (echo, concaténation)
__get($nom) / __set($nom, $val)on lit / écrit une propriété inaccessible ou inexistante
__isset($nom) / __unset($nom)on appelle isset() / unset() sur une telle propriété
__call($nom, $args) / __callStatic(...)on appelle une méthode inaccessible / statique inaccessible
__clone()l’objet est copié avec clone
__invoke(...)l’objet est appelé comme une fonction : $objet(...)
__sleep() / __wakeup()l’objet est sérialisé / désérialisé
__serialize() / __unserialize()idem, version moderne (PHP 7.4+)

Construire, détruire, afficher

class Journal
{
    private $f;
    public function __construct(string $chemin) { $this->f = fopen($chemin, 'a'); }
    public function ecrire(string $txt): void { fwrite($this->f, date('H:i ') . $txt . PHP_EOL); }
    public function __destruct() { fclose($this->f); }   // fermé automatiquement
}

class Produit
{
    public function __construct(private string $nom, private float $prix) {}
    public function __toString(): string { return "{$this->nom} ({$this->prix} DH)"; }
}
echo "Produit vedette : " . new Produit("Tajine", 180);

Propriétés dynamiques : __get, __set, __isset, __unset

class Config
{
    private array $valeurs = [];
    public function __get(string $cle): mixed { return $this->valeurs[$cle] ?? null; }
    public function __set(string $cle, mixed $v): void { $this->valeurs[$cle] = $v; }
    public function __isset(string $cle): bool { return isset($this->valeurs[$cle]); }
    public function __unset(string $cle): void { unset($this->valeurs[$cle]); }
}

$c = new Config();
$c->devise = "DH";            // __set
echo $c->devise;              // __get → DH
var_dump(isset($c->tva));     // __isset → bool(false)
unset($c->devise);            // __unset

Méthodes à la demande : __call, __callStatic, __invoke

class Requete
{
    private array $filtres = [];
    public function __call(string $nom, array $args): static
    {
        if (str_starts_with($nom, 'where')) {             // whereVille('Fès'), wherePrix(30)...
            $champ = strtolower(substr($nom, 5));
            $this->filtres[$champ] = $args[0];
            return $this;
        }
        throw new BadMethodCallException("Méthode $nom inconnue");
    }
    public function sql(): string
    {
        return "WHERE " . implode(" AND ", array_map(fn($c) => "$c = ?", array_keys($this->filtres)));
    }
}
echo (new Requete())->whereVille('Fès')->whereStatut('payée')->sql();

class Remise
{
    public function __construct(private int $pourcent) {}
    public function __invoke(float $prix): float { return $prix * (100 - $this->pourcent) / 100; }
}
$soldes = new Remise(20);
echo $soldes(150);                                // 120
print_r(array_map($soldes, [30, 150]));           // [24, 120]

Copier un objet : clone et __clone

Sérialiser : __sleep, __wakeup, __serialize, __unserialize

class Panier
{
    private array $lignes = [];
    private ?PDO $pdo = null;              // une connexion ne se sérialise pas

    public function __serialize(): array
    {
        return ['lignes' => $this->lignes];
    }
    public function __unserialize(array $data): void
    {
        $this->lignes = $data['lignes'];
        // $this->pdo sera recréée à la première requête
    }
}
file_put_contents('panier.txt', serialize($panier));
$panier = unserialize(file_get_contents('panier.txt'));

$_SESSION utilise ce mécanisme : un objet rangé en session est sérialisé à la fin du script et reconstruit au session_start() suivant (la classe doit alors être chargée avant session_start()).

Exemple simple

Le détecteur de mouvement

Une lampe à détecteur s'allume toute seule quand quelqu'un passe : personne n'appuie sur l'interrupteur. Une méthode magique est appelée par PHP lui-même quand l'événement se produit (affichage, lecture d'une propriété inconnue...).

Exemple simple

La valise sous vide

Pour ranger un objet dans un fichier ou une session, on le « met sous vide » (serialize) en une chaîne de texte, puis on le regonfle plus tard (unserialize). __sleep et __wakeup disent quoi garder et quoi refaire à l'arrivée.

3Manipuler

La classe Fiche stocke des informations libres sur un client. Complétez ses méthodes magiques pour réaliser les consignes.
Pas à pas dans le lab ↗ Cliquez sur Exécuter : PHP 8.3 démarre dans votre navigateur (environ 5 Mo à télécharger, une seule fois).

La page s'affichera ici.

Le navigateur demandera /index.php au serveur, qui exécutera le PHP et renverra du HTML.

Entre le navigateur et le serveur aucun échange pour l'instant

Consignes 0 / 3

  1. « Ville : Fès » et « Fax : non renseigné »
  2. echo $f affiche « Amina Benali (2 infos) »
  3. « appeler() n'existe pas sur la fiche de Amina Benali »

4Pratiquer

Exercice 1 · __toString pour un produit

Ajoutez à la classe Produit(nom, prix) une méthode __toString qui renvoie « Tajine (180 DH) », puis affichez une liste de produits avec implode.

Voir le corrigé
<?php
class Produit
{
    public function __construct(private string $nom, private float $prix) {}
    public function __toString(): string
    {
        return "{$this->nom} ({$this->prix} DH)";
    }
}
$liste = [new Produit("Tajine", 180), new Produit("Savon", 30)];
echo implode(", ", $liste);   // implode convertit chaque objet en chaîne avec __toString

Exercice 2 · Clonage profond

Un objet Commande contient un objet Client. Après $copie = clone $commande, modifier $copie->client->ville modifie aussi l'original. Corrigez avec __clone.

Voir le corrigé
<?php
class Commande
{
    public function __construct(public Client $client, public array $lignes = []) {}
    public function __clone()
    {
        $this->client = clone $this->client;   // copie aussi l'objet contenu
    }
}

Exercice 3 · Sérialiser un panier

Un objet Panier contient une connexion PDO (qu'on ne peut pas sérialiser) et un tableau de lignes. Écrivez __sleep et __wakeup pour ne garder que les lignes et reconnecter au réveil.

Voir le corrigé
<?php
class Panier
{
    private ?PDO $pdo = null;
    private array $lignes = [];

    public function __sleep(): array
    {
        return ['lignes'];                // propriétés à garder
    }

    public function __wakeup(): void
    {
        $this->pdo = new PDO('mysql:host=localhost;dbname=souk', 'root', '');
    }
}
$texte = serialize($panier);              // O:6:"Panier":1:{s:14:"Panierlignes";a:...}
$panier2 = unserialize($texte);           // __wakeup est appelé

5Vérifier

1. Quand PHP appelle-t-il __get('stock') ?
2. Quelle méthode magique permet d'écrire echo $produit; ?
3. Quand __destruct est-il appelé ?