Comment fonctionnent les facades sur Laravel

Actuellement, je suis en train de construire une application web basée sur Slim et un peut d'architecture faite maison sur le classique modèle MVC.

En étudiant le fonctionnement des facades sur Laravel, j'ai fini par capter leur fonctionnement "mystérieux" qui permet d'accéder à un objet spécifique depuis n'importe quel endroit d'une application.

Au final, il n'y a rien d'extraordinaire une fois qu'on a compris le fonctionnement. Fonctionnement que je vais expliquer ci-dessous en reprenant le code de Laravel.

Depuis une méthode magique dans une classe (peut-être abstraite)

En PHP, il existe une panoplie de méthodes magiques qui sont appelées à divers moments d'une application.

Celle qui nous intéresse plus particulièrement est __callStatic qui est appelée lorsque qu'une méthode statique ne peut pas être invoquée, soit parce qu'elle n'existe pas ou bien est inaccessible à l'extérieur (private ou protected).

// ~/src/Illuminate/Support/Facades/Facade.php ligne 253

public static function __callStatic($method, $args)
{
    $instance = static::getFacadeRoot();

    if (! $instance) {
        throw new RuntimeException('A facade root has not been set.');
    }

    return $instance->$method(...$args);
}

Là-dedans, Laravel appelle une méthode statique getFacadeRoot qui est censée retourner une instance de l'objet désiré.

Cette méthode appelle à son tour resolveFacadeInstance avec un paramètre crucial qui permet de faire le café: le résultat de getFacadeAccessor.

Lorsque l'on veut créer une facade personnalisée, la documentation de Laravel nous dit, en gros: étend Illuminate\Support\Facade et implémente la méthode getFacadeAccessor qui retourne une string.

Exemple avec Illuminate\Support\Facades\Cache:

// ~/src/Illuminate/Support/Facades/Cache.php

class Cache extends Facade
{
    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return 'cache';
    }
}

Jusque là, rien de sorcier.

Jusqu'au conteneur de dépendances

Jusqu'à maintenant, on a vu comment on sait quel objet aller récupérer dans le conteneur de dépendances. La méthode resolveFacadeInstance sur Illuminate\Support\Facades\Facade est censé renvoyer l'objet correct:

// ~/src/Illuminate/Support/Facades/Facade.php

/**
 * Resolve the facade root instance from the container.
 *
 * @param  object|string  $name
 * @return mixed
 */
protected static function resolveFacadeInstance($name)
{
    if (is_object($name)) {
        return $name;
    }

    if (isset(static::$resolvedInstance[$name])) {
        return static::$resolvedInstance[$name];
    }

    if (static::$app) {
        return static::$resolvedInstance[$name] = static::$app[$name];
    }
}

On remarque qu'il y a une couche de cache qui permet de répertorier les objets déjà résolus par la facade. Cela permet de ne pas devoir appeler le conteneur de dépendances à chaque fois qu'on désire appeler une méthode sur un objet.

En effet, la capacité du conteneur de dépendances de câbler automagiquement un graphe de dépendances d'objets repose sur la réflection. La réflection (ou l'introspection d'objets) est la capacité d'un langage à s'analyer à l'exécution, d'extraire des informations intéressantes depuis des classes et de les exploiter. Réfléchir prend du temps, m'voyez ? Blague à part.

Pour celles et ceux qui sont attentif·ve·s, vous allez certainement poser la question suivante: "Comment la facade connaît le conteneur ?".

La suite, au prochain épisode !

La connaissance du conteneur de dépendances

Il manque le composant essentiel du puzzle. Où est-ce que diable le conteneur de dépendances se cache là-dedans ?

La réponse réside aux lignes 239 à 241 du fichier ~/src/Illuminate/Support/Facades/Facade.php:

public static function setFacadeApplication($app)
{
    static::$app = $app;
}

Quelque part dans les entrailles de Laravel, cette méthode est appelée avec un conteneur de dépendances prêt à être utilisé. Pas mal, non ?

Avec ce qu'on a sous les yeux, on est capable de construire un système de facades fonctionnel à l'aide d'une méthode magique.

Le mot de la fin

Jusqu'à maintenant, on a parlé du fonctionnement des facades qui ont fait le sujet de cet article. Je pourrais encore continuer avec les alias et comment on arrive à résoudre \Cache vers Illuminate\Support\Facades\Cache. Je donne quelques indices: Illuminate\Foundation\AliasLoader, class_alias et spl_autoload_register.

Peut-être que j'en ferai un sujet pour un autre article.

Les IDEs sont déjà un peu perdus lors de l'utilisation des facades. On peut leur donner un coup de pouce en générant un fichier qui énumère les facades et les méthodes appelables sur l'objet réel, le tout bien rangé dans un commentaire PHPDoc sur la classe. Je vous donne l'outil qui permet de tout faire: la réflection.