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

Проблема: я пытаюсь расширить ArrayObject PHP, как показано ниже. К сожалению, я не могу заставить его работать должным образом при настройке многомерных объектов, и вместо этого возникает ошибка, поскольку у меня включены строгие настройки в 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);
    }
}

person Industrial    schedule 31.08.2011    source источник
comment
Какую версию PHP вы используете?   -  person Farray    schedule 04.09.2011
comment
Привет Индустриал! Вам действительно нужно, чтобы ваш Config класс был специализацией ArrayObject, или вам нужны только средства хранения информации, которые он предоставляет?   -  person nick2083    schedule 07.09.2011
comment
Привет Ник! Нет, после этого вопроса я остановился на реализации ArrayObject: способ изменения многомерного массива"> stackoverflow.com/questions/7202784/   -  person Industrial    schedule 07.09.2011


Ответы (3)


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

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

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

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

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

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

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

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

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

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

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

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

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

Реализация

/* 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(new MultiDimensionalObject());

Результат:

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
person nick2083    schedule 09.09.2011

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

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

person xdazz    schedule 31.08.2011

Скопировал и вставил ваш код, и он отлично работает в моем тестовом блоке 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 dot spamtrak dot org 17-Apr-2011 07: 27
Если вы планируете получить свой собственный класс от ArrayObject и хотите поддерживать полную функциональность ArrayObject (например, возможность приведения к массиву), необходимо использовать собственный частный класс ArrayObject. свойство «хранение».

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

Обновление: у второй половины xdazz есть ответ на вашу проблему. Если вы обращаетесь к своему объекту Config как к массиву, он работает без ошибок:

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

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

person Farray    schedule 04.09.2011
comment
Привет Фаррей! Я могу получить к нему доступ как к массиву, хотя немного грустно, что я не могу одновременно читать и писать в класс ArrayObject. - person Industrial; 06.09.2011
comment
@Industrial К сожалению (для вашего случая), он работает по назначению. Вы создаете объект из пустого значения, поэтому он говорит вам об этом. Вы можете скрыть это, используя ini_set('display_errors', 0); или отключив строгие отчеты об ошибках error_reporting(E_ALL); Есть ли один из этих вариантов? - person Farray; 06.09.2011