Estendendo ArrayObject em PHP corretamente?

Problema: Estou tentando estender ArrayObject do PHP como mostrado abaixo. Infelizmente, não consigo que funcione corretamente ao configurar objects multidimensionais e, em vez disso, um erro é lançado, pois eu tenho as configurações rigorosas ativadas no PHP. ( Error: Strict standards: Creating default object from empty value )

Pergunta: Como posso modificar minha class para criar automaticamente níveis não existentes para mim?

O código:

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

Tomando uma visão mais oop do seu problema, você pode criar uma class que modelo o conceito de um object multidimensional.

A solução im postagem não se estende do ArrayObject para arquivar os objectives que você menciona. Como você marcou sua pergunta como oop, eu acho que é importante reforçar a separação da forma como você armazena o estado de um object de como você acessa.

Espero que isso ajude você a criar o que você precisa!

Pelo que você disse, um object multidimensional é aquele que:

  • lida com vários níveis de informação aninhada
  • faz isso fornecendo access leitura / escrita à informação através de propriedades
  • comporta-se bem quando as propriedades indefinidas são acessadas. Isso significa que, por exemplo, você faz o seguinte em uma instância vazia: $config->database->host = 'localhost' os níveis do database e do host são inicializados automaticamente e o host retornará 'localhost' quando for consultado.
  • idealmente, seria inicializado a partir de matrizes associativas (porque você já pode analisar arquivos de configuração neles)

Solução proposta

Então, como esses resources podem ser implementados?

O segundo é fácil: usando os methods __set e __get do PHP. Aqueles serão chamados sempre que uma leitura / gravação for feita em uma propriedade inacessível (uma que não esteja definida em um object). O truque será, então, não declarar qualquer propriedade e lidar com as operações da propriedade através desses methods e mapear o nome da propriedade associado como chave para uma matriz assosiativa usada como armazenamento. Eles fornecerão basicamente uma interface para acessar informações armazenadas internamente.

Para o terceiro, precisamos de uma maneira de criar um novo nível de nidificação quando uma propriedade não declarada é lida. O ponto chave aqui é perceber que o valor retornado para a propriedade deve ser um object multidimensional, de modo que outros níveis de nidificação podem ser criados a partir dele também: sempre que for pedido uma propriedade cujo nome não está presente na matriz interna, Vamos associar esse nome a uma nova instância do MultiDimensionalObject e devolvê-lo. O object retornado também poderá manipular propriedades definidas ou indefinidas.

Quando uma propriedade não declarada é escrita, tudo o que temos a fazer é atribuir seu nome com o valor fornecido na matriz interna.

O quarto é fácil (veja-o na implementação __construct ). Nós simplesmente precisamos ter certeza de que criamos um MultiDimensionalObject quando o valor de uma propriedade é uma matriz.

Finalmente, o primeiro: a maneira como lidamos com o segundo e terceiro resources nos permite ler e escrever propriedades (declaradas e não declaradas) em qualquer nível de nidificação. Você pode fazer coisas como $config->foo->bar->baz = 'hello' em uma instância vazia e depois perguntar por $config->foo->bar->baz sucesso.

Importante Observe que MultiDimensionalObject vez de se MultiDimensionalObject um array é composto por uma matriz, permitindo que você altere a forma como você armazena o estado do object conforme necessário.

Implementação

 /* 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 */ /* Ie: $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

Código: var_dump (novo MultiDimensionalObject ());

Resultado:

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

Código:

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

Resultado:

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

Código:

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

Resultado:

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

Código:

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

Resultado:

 admin 

Implementar o método offsetGet . Se você estiver acessando uma propriedade não existente, você pode criar uma como desejar.

À medida que você está ampliando ArrayObject, você deve usar a maneira array [] para definir ou obter.

Copiado colou seu código e funciona bem na minha checkbox de teste do PHP (executando o PHP 5.3.6). Ele menciona o aviso Strict Standards, mas ainda funciona como esperado. Aqui está o resultado de print_r:

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

Vale ressaltar que, nos documentos do PHP, há um comentário com orientação relacionada ao que você está tentando fazer:

sfinktah em php dot spamtrak dot org 17-Abr-2011 07:27
Se você planeja derivar sua própria class de ArrayObject e deseja manter a funcionalidade ArrayObject completa (como ser capaz de transmitir para uma matriz), é necessário usar o “armazenamento” de propriedade privada da ArrayObject.

A explicação detalhada está ligada acima, mas, além de offsetSet que você possui e o offsetGet que xdazz menciona, você também deve implementar offsetExists e offsetUnset . Isso não deve ter nada a ver com o seu erro atual, mas é algo que você deveria estar atento.

Atualização: o segundo semestre do xdazz tem a resposta para o seu problema. Se você acessar seu object Config como uma matriz, ele funciona sem erros:

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

Você pode fazer isso ou você está restrito à syntax do object por algum motivo?