Правильное расширение ArrayObject в PHP?


Проблема: Я пытаюсь расширить PHP ArrayObject, как показано ниже. К сожалению, я не могу заставить его работать должным образом при настройке многомерных объектов, и вместо этого возникает ошибка, так как у меня включены строгие настройки в PHP. (Error: Strict standards: Creating default object from empty value)

Вопрос: Как я могу изменить свой класс, чтобы автоматически создавать для меня несуществующие уровни?

Код:

$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);
    }
}
Author: Andrejs Cainikovs, 2011-08-31

3 answers

Рассматривая вашу проблему с точки зрения ооп, вы можете создать класс, который моделирует концепцию многомерного объекта.

Решение, которое я публикую, не распространяется на ArrayObject для архивирования целей, которые вы упомянули. Поскольку вы отметили свой вопрос как ооп, я думаю, что важно усилить разделение способа хранения состояния объекта и способа доступа к нему.

Надеюсь, это поможет вам архивировать то, что вам нужно!

Из того, что вы сказали, многомерная объект - это тот, который:

  • обрабатывает несколько уровней вложенной информации
  • он делает это, предоставляя доступ для чтения/записи к информации через свойства
  • ведет себя хорошо при доступе к неопределенным свойствам. Это означает, что, например, вы делаете следующее для пустого экземпляра: $config->database->host = 'localhost' уровни database и host инициализируются автоматически, и host вернет 'localhost' при запросе.
  • в идеале, было бы инициализировано из ассоциативных массивов (потому что вы уже можете анализировать конфигурационные файлы в них)

Предлагаемое Решение

Итак, как можно реализовать эти функции?

Второй вариант прост: использование методов PHP __get и __set. Они будут вызываться всякий раз, когда выполняется чтение/запись в недоступном свойстве (которое не определено в объекте). Хитрость будет заключаться в том, чтобы не объявлять какое-либо свойство и обрабатывать операции свойства с помощью этих методов и сопоставлять имя свойства быть доступным в качестве ключа к ассоциативному массиву, используемому в качестве хранилища. Они будут предоставлять в основном интерфейс для доступа к информации, хранящейся внутри компании.

Для третьего нам нужен способ создания нового уровня вложенности при чтении необъявленного свойства. Ключевым моментом здесь является понимание того, что возвращаемое значение для свойства должно быть многомерным объектом, поэтому из него также могут быть созданы дополнительные уровни вложенности: всякий раз, когда запрашивалось свойство, имя которого отсутствует в внутренний массив, мы свяжем это имя с новым экземпляром MultiDimensionalObject и вернем его. Возвращаемый объект также сможет обрабатывать определенные или неопределенные свойства.

Когда записывается необъявленное свойство, все, что нам нужно сделать, это присвоить его имени значение, указанное во внутреннем массиве.

Четвертый вариант прост (см. Его в реализации __construct). Нам просто нужно убедиться, что мы создаем MultiDimensionalObject, когда значение свойства является массивом.

Наконец, кулак первый: то, как мы обрабатываем вторую и третью функции, позволяет нам читать и записывать свойства (объявленные и необъявленные) на любом уровне вложенности. Вы можете выполнять такие действия, как $config->foo->bar->baz = 'hello' на пустом экземпляре, а затем успешно запрашивать $config->foo->bar->baz.

Важный Обратите внимание, что MultiDimensionalObject вместо вместо сам по себе массив - это , состоящий из массива, позволяющий изменять способ хранения состояния объекта как необходимо.

Реализация

/* 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);
    }
}

Демо-версия

Код: var_dump(новый многомерный объект());

Результат:

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

Код:

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

Результат:

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

Код:

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

Результат:

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

Код:

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

Результат:

admin
 13
Author: nick2083, 2011-09-09 04:06:04

Реализуйте метод offsetGet. Если вы обращаетесь к несуществующему свойству, вы можете создать его по своему усмотрению.

Поскольку вы расширяете объект ArrayObject, вы должны использовать способ массива [] установить или получить.

 1
Author: xdazz, 2011-08-31 09:22:53

Скопировал, вставил ваш код, и он отлично работает в моем тестовом окне PHP (работает на PHP 5.3.6). В нем упоминается предупреждение о строгих стандартах, но оно по-прежнему работает так, как ожидалось. Вот вывод из print_r:

Config Object
(
    [storage:ArrayObject:private] => Array
        (
            [lvl1_0] => 1
            [lvl1_1] => stdClass Object
                (
                    [lvl2] => 1
                )

        )

)

Стоит отметить, что в документах PHP есть комментарий с рекомендациями, связанными с тем, что вы пытаетесь сделать:

Sfinktah в php точка спам-трак точка орг 17 апреля-2011 07:27
Если вы планируете наследовать свой собственный класс от ArrayObject, и если вы хотите сохранить полную функциональность ArrayObject (например, возможность приведения к массиву), необходимо использовать собственное частное свойство ArrayObject "хранилище".

Подробное объяснение приведено выше, но в дополнение к offsetSet, которое у вас есть, и offsetGet, которое упоминает xdazz, вы также должны реализовать offsetExists и offsetUnset. Это не должно иметь никакого отношения к вашей текущей ошибке, но это то, о чем вы должны помнить.

Обновление: вторая половина xdazz имеет ответ на вашу проблему. Если вы обращаетесь к объекту конфигурации в виде массива, он работает без каких-либо ошибок:

$config = new Config;
$config[ 'lvl1_0' ] = true;
$config[ 'lvl1_1' ][ 'lvl2' ] = true;

Вы можете это сделать или по какой-то причине вы ограничены синтаксисом объекта?

 1
Author: Farray, 2011-09-05 18:30:29