Comunidad de diseño web y desarrollo en internet online

Crear e implementar el patrón de diseño Singleton en PHP

Singleton es un patrón de diseño, que tiene como propósito asegurar que solo se pueda crear una instancia de la clase y proporcionar un punto global de acceso a ella.

Objetivos Principales

  • Garantizar –y se debe asegurar de ello- que la clase solo tenga una y solo una instancia.
  • Debe proporcionar un punto de global conocido para acceder a ella.

Requerimientos:

  • Solo puede haber una instancia de la clase.
  • Proporcionar punto global de acceso a la instancia única.

Análisis de Requerimientos

  • El constructor de la clase debe ser privado (private). Es obvio que no puede ser público (public), y tampoco podría ser protegida (protected) porque sus hijos(subclases) -en caso de herencia-, podrían entonces crear múltiples instancias.
  • Para almacenar nuestra instancia debemos crear una propiedad estática privada. De nuevo, no puede ser protegida, ni pública.
  • Hacemos un método de clase (método estático) para crear el punto global de acceso a nuestra única instancia. Este método debe retornar la instancia única.

Singleton en PHP

Código :

<?php 
class Singleton
{
   private static $instancia;
   private $contador;
   

   private function __construct()
   {
      echo "He creado un " . __CLASS__ . "\n";
      $this->contador =0;
   }

   public static function getInstance()
   {
      if (  !self::$instancia instanceof self)
      {
         self::$instancia = new self;
      }
      return self::$instancia;
   }
   
   public function incrementar()
   {
      return ++$this->contador;
   }
   
   public function disminuir()
   {
      return --$this->contador;
   }
}
?>


Probando Singleton:

Código :

<?php
$instancia = Singleton::getInstance();
echo "instancia (incrementar): " . $instancia->incrementar() . "\n";
echo "instancia (incrementar): " .$instancia->incrementar() . "\n";
echo "instancia (incrementar): " .$instancia->incrementar() . "\n";
$otraInstancia = Singleton::getInstance();
echo "otraInstancia (disminuir): " .$otraInstancia->disminuir() . "\n";
echo "otraInstancia (incrementar): " .$otraInstancia->incrementar() . "\n";
?>

Salida:

He creado un Singleton
instancia (incrementar): 1
instancia (incrementar): 2
instancia (incrementar): 3
otraInstancia (disminuir): 2
otraInstancia (incrementar): 3

Con eso "aparentemente" hemos dado solución a los requerimientos de Singleton. Pues, no podemos invocar directamente al constructor de la clase, y solo podemos hacerlo a través del método getInstance(punto global de acceso conocido). Nuestra referencia de la instancia es privada así que la herencia no podría modificarla.

Sin embargo, aún cuando el constructor es privado así como la referencia de la instancia, aún en php no hemos asegurado que se creen más instancias de ella.

Transgresión 1: Clonado de objetos

En php en posible clonar objetos, lo que permitiría crear otra instancia de nuestro Singleton.

Demostración:

Código :

<?php
$instancia = Singleton::getInstance();
echo "Instancia quedo en: " . $instancia->incrementar() . "\n";
$miClon  = clone($instancia);
echo "Clon: ". $miClon->incrementar() . "\n";
echo "Clon: ". $miClon->incrementar() . "\n";
echo "Clon: ". $miClon->incrementar() . "\n";
echo "Instancia(incrementar-1) seguia en: " . ($instancia->incrementar()-1) . "\n";
?>

Salida:

He creado un Singleton
Instancia quedo en: 1
Clon: 2
Clon: 3
Clon: 4
Instancia(incrementar-1) seguia en: 1

Método mágico __clone, solución a clonado de singleton


Desde PHP 5 tenemos disponible el método mágico __clone, que es invocado cuando se clona un instancia. Con este método podemos emitir un mensaje de error y proceder a detener la ejecución del script por operación inválida.

Actualizando Singleton:

Código :

<?php 
class Singleton
{
   //codigo previo
   
   public function __clone()
   {
      trigger_error("Operación Invalida: No puedes clonar una instancia de ". get_class($this) ." class.", E_USER_ERROR );
   }
}
?>

Ahora nuestra salida sería:

He creado un Singleton
Instancia quedo en: 1
Fatal error: Operación Inválida: No puedes clonar una instancia de Singleton class.

Transgresión 2: serializacion y deserializacion

Con la función serialize podemos almacenar una representación apta de una variable (una instancia, es una variable a fin de cuentas), sin perder su tipo ni estructura. Y, con unserialize (deserializacion) podemos obtener esa "representación apta" de la variable, accediendo a ella como se si tratara de un clon.

Demostración:

Código :

<?php
$instancia = Singleton::getInstance();
echo "Instancia quedo en: " . $instancia->incrementar() . "\n";
$serializado = serialize($instancia);
$nuevaInstancia = unserialize($serializado);
echo "Deserializado: " . $nuevaInstancia->incrementar() . "\n";
echo "Deserializado: " . $nuevaInstancia->incrementar() . "\n";
echo "Deserializado: " . $nuevaInstancia->incrementar() . "\n";
echo "Instancia sigue en: " . ($instancia->incrementar()-1) . "\n";
?>

Salida:

Instancia quedo en: 1
Deserializado: 2
Deserializado: 3
Deserializado: 4
instancia(incrementar-1) seguía en: 1

Método __wakeup, solución a deserialización


Recurriendo de nuevo a los métodos mágicos, esta vez a __wakeup, que es invocado cuando objeto es desearilizado. Su contraparte es el método mágico __sleep, que se invoca cuando se serializa un objeto.

Actualizando Singleton

Código :

<?php 
class Singleton
{
   //codigo previo
   
   public function __wakeup()
   {
      trigger_error("No puedes deserializar una instancia de ". get_class($this) ." class.");
   }
}
?>

Ahora nuestra salida sería:

He creado un Singleton
Instancia quedo en: 1
Fatal error: Operación Inválida: No puedes deserializar una instancia de Singleton class.

Con el apoyo de estos métodos mágicos nos aseguramos de que no se genere más de una instancia de Singleton, cumpliendo así con el objetivo principal de Singleton que es el de mantener una instancia.

Nota Final:

Sé que el ejemplo no es de lo mejor para demostrar y quizás te cuesta ver lo que es el patrón de diseño Singleton. En una próxima entrega, explicaré otro tema donde usaré el patrón de diseño Singleton.

Exención


Singleton es un patrón de diseño, y como tal, la lógica e implementación de los patrones está divulgada en diversos artículos, lenguajes y versiones. Por lo que, cualquier código en este artículo -a menos que se indique lo contrario- solo se presenta con propósito educativo y ni el autor del articulo ni Cristalab tienen ni pretenden tomar propiedad intelectual sobre ellos.

¿Sabes SQL? ¿No-SQL? Aprende MySQL, PostgreSQL, MongoDB, Redis y más con el Curso Profesional de Bases de Datos que empieza el martes, en vivo.

Publica tu comentario

El autor de este artículo ha cerrado los comentarios. Si tienes preguntas o comentarios, puedes hacerlos en el foro

Entra al foro y participa en la discusión

o puedes...

¿Estás registrado en Cristalab y quieres
publicar tu URL y avatar?

¿No estás registrado aún pero quieres hacerlo antes de publicar tu comentario?

Registrate