PHP – Melhores arrays orientados a objeto

Eu tenho trabalhado no desenvolvimento de software e SDKs em PHP por quase uma década e, através da experiência de suporte para esses desenvolvedores, eu aprendi algo interessante sobre a comunidade PHP em geral. A maioria dos desenvolvedores PHP tem uma compreensão muito boa de tipos nativos (por exemplo, strings, arrays inteiros, booleanos). Como eles são os menores denominadores comuns da linguagem PHP, é geralmente muito fácil para os desenvolvedores entender esses tipos.

Mas no momento em que você introduz conceitos de objetos como tipos de retorno, as pessoas de repente não conseguem descobrir como a coisa funciona. É muito parecido com quando as pessoas se esquecem de como dirigir no instante em que começa a chover. A maioria dos softwares PHP tem nos treinado a esperarmos tipos nativos como respostas, mas se você retornar algo como um objeto SimpleXMLElement ou algum outro tipo de objeto personalizado, a confusão começa!

Interrompendo a insanidade com coleções

Em um esforço para encontrar uma solução melhor para o fiasco array vs objeto, eu comecei a investigar a possibilidade de arrays orientados a objeto em PHP. Dessa forma, você pode retornar um objeto semelhante a um array que se comporta exatamente como um array, mas tem todo o poder de um objeto.

O PHP tem duas classes chamadas ArrayObject e ArrayIterator, que são mais como um apoio do que qualquer coisa útil. O que eles fornecem, no entanto, é uma fundação razoavelmente sólida para a construção de uma classe útil de array orientado a objeto em cima de tudo isso. Essas classes também implementam um conjunto de interfaces que nos dão uma quantidade significativa de funcionalidade relativamente de graça.

Uma vez que a palavra array já possui um significado específico em PHP, vou chamar nossa nova classe de objeto orientados array de classe Collection. O objetivo dessa classe é se comportar de forma idêntica a um array, mas suportar uma variedade de métodos como o tipo que você gostaria de encontrar em linguagens do tipo tudo-é-um-objeto, como Ruby e JavaScript.

Constructor

Vamos começar com a nossa definição de classe e constructor:

class Collection implements IteratorAggregate, ArrayAccess, Countable, Serializable
{
private $collection;
private $iterator;

public function __construct($value = array())
{
$this->collection = new ArrayObject($value, ArrayObject::ARRAY_AS_PROPS);
$this->iterator = $this->collection->getIterator();
}
}

Nota: A interface IteratorAggregate estende a interface Traversable, que habilita foreach através da coleção.

Métodos de interface

Em seguida, precisamos implementar todos os métodos que são definidos pelas interfaces. Estes incluem:

  • currentkeynextrewindseek, e valid a partir da interface Traversable.
  • getIterator a partir da interface IteratorAggregate.
  • offsetExistsoffsetGet offsetSet e offsetUnset a partir da interface ArrayAccess.
  • count a partir da interface Countable.
  • serialize e unserialize a partir da interface Serializable.

Na maior parte, eles podem ser implementados simplesmente chamando os métodos já existentes nas propriedades $this->collection and $this->iterator.

Se você quiser que suas coleções sejam capazes de ser serializadas e não-serializadas, você precisa personalizar a implementação desses métodos. A coisa mais difícil para se observar é o tratamento de casos em que sua coleção contém um elementoSimpleXMLElement. Uma vez que o SimpleXMLElement não pode ser serializado, você vai precisar converter manualmente o objeto para uma string XML em serialização, e reanalisá-lo em não-serialização.

Tipos de conversão

Até agora, você já será capaz de fazer coisas como esta:

$data = new Collection(array(1, 2, 3, 4, 5, 'a', 'b', 'c'));
$numbers = new Collection();
$letters = new Collection();

foreach ($data as $entry)
{
if (is_int($entry))
{
$numbers[] = $entry;
}
elseif (is_string($entry))
{
$letters[] = $entry;
}
}

No entanto, uma vez que as funções do array no PHP exigem que as entradas sejam arrays reais, nós vamos precisar ser capazes de exportar com facilidade a coleção para um array à moda antiga.

public function to_array()
{
return $this->collection->getArrayCopy();
}

Talvez você ainda pretenda adicionar funcionalidade para implementar coisas como to_json(),to_yaml() ou to_object(). Isso é realmente fácil de fazer com as ferramentas encontradas no próprio PHP e com pacotes de terceiros populares (como o Symfony YAML).

Métodos mágicos

O que seria de uma coleção de classe incrível sem um pouco de mágica?

// Support looking up nodes using $obj->key
// Also, call methods without parenthesis: $obj->method
public function __get($name)
{
if (method_exists($this, $name))
{
return $this->$name();
}
elseif ($this->exists($name))
{
return $this[$name];
}

return null;
}

// Support setting values with $obj->key = $value
public function __set($name, $value)
{
if (method_exists($this, $name))
{
return $this->$name($value);
}

$this[$name] = $value;
return $this;
}

// Support cloning this object
public function __clone()
{
$this->collection = clone $this->collection;
$this->iterator = clone $this->iterator;
}

// Support echo $obj
public function __toString()
{
print_r($this->collection->getArrayCopy());
}

E agora?

Então, você montou essa classe. E agora? Bem, agora você tem um objeto que se comporta como um array, que pode fazer todas as coisas que um array é projetado para fazer e tem todas as vantagens de ser um objeto.

Vamos analisar o que funciona e o que não funciona. (Finja que também temos tido tempo para implementar métodos, como first e last).

$array = array(
'key1' => 'value1',
'key2' => 'value2',
'key3' => 'value3',
'key4' => 'value4',
'key5' => 'value5'
);

$array[0]       // Error!
$array[‘key1’]  // “value1”
$array->key1    // Error!
rewind($array)  // “value1”
end($array)     // “value5”
$array->first() // Error!
$array->first   // Error!
$array->last()  // Error!
$array->last    // Error!

array_values($array); // [“value1”, “value2”, “value3”, “value4”, “value5”]

Versus…

$collection = new Collection(array(
'key1' => 'value1',
'key2' => 'value2',
'key3' => 'value3',
'key4' => 'value4',
'key5' => 'value5'
));

$collection[0]       // Error
$collection[‘key1’]  // “value1”
$collection->key1    // “value1”
rewind($collection)  // “value1”
end($collection)     // “value5”
$collection->first() // “value1”
$collection->first   // “value1″
$collection->last()  //” value5″
$collection->last    // “value5”

array_values($collection->to_array); // [“value1”, “value2”, “value3”, “value4”, “value5”]

Agora você pode acessar as chaves da coleção como índices (como arrays) ou propriedades (como objetos). A vantagem aqui é que você pode estender sua classe ainda mais com a implementação de métodos como pushpopfirstlastflattenslicesort_byreduceeach,mapminmaxeveryanygrepungrep, e mais, tudo manipulando o valor de $this->collection. Você não pode fazer isso com um array à moda antiga!

Deixe um comentário