Patrón singleton con herencia en PHP

Pensaba que sería más sencillo. Me he tirado varios días hasta conseguir que todo funcione correctamente.

La idea es implementar el siguiente diagrama diseñado con ArgoUML:

diagramadeclase11

Es decir, una clase base de la que heredan otras.

El escenario consiste en distintos webservices a los que tengo que llamar dentro de la aplicación. Lo que he hecho ha sido mapear cada webservice con una clase que contenga los mismos métodos que el webservice, de manera que lo que haya que hacer contra los webservice se haga contra clases del mismo nombre y con los mismos métodos devolviendo directamente la información devuelta.

Desde el principio planteé que las clases de los webservices extendiesen de una clase base que tuviese la funcionalidad común de todos, es decir, las llamadas a los webservices y el control de errores de los mismos, así evitaría duplicar esta funcionalidad en cada clase.

Posteriormente decidí que cada clase debía implementar el patrón singleton para asegurar que sólo hubiese una instancia de la misma clase en cada momento y evitar así que el código se viciase instanciando una nueva cada vez que se necesita.

La solución global era, por tanto, aplicar el singleton a la clase padre y que todas las que extendiesen de ésa tuviesen automáticamente el singleton y no tener que implementarlo en cada una de ellas. Idealmente es bonito, la práctica fue bastante peor.

La primera prueba fue declarar el método getInstancia encargado de asegurar el singleton de este modo:

private static $instancia = null;

public static function getInstancia($asurl)
{
    $returnValue = null;
    if (!isset(self::$instancia)) {
            $class = _CLASS_;
            self::$instancia = new $class($asurl);
    }
    $returnValue=self::$instancia;
    return $returnValue;
}

Se suponía que haciéndolo así se crearía, en caso de no existir, una nueva instancia del objeto que fue llamado, en nuestro caso, por ejemplo, Clase1. Sin embargo al comenzar las pruebas nos dimos cuenta que _CLASS_ no devuelve el nombre de la clase a la que se llama sino el de la clase sobre la que se ejecuta, en este caso Base, con lo cual no nos servía para nuestro propósito.

A continuación encontramos una función de PHP que nos devuelve lo que nosotros necesitamos, es decir, el nombre de la clase llamada: get_called_class. Lástima que esté disponible a partir de PHP 5.3 (en beta). Aún así encontramos una implementación alternativa de esta función válida para versiones anteriores de PHP.

if (!function_exists('get_called_class')){
    function get_called_class(){
        $bt = debug_backtrace();
        $lines = file($bt[1]['file']);
        preg_match('/([a-zA-Z0-9_]+)::'.$bt[1]['function'].'/', $lines[$bt[1]['line']-1], $matches);
        return $matches[1];
    }
}

Con esto ya podíamos crear la instancia de la clase que necesitábamos desde el singleton, pero nos aguardaba aún una sorpresa. No podíamos instanciar más de una clase (distinta) de las que heredan de un mismo padre, a pesar de ser distintos objetos asume que al heredar del mismo tiene que utilizar la misma instancia del singleton y no te deja, ya que  instancia (la variable del código anterior) ya está generada y es de otro tipo.

Pongamos un ejemplo para verlo mejor. Creamos una instancia de Clase1, el singleton nos devolverá una nueva instancia ya que no existe. Ahora creamos otra de Clase2. El objeto devuelto es de tipo Clase1. Aunque nosotros queremos crear objetos distintos (Clase1 y Clase2), la instancia de Base es compartida por ambos. Como ya he instanciado Clase1, instancia es de tipo Clase1, con lo que al instanciar Clase2 te devuelve la instancia ya creada, Clase1. Podríamos comprobar que instancia (la variable) esté creada y sea del mismo tipo que la nueva que se quiere instanciar, pero al crear la nueva (Clase2) destruiríamos la antigua (Clase1) con lo que perderíamos este objeto, y eso no es lo que se busca.

¿La solución? Parecerá algo cutre, pero es esta:

Base

class osusnet_com_Base
{
    private $asurl = '';
    private static $instancia = array();
    private $iVersion = null;
    private $sAsConsumer = '';
    private $debug = 0;
    protected function __construct($asurl)
    {
        if($asurl==""){
            return false;
        }else{
            $this->asurl=$asurl;
            $this->iVersion=1;
            $this->sAsConsumer="kk";
            $this->debug=0;
        }
    }

    public static function getInstancia($asurl)
    {
        $returnValue = null;
        $class = get_called_class();
        if (!isset(self::$instancia[$class])) {
            self::$instancia[$class] = new $class($asurl);
            $returnValue=self::$instancia[$class];
        }
        return $returnValue;
    }
}

Clase1

class osusnet_com_Clase1 extends osusnet_com_Base
{
    public function GetUserContext($cookie)
    {
    }
    public function CheckStateUser()
    {
    }
    public function LogIn()
    {
    }
}

Es decir, instancia (la variable) pasa a ser un array donde guardaremos un elemento para cada instancia de cada una de las clases que lo heredan. Si buscamos un tipo ya creado nos devolverá esa instancia, en caso contrario la crea y la deja disponible para posteriores instancias. La base es única pero se crea un objeto de cada clase disponible.

Como cualquier patrón singleton, para usarlo debemos hacer:

$instancia = osusnet_com_Clase1::getInstancia($url);

Recuerda que el constructor está protegido con lo cual cualquier intento de querer instanciar una clase de estas directamente dará error, prueba a hacer:

$instancia=new osusnet_com_Clase1();

No se cual será el comportamiento en otros lenguajes, mis años de Java quedan ya algo atrás, ni siquiera se si la teoría de POO es el comportamiento aquí descrito o es una mala implementación de PHP, no soy un experto en la teoria de la programación orientada a objetos. A mi la lógica me dice que si instancias una clase de un tipo, te debería instanciar todo,  sin compartir la clase base, aunque igual también tiene sentido que si todas extienden de Base y ésta tiene el singleton donde instancia y getInstancia son estáticos, sea común a todas. Cuando más lo pienso más me lío.

Al menos encontré una solución.

2 comentarios en “Patrón singleton con herencia en PHP

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *