Crear una matriz estática sin cambiar miles de líneas de código

Tenemos una clase que contiene una matriz pública llamada $saved que contiene muchos datos necesarios para compartir entre métodos (ejemplo a continuación)...

class Common {
    public $saved = array();

    public function setUser($data) {
        $this->saved['user_data'] = $data;
    }

    public function getUserID() {
        return $this->saved['user_data']['id'];
    }
}

Hay literalmente miles de líneas de código que funcionan así.

El problema es esa nueva instancia de clases que se extienden Common se están haciendo dentro de algunos métodos por lo que cuando acceden $saved no contiene los mismos datos.

La solución es hacer $saved una variable estática, sin embargo, no puedo cambiar todas las referencias a $this->saved así que quiero intentar mantener el código idéntico pero hacerlo actuar estático.

Aquí está mi intento de hacer $this->saved llamadas estáticas...

class PropertyTest {
    private $data = array();

    public function __set($name, $value) {
        $this->data[$name] = $value;
    }

    public function __get($name) {
        if (array_key_exists($name, $this->data)) {
            return $this->data[$name];
        }

        return null;
    }

    public function __isset($name) {
        return isset($this->data[$name]);
    }

    public function __unset($name) {
        unset($this->data[$name]);
    }
}

class Common {
    public $saved;
    private static $_instance;

    public function __construct() {
        $this->saved = self::getInstance();
    }

    public static function getInstance() {
        if (self::$_instance === null) {
            self::$_instance = new PropertyTest();
            self::$_instance->foo = array();
        }
        return self::$_instance->foo;
    }
}

Esto no funciona del todo cuando se establece una variable que no parece permanecer estática (caso de prueba a continuación)...

class Template extends Common {

    public function __construct() {
        parent::__construct();
        $this->saved['user_data'] = array('name' => 'bob');
        $user = new User();
    }
}

class User extends Common {

    public function __construct() {
        parent::__construct();
        $this->saved['user_data']['name'] .= " rocks!";
        $this->saved['user_data']['id'] = array(400, 10, 20);
    }
}

$tpl = new Template();
print_r($tpl->saved['user_data']);

$this->saved está vacío cuando User se inicializa y no parece ser la misma variable, la final print_r solo muestra una matriz de nombre => bob.

¿Alguna idea?

preguntado el 22 de mayo de 12 a las 17:05

Me parece que necesitas una variable global. Podría disfrazarlo como un singleton, pero al final es solo una variable global. -

@jeroen, siempre me reía cuando la gente decía lo malvado que era global y cómo debería ser reemplazado por un singleton. Ambos son el mismo mal XD. -

@Xeoncross Muy cierto, aunque todavía tengo que ver un programa/script sin variables globales... -

@fire Me parece que tu principal problema son cosas como $this->data[$name] = $value; en clases que se extienden desde Common. Si usa getters y setters de Common en consecuencia, tendrás mucha libertad para cambiarlo como quieras. -

3 Respuestas

En primer lugar, tengo que decir que, en mi opinión, no es asi de bueno para usar la propiedad de una instancia como propiedad de una clase ($saved no se declara como static pero su valor se comparte con todas las instancias).

Aquí hay una versión funcional http://codepad.org/8hj1MOCT, y aquí está el código comentado. Básicamente, el truco está en usar ambos Interfaz ArrayAccess y patrón singleton.

class Accumulator implements ArrayAccess {

    private $container = array();
    private static $instance = null;

    private function __construct() {
    }

    public function getInstance() {
        if( self::$instance === null ) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public function offsetSet($offset, $value) {
        if (is_null($offset)) {
            $this->container[] = $value;
        } else {
            $this->container[$offset] = $value;
        }
    }

    public function offsetExists($offset) {
        return isset($this->container[$offset]);
    }

    public function offsetUnset($offset) {
        unset($this->container[$offset]);
    }

    public function offsetGet($offset) {
        return isset($this->container[$offset]) ? $this->container[$offset] : null;
    }

}


class Common {
    public $saved = null;


    public function __construct() {
        // initialize the "saved" object's property with the singleton
        // that variable can be used with the array syntax thanks to the ArrayAccess interface
        // so you won't have to modify your actual code
        // but also, since it's an object, this local "$this->saved" is a reference to the singleton object
        // so any change made to "$this->saved" is in reality made into the Accumulator::$instance variable
        $this->saved = Accumulator::getInstance();
    }

    public function setUser($data) {
        $this->saved['user_data'] = $data;
    }

    public function getUser() {
        return $this->saved['user_data'];
    }

}


class Template extends Common {

    // you can redeclare the variable or not. Since the property is inherited, IMO you should not redeclare it, but it works in both cases
    // public $saved = null;

    public function __construct() {
        // maybe we can move this initialization in a method in the parent class and call that method here
        $this->saved = Accumulator::getInstance();
    }

}

contestado el 22 de mayo de 12 a las 19:05

gracias pero sufre el mismo problema que proporcioné en mi último ejemplo de código, míralo en acción aquí codepad.org/VC3HX0lW deberías ver el nombre => bob rocks! y una matriz de id - incendio

Creo que hay una serie de problemas con esta implementación que bien podrían volver a molestarte. Sin embargo, en su implementación actual, está construyendo una nueva instancia (aunque a través de una llamada estática) cada vez.

En su lugar, use getInstance() como su enlace único y haga que su __construct sea privado, ya que solo accederá desde el contexto de la clase Común.

Al igual que:

class Common {
    public $saved;

    private static $_instance;

    private function __construct() {
    }

    public static function getInstance() {
        if (self::$_instance === null) {
            self::$_instance = new self();
            ... any other modifications you want to make ....
        }
        return self::$_instance;
    }
}

Y nunca ejecute parent::_construct(), en lugar de eso, use siempre el método getInstance().

También es posible que desee deshacerse de la idea de extender esta clase única. Este es realmente un antipatrón malo y podría costarle una serie de problemas a largo plazo. En su lugar, simplemente mantenga una Common clase en la que otras clases pueden leer/escribir. Como es un singleton, no necesita preocuparse por la inyección.

contestado el 22 de mayo de 12 a las 18:05

Parece que he resuelto el problema, haciendo $this->saved una referencia a una variable estática funciona...

class Common {
    private static $savedData = array();
    public $saved;

    public function __construct() {
        $this->saved =& self::$savedData;
    }
}

contestado el 23 de mayo de 12 a las 10:05

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.