13 de abril de 2015

Patrón Singleton PHP

Singleton o instancia única es un patrón de diseño cuya función es crear un único acceso global para restringir la creación de objetos pertenecientes a una clase o el valor de un tipo a un único objeto, de forma que garantiza que la clase sólo tenga una instancia además de proporcionar un acceso al objeto en toda la aplicación, de ahí su nombre Singleton (instancia única).

A continuación el ejemplo, tengo creada una base de datos llamada ejemplo y una tabla creada en esta base de datos de nombre empleados, lo primero sera crear un archivo php llamado parametros.php donde se especificaran los datos de conexion, este archivo lo guardare en una carpeta llamada CFG

<?php

// CFG/parametros.php guarda los parametros de conexion a la base de datos

    define('DB_MOTOR','mysql'); 
    define('DB_HOST','localhost'); 
    define('DB_USER','root'); 
    define('DB_PASS',''); 
    define('DB_NAME','ejemplo'); 
    define('DB_CHARSET','utf-8'); 

?>
Esta es la clase cnn.class.php que implementa el patrón de diseño Singleton
<?php
//cnn.class.php se encarga de crear la instancia unica del objeto

require_once "CFG/parametros.php";
class Conexion {

private static $_instancia;
private $_db;

 public static function getInstance() {
  if(!self::$_instancia) {
   self::$_instancia = new self();
                      /* otra opcion
                        $c = __CLASS__;
             self::$instancia = new $c;
        */
                 }
  return self::$_instancia;
 }


   private function __construct() {
      try{
        $this->db = new PDO(DB_MOTOR.':host='.DB_HOST.';dbname='.DB_NAME, DB_USER, DB_PASS);
        $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        $this->db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
        $this->db->setAttribute(PDO::ATTR_PERSISTENT,true);
        $this->db->setAttribute(PDO::MYSQL_ATTR_INIT_COMMAND, "SET NAMES 'utf8'");
  
   }catch(PDOException $e){
         echo 'Ha surgido un error y no se puede conectar a la base de datos' .E_USER_ERROR. PHP_EOL;
         echo "";
         echo "Detalle: ".$e->getMessage();
         exit;
      }
   }
  
   public function getConnection() {
  return $this->db;
 }
   
    public function CloseConnection(){
        return $this->db=null;
    }
   
    public function __clone(){
         trigger_error('No esta permitido clonar esta clase', E_USER_ERROR);
    } 

    public function __wakeup(){
         trigger_error("No puede deserializar una instancia de ". get_class($this) ." class.", E_USER_ERROR );
    }
}
?>

El patrón Singleton provee una única instancia global debido a que si se solicita una instancia de la clase:

a) La propia clase es la responsable de crear la única instancia, la cual debe garantizar que no se pueda crear ninguna otra (interceptando todas las peticiones para crear nuevos objetos) y proporcionando un solo punto de acceso a ella.

b) Si no se ha creado anteriormente (o sea, es la primera vez que se usa esta clase) se crea la instancia.

c) Si existe ya anteriormente una instancia (es la segunda o más veces que se usa), se retorna la existente todas las veces que se solicite.

d) El constructor de la clase debe declararse como privado para que la clase no pueda ser instanciada directamente.

e) Adicionalmente en PHP5 no se permite que la clase sea clonada con el método__clone().

Para hacer uso de la clase que implementa elpatron Singleton se crea el script test.php el cual solamente prueba la conexión a la base de datos y luego se desconecta

<?php
// test.php utiliza la instancia unica
// que se crea en la clase cnn.class.php
// utilizando el patron Singleton

require_once 'cnn.class.php';
$db1 = Conexion::getInstance();
$db = $db1->getConnection();
if(!is_null($db)){
   $stmt = $db->query('SELECT * FROM empleados');
$row_count = $stmt->rowCount();
echo $row_count.' rows selected';
}
else {
    echo"No se realizo la conexion";
}

try{
    $db = $db1->CloseConnection();
    echo "conexion cerrada";
}
catch(Exception $e){
    echo $e.getmessage();
}
?>

Apuntes del Patrón Singleton

Muchos caen en el cuento de Singleton de crear una instancia única para toda la aplicación pero en ambientes web se debe tener en cuenta la palabrita "stateless", básicamente indica que cada petición al servidor es tratada independientemente, es decir, si entra Jose a tu sitio web y hace operaciones sobre la base de datos y al mismo tiempo esta Pedro realizando operaciones sobre la base de datos(aunque no necesariamente sobre la misma tabla)ambas peticiones, ambos usuarios son tratados independientemente, basado en lo anterior, Singleton me garantiza una instacia única para cada usuario, no para toda la aplicación.

Se puede implementar perfectamente el patrón Singleton con PHP, pero por el contexto web, este se aplicará normalmente durante la vida de una única página, a menos que implementemos algún mecanismo para persistir el objeto Singleton y así lograr que este perdure durante toda la vida de la aplicación web
Aun cuando el patrón Singleton no cumple su promesa en ambientes web, tiene sus defensores en ambientes cliente servidor, aunque bien utilizar Dependecy Injections seria un a mejor alternativa de desarrollo igualmente una abstracción de la base de datos presenta una solución mas escalable.

Hay comentarios en que tratan a Singleton como anti-patrón y que mas les vale utilizar variables globales, la verdad si un desarrollador argumenta que utilizar variables globales es LA SOLUCIÓN pues no hay mas que hablar.

Aunque posible, realizar pruebas unitarias es un dolor de cabeza, igualmente algunos se quejan de que el patrón Singleton oculta la logica de la clase que instancia y que al momento de desarrollar modulos que dependan de la clase Singleton se les hará un dolor de cabeza el tener un constructor privado o el no conocer la lógica de la clase, pero eso depende de tu entorno, de que tanta confianza te tenga tu equipo de desarrollo, en mi opinión, aunque vale la pena el mencionarlo no lo veo como un problema.

Las transacciones funcionan correctamente en Singleton pues cada transacción es tratada por separado, así pues, Jose hará sus commit o rollback a la base de datos independientemente de los commit y rollback de Pedro.

La instancia del singleton la creas tú, y por tanto controlas su ciclo de vida igual que cualquier otro objeto.

Puedes crear una clase que herede de la clase que tienes definida como singleton, no tiene nada raro. Sin embargo, dado que no puedes sobreescribir los métodos estáticos, no tiene sentido heredar de la clase de métodos estáticos.