Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extending ArrayObject in PHP properly?

Problem: I am trying to extend PHP's ArrayObject as shown below. Unfortunately I can't get it to work properly when setting multi-dimensional objects and instead an error thrown as I have the strict settings enabled in PHP. (Error: Strict standards: Creating default object from empty value)

Question: How can I modify my class to automatically create non-existing levels for me?

The code:

$config = new Config;
$config->lvl1_0 = true; // Works
$config->lvl1_1->lvl2 = true; // Throws error as "lvl1" isn't set already

class Config extends ArrayObject
{
    function __construct() {
        parent::__construct(array(), self::ARRAY_AS_PROPS);
    }

    public function offsetSet($k, $v) {
        $v = is_array($v) ? new self($v) : $v;
        return parent::offsetSet($k, $v);
    }
}
like image 365
Industrial Avatar asked Aug 31 '11 08:08

Industrial


People also ask

How do you call an array of objects in PHP?

If it is called an array or object depends on the outermost part of your variable. So [new StdClass] is an array even if it has (nested) objects inside of it and $object->property = array(); is an object even if it has (nested) arrays inside. And if you are not sure if you have an object or array, just use gettype() .

How do you declare an array in PHP?

An array can be created using the array() language construct. It takes any number of comma-separated key => value pairs as arguments. The comma after the last array element is optional and can be omitted. This is usually done for single-line arrays, i.e. array(1, 2) is preferred over array(1, 2, ) .

How do I loop through an array of objects in PHP?

Use PHP foreach loop to loop through Array of Objects The PHP foreach loop is a way to iterate through an array of objects. This loop will take each object in the array and perform the specified operation on it. The below PHP code example shows how to loop through an array of objects using the foreach loop.

How do you create a new object in PHP?

To create a new object in PHP, you must use the new keyword.


2 Answers

Taking a more oop view of your issue, you can create a class that models the concept of an multi-dimensional object.

The solution im posting doesn't extends from ArrayObject to achieve the goals you mention. As you tagged your question as oop, i think it´s important to reinforce the separation the way you store an object's state from how do you access it.

Hope this will help you achieve what you need!

From what you said, an multi-dimensional object is one that:

  • handles multiple levels of nested information
  • it does so by providing reading/writing access to the information via properties
  • behaves nicely when undefined properties are accessed. This means that, for example, you do the following on an empty instance: $config->database->host = 'localhost' the database and host levels are initialized automatically, and host will return 'localhost' when queried.
  • ideally, would be initialized from an associative arrays (because you can already parse config files into them)

Proposed Solution

So, how can those features be implemented?

The second one is easy: using PHP's __get and __set methods. Those will get called whenever an read/write is beign done on an inaccessible property (one that's not defined in an object). The trick will be then not to declare any property and handle propertie's operations through those methods and map the property name being accessed as a key to an associative array used as storage. They'll provide basically an interface for accessing information stored internally.

For the third one, we need a way to create a new nesting level when a undeclared property is read. The key point here is realizing that the returned value for the property must be an multi-dimensional object so further levels of nesting can be created from it also: whenever we´re asked for a property whose name is not present in the internal array, we´ll associate that name with a new instance of MultiDimensionalObject and return it. The returned object will be able to handle defined or undefined properties too.

When an undeclared property is written, all we have to do is assign it's name with the value provided in the internal array.

The fourth one is easy (see it on __construct implementation). We just have to make sure that we create an MultiDimensionalObject when a property's value is an array.

Finally, the fist one: the way we handle the second and third features allows us to read and write properties (declared and undeclared) in any level of nesting. You can do things like $config->foo->bar->baz = 'hello' on an empty instance and then query for $config->foo->bar->baz successfully.

Important Notice that MultiDimensionalObject instead of beign itself an array is it composed with an array, letting you change the way you store the object's state as needed.

Implementation

/* Provides an easy to use interface for reading/writing associative array based information */
/* by exposing properties that represents each key of the array */
class MultiDimensionalObject {

    /* Keeps the state of each property  */
    private $properties;
    
    /* Creates a new MultiDimensionalObject instance initialized with $properties */
    public function __construct($properties = array()) {
        $this->properties = array();
        $this->populate($properties);
    }
    
    /* Creates properties for this instance whose names/contents are defined by the keys/values in the $properties associative array */
    private function populate($properties) {
        foreach($properties as $name => $value) {
            $this->create_property($name, $value);
        }
    }
    
    /* Creates a new property or overrides an existing one using $name as property name and $value as its value */
    private function create_property($name, $value) {
        $this->properties[$name] = is_array($value) ? $this->create_complex_property($value)
                                                    : $this->create_simple_property($value);
    }
    
    /* Creates a new complex property. Complex properties are created from arrays and are represented by instances of MultiDimensionalObject */
    private function create_complex_property($value = array()){
        return new MultiDimensionalObject($value);
    }
    
    /* Creates a simple property. Simple properties are the ones that are not arrays: they can be strings, bools, objects, etc. */
    private function create_simple_property($value) {
        return $value;
    }
    
    /* Gets the value of the property named $name */
    /* If $name does not exists, it is initilialized with an empty instance of MultiDimensionalObject before returning it */
    /* By using this technique, we can initialize nested properties even if the path to them don't exist */
    /* I.e.: $config->foo
                    - property doesn't exists, it is initialized to an instance of MultiDimensionalObject and returned
             
             $config->foo->bar = "hello";
                    - as explained before, doesn't exists, it is initialized to an instance of MultiDimensionalObject and returned.
                    - when set to "hello"; bar becomes a string (it is no longer an MultiDimensionalObject instance) */    
    public function __get($name) {
        $this->create_property_if_not_exists($name);
        return $this->properties[$name];
    }
    
    private function create_property_if_not_exists($name) {
        if (array_key_exists($name, $this->properties)) return;
        $this->create_property($name, array());
    }
    
    public function __set($name, $value) {
        $this->create_property($name, $value);
    }
}

Demo

Code:

var_dump(new MultiDimensionalObject());

Result:

object(MultiDimensionalObject)[1]
    private 'properties' => 
        array
            empty

Code:

$data = array( 'database' => array ( 'host' => 'localhost' ) );
$config = new MultiDimensionalObject($data);        
var_dump($config->database);

Result:

object(MultiDimensionalObject)[2]
    private 'properties' => 
        array
            'host' => string 'localhost' (length=9)

Code:

$config->database->credentials->username = "admin";
$config->database->credentials->password = "pass";
var_dump($config->database->credentials);

Result:

object(MultiDimensionalObject)[3]
    private 'properties' => 
        array
          'username' => string 'admin' (length=5)
          'password' => string 'pass' (length=4)

Code:

$config->database->credentials->username;

Result:

admin
like image 68
nick2083 Avatar answered Nov 03 '22 04:11

nick2083


Implement the offsetGet method. If you are accessing a non exist property, you can create one as you like.

As you are extend ArrayObject, you should use the array way [] to set or get.

like image 38
xdazz Avatar answered Nov 03 '22 04:11

xdazz