mardi 27 mai 2014

Pattern Singleton + PHP trait : une manière simple d'implémenter un design pattern récurrent

Depuis PHP 5.3.0, il est possible de faire la différence entre la classe qui contient un code statique et la classe qui exécute ce code. Ce qui rend donc l'implémentation du design pattern singleton assez simple.
Et avec PHP 5.4.0, nous avons droit aux traits. Les traits sont des bouts de code qui peuvent être réutilisés dans n'importe quelle classe. Vous trouverez ici tout ce que vous devez savoir sur les traits.

Objectif

Dans une premier temps je vais présenter (rapidement) le pattern singleton. En suite je vais décrire une méthode d'implémentation baseé sur les traits de PHP.
Attention :
Utiliser des traits pour ce genre de technique à l'avantage de ne pas bloquer l'héritage d'un objet, tout en écrivant un code complet (en opposition aux interfaces, où le code doit être recopié implémenté pour chaque classe).
Le pattern singleton est relativement simple a écrire, c'est pourquoi je trouve qu'il s'associe bien avec cette nouvelle fonctionnalité de PHP. Mais on peut rapidment écrire du code spaghetti avec le traits. C'est pourquoi je préconise de les utiliser seulement sur des comportements simples est clairs.

Le pattern singleton

Présentation

Le pattern singleton consiste à ne créer qu'une seule instance de classe et n'utiliser quelle dans toute l'application. L'exemple le plus courant est l'utilisation d'une classe "Db" pour la connexion à la base de données. C'est l'exemple que je vais prendre, et "Db" va étendre "PDO" pour plus de simplicité.
Plus besoin de se trimbaler une variable globale, où d'autres techniques douteuse pour récupérer la connexion, le code devient :
$db = Db::singleton();
ou encore
$count = Db::singleton()->exec("...");
Cette implémentation permet de passer des paramètres au constructeur si nécessaire.

Implémentation du pattern singleton

trait Singleton {
    /**
     * @ignore
     */
    public static $instance = null;
    
    /**
     * Return the same instance of the class.
     * 
     * @param mixed $args[optional]
     * @return object
     */
    public static function singleton() {
        $class = new ReflectionClass(get_called_class());
        $instance = $class->getProperty("instance");
        if (is_null($instance->getValue())) {
            switch (func_num_args()) {
            case 0:
                $instance->setValue($class->newInstance());
                break;
            default:
                $instance->setValue($class->newInstanceArgs(func_get_args()));
            }
        }
        return $instance->getValue();
    }
}

Utilisation de notre "trait"

Maintenant il suffit simplement de créer la "Db", de lui ajouter le trait "Singleton", de créer un constructeur avec des paramètres par défaut pour simplifier les choses.
class Db extends PDO {
    use Singleton;
   
    public function __construct($user = "USERNAME, $password = "PASSWORD") {
        $dsn = "mysql:host=DB_HOST;dbname=DB_NAME;charset=utf8";
        parent::__construct($dsn, $user, $password);
    }
}

Exemple d'utilisation sans arguments pour le constructeur:
$count = Db::singleton()->exec("...");
if ($count == 0) {
    ...
}

Exmple d'utilisation où la connexion dépend de l'utilisateur connecté:
$count = Db::singleton($user->login, $user->password)->exec("...");
if ($count == 0) {
    ...
}

Remarque: dans le deuxième cas, cela ne marche que si le premier appel à "Db::singleton()" se fait avec les paramètres, car uns fois l'instance crée elle est réutilisée.

Aucun commentaire:

Enregistrer un commentaire