Правильно ли расширять ArrayObject в PHP? - PullRequest
7 голосов
/ 31 августа 2011

Проблема: Я пытаюсь расширить 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);
    }
}

Ответы [ 3 ]

13 голосов
/ 09 сентября 2011

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

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

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

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

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

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

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

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

Для третьего нам нужен способ создания нового уровня вложенности при получении необъявленного свойства.Ключевым моментом здесь является понимание того, что возвращаемое значение для свойства должно быть многомерным объектом, поэтому из него можно также создавать дополнительные уровни вложенности: всякий раз, когда нас просят о свойстве, имя которого отсутствует во внутреннем массиве,мы свяжем это имя с новым экземпляром 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
1 голос
/ 04 сентября 2011

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

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

        )

)

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

сфинктах на 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;

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

1 голос
/ 31 августа 2011

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

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

...