Novedades PHP 5.3 (III) – Nuevo Singleton – Design Patterns

Seguimos con los posts sobre las novedades de PHP 5.3 y en este post empezaremos a ver cómo estas mejoras afectan o mejoran los patrones de diseño clásicos.

Los patrones de diseño son un conjunto de soluciones a problemas comunes en el desarrollo de aplicaciones informáticas. Hace años, casi nadie los usaba en aplicaciones web, pero con el crecimiento de Internet y de sus aplicaciones se ve necesario seguir una serie de patrones.

Para empezar, hablaremos del patrón Singleton y las diferencias entre PHP 5.2 y PHP 5.3. El patrón Singleton sirve para aquellos objetos en los cuales solamente tenemos una instancia en cada ejecución de nuestra aplicación. Ejemplos claros de ello pueden ser la conexión a la base de datos, la gestión de logs de la aplicación, en muchas ocasiones los modelos de un diseño MVC, etc.

Singleton en PHP 5.2

<?php
class DB {
    private static $_instance = null;
 
    private function  __construct() {
        // Aquí pondríamos la lógica de conexión a la BBDD
        // con PDO, mysql_connect, etc...
    }
 
    public static function getInstance() {
       if(self::$_instance === null) {
           self::$_instance = new DB();
       }
       return self::$_instance;
    }
    // Si no hacemos un override del metodo __clone,
    // podríamos tener más de una instancia
    final public function __clone() {
        throw new Exception('Only one instance is allowed');
    }
}
 
// Desde cualquier punto del código
$db = DB::getInstance();
 
// Sin el override de __clone, podríamos hacer
$db2 = clone $db;
// Y $db2 sería otro objeto de tipo DB
?>

Esto está muy bien y el código queda bastante mono pero tiene un problema. Si queremos hacer nuevos singleton en nuestra aplicación, deberemos repetir en todos ellos la lógica del getInstance, el override de __clone y todos estos temas o bien hacer algunas cosas raras. Y esto va en contra del principio DRY (Don’t Repeat Yourself!).

En PHP 5.3, afortunadamente, y ligado con el tema de los Late Static Bindings (ver post al respecto) existe la función get_called_class que nos da en un contexto static cuál es el nombre de la clase llamado. Y diréis… ¿y esto para qué narices sirve? Vamos a verlo!

Singletons en PHP 5.3 con get_called_class

<?php
abstract class Singleton {
    private static $_instances = array();
 
    final private function  __construct() {        
        // Por si acaso, controlamos que no exista ya
        if(array_key_exists(get_called_class(), self::$_instances)) {
            throw new Exception('Already one instance of '.get_called_class());
        }
        static::initialize();
    }
    // Método abstracto para inicializar cada singleton
    abstract protected static function initialize();
 
    final public static function getInstance() {
       $class = get_called_class();
       if(!array_key_exists($class, self::$_instances)) {
           self::$_instances[$class] = new $class();
       }
       return self::$_instances[$class];
    }
    final public function __clone() {
        throw new Exception('Class type ' . get_called_class() . ' cannot be cloned');
    }
}
 
class DB extends Singleton {
    protected static function initialize() {        
        // Aquí realizaríamos la conexión a la BBDD con el método que queramos
    }
}
 
class Log extends Singleton {
    protected static function initialize() {
        // Aquí trataríamos el log de la aplicación con gestión de directorios y archivos
    }
}
 
$db = DB::getInstance(); // Initializing DB
$log = Log::getInstance(); // Initializing Log
$db2 = clone $db; // Exception!
 
 
?>

Tenemos una clase abstracta singleton que controla las instancias únicas de todas ellas mediante un array static.
En cada clase que hereda de la abstracta, ya podemos olvidarnos del control de instancia única y solamente será necesario definir el método initialize y así las clases tendrán solamente aquellos métodos diferentes y no todo el control de singleton.

Y además, como dice mi amigo Raúl, queda bonito! 🙂

Próximamente… anonymous functions y arrays en la certificación! Y otros patrones!

You may also like...

5 Responses

  1. Javier S. says:

    Ei Ricard! Muy bueno el post, muy interesante!

    Tu ejemplo me ha hecho cuestionarme: si quisiéramos tener dos conexiones a dos bases de datos diferentes, no podríamos usar el patrón singleton. ¿Qué se hace en esos casos?

  2. Ricard Clau says:

    Hola Javier

    Para el caso de dos bases de datos, tienes almenos dos opciones:

    – La guarrísima: crear un objeto DB1 y un objeto DB2 y DB3, etc… (muy guarro pero efectivo, solo has de cambiar alguna cosilla en cada uno).

    – La bonita (para Raulito): se modifica el patrón singleton un poco y se usa una extensión del mismo llamada multiton (aunque el nombre siempre me ha parecido algo cutre, jejeje).

    Básicamente, le pasas un parámetro al método getInstance y la variable static no es un objeto sino un array de objetos, del cual devolvemos solo la clave que estamos pidiendo.

    En realidad, es un poco lo mismo que se hace en el singleton este común de PHP 5.3.

    ¿Lo entiendes con esto?

  3. Javier S. says:

    Gracias por la respuesta, Ricard. El patrón Multiton me parece una gran solución! Y bonita! 😛
    En artículo del Patrón Multiton en la wikipedia me ha ayudado a entenderlo.

    Sí que parece un poco un nombre de pokemon, pero va en la línea del singleton, jeje

    Un saludo!

  4. Pepe says:

    Hola. No tengo tan claro que algo deba heredar de una clase llamada Singleton o lo que es lo mismo, que una clase Singleton sea la raíz de muchos objetos

    Con interfaces tampoco tiene sentido porque sólo le indicas a esa clase que hace Singleton pero no cómo hacerlo. No los he usado todavía pero supongo que la implementación en PHP 5.4 de los trais servirían para solucionar este problema de forma más elegante.

    ¿Estoy metiendo mucho la pata?