fire
fire

Reputation: 21531

Creating a static array without changing thousands of lines of code

We have a class that holds a public array called $saved that contains lots of data required to share between methods (example below)...

class Common {
    public $saved = array();

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

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

There are literally thousands of lines of code that work like this.

The problem is that new instance of classes that extend Common are being made within some methods so when they access $saved it does not hold the same data.

The solution is to make $saved a static variable, however I can't change all of the references to $this->saved so I want to try and keep the code identical but make it act static.

Here is my attempt to make $this->saved calls static...

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;
    }
}

This doesn't quite work when setting a variable it doesn't seem to stay static (test case below)...

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 is empty when User gets initialized and doesn't seem to be the same variable, the final print_r only shows an array of name => bob.

Any ideas?

Upvotes: 3

Views: 200

Answers (3)

fire
fire

Reputation: 21531

I seem to have solved the problem, by making $this->saved a reference to a static variable it works...

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

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

Upvotes: 0

pomeh
pomeh

Reputation: 4912

First of all, I have to say that, IMO, it is not that good to use an instance's property as a class's property ($saved is not declared as static but its value is shared with all instance).

Here is a working version http://codepad.org/8hj1MOCT, and here is the commented code. Basically, the trick is located in using both ArrayAccess interface and the singleton pattern.

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();
    }

}

Upvotes: 1

Lee Davis
Lee Davis

Reputation: 4756

I think there are a number of issues with this implementation that could well come back to bite you. However, in your current implementation your contructing a new instance (albeit through a static call) every time.

Instead use getInstance() as your singleton hook, and make your __construct private, as you'll only be accessing it from with the context of the Common class.

Like so:

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;
    }
}

And don't ever run parent::_construct(), instead always use the getInstance() method.

You might also want to ditch the idea of extending this singleton class. This is really a bad antipattern and could cost you a number of issues in the long run. Instead just maintain a Common class that other classes can read / write to. As its a singleton you don't need to worry about injection.

Upvotes: 0

Related Questions