Collections no PHP – Array com super poderes

Collections PHP
Tempo de leitura 7 minutos

Quem nunca precisou ficar escrevendo linhas complicadas de código para lidar com arrays que atire a primeira pedra, mas saiba que com collections esses dias acabaram.

Neste artigo quero mostrar como lidar com arrays de uma forma poderosa e organizada enquanto conhecemos recursos nativos do PHP. Em uma segunda parte, eu quero mostrar uma biblioteca poderosíssima que vai te ajudar a lidar com coleções/collections.

Se você quiser saber quando novos artigos forem lançados, se cadastra na newsletter.

ArrayAccess – interface do PHP para lidar com Collections

Eu considero arrays um recurso poderoso do PHP ao tempo que a linguagem oferece MUITAS formas de manipulá-lo, o que quero dizer é que arrays são simples, mas poderosos.

Um recurso focado em Orientação a Objetos que ajuda MUITO na hora de manipular arrays é a interface nativa ArrayAccess, com ela podemos trabalhar uma classe como se fosse um array, porém com mais controle.

A interface ArrayAccess exige que quatro métodos sejam incluídos na classe, cada um responsável por manipular uma ação de um array:

ArrayAccess {
    // verificar se o item existe no array
    abstract public offsetExists ( mixed $offset ) : boolean

    // ler um item do array
    abstract public offsetGet ( mixed $offset ) : mixed

    // adicionar um item no array
    abstract public offsetSet ( mixed $offset , mixed $value ) : void

    // remover um item do array
    abstract public offsetUnset ( mixed $offset ) : void
}

Para o exemplo que deste artigo, simplesmente criei um arquivo composer.json com o comando composer init e registrei um namespace PSR-4 chamado App que aponta para o diretório src.

Se você ficou em dúvidas sobre esse passo a passo do Composer vou recomandar um artigo pra você, embora seja interessante ler desde o começo, o ponto exato que interessa de verdade é a partir do tópico Colocando a mão na massa – iniciando projeto PHP.

Projeto PHP do zero, vale a pena? – Série PHP sem framework – parte 1

O meu composer.json ficou assim:

{
    "name": "erikfig/php-collections",
    "description": "",
    "authors": [
        {
            "name": "Erik Figueiredo",
            "email": "erik.figueiredo@gmail.com"
        }
    ],
    "autoload" : {
        "psr-4": {
            "App\\": "src"
        }
    },
    "require": {}
}

Na sequência, basta rodar o comando composer dump ou composer install, deixando tudo pronto.

Crie o diretório src e um arquivo chamado Collection1.php denro dele, agora crie outro arquivo chamado bootstrap.php na raiz do projeto e nossa extrutura está pronta!

estrutura do projeto collections php
Estrutura base do projeto/exemplo

Na nossa próxima etapa vamos:

  • Criar uma classe
  • Implementar a interface ArrayAccess
  • Testar
  • Adicionar recursos interessantes

Primeiramente iremos criar a classe, acredito que não tenha novidades pra você, mas se tiver pergunte nos comentários ou veja o artigo que eu JÁ recomendei.

Abra o arquivo Collection1.php e adicione o seguinte:

<?php

namespace App;

use ArrayAccess;

class Collection1 implements ArrayAccess
{
    private $data;

    // Não é necessário para ArrayAccess funcionar
    // adicionei para facilitar o uso com um array
    // já existente
    public function __construct(array $data = [])
    {
        $this->data = $data;
    }

    // OBRIGATÓRIO para a interface ArrayAccess
    public function offsetExists($offset) :bool
    {
        return isset($this->data[$offset]);
    }

    // OBRIGATÓRIO para a interface ArrayAccess
    public function offsetGet($offset)
    {
        return $this->data[$offset] ?? null;
    }

    // OBRIGATÓRIO para a interface ArrayAccess
    public function offsetSet($offset, $value) :void
    {
        $this->data[$offset] = $value;
    }

    // OBRIGATÓRIO para a interface ArrayAccess
    public function offsetUnset($offset) :void
    {
        unset($this->data[$offset]);
    }
}

Pronto, nossa classe está pronta, vamos abrir o bootstrap.php para testar:

<?php

// carrego o autoload do Composer
// para carregamento das classes
require __DIR__ . '/vendor/autoload.php';

// um array qualquer
$names = [
    'Erik',
    'Leticia'
];

// instâncio minha classe e passo o array
// como parâmetro
$collection = new App\Collection1($names);

// uso o método offsetSet para adicionar
// um item ao array
$collection->offsetSet(null, 'Geovana');

// mas eu posso usar a classe como um array também
$collection[] = 'Fernanda';

// exibindo o resultado
var_dump($collection);
var_dump($collection[0]);

Rode o arquivo bootstrap.php no navegador ou com o comando php bootstrap.php (via CMD/Terminal).

Você vai notar que eu usei null no offsetSet e que o efeito é o mesmo que $collection[], então o último nome substituirá o anterior. Em um array “de verdade” isso não deveria acontecer, o array INCREMENTARIA a chave em 1 e adicionaria o novo valor, podemos obter esse efeito alterando o método offsetSet em Collection1.

    public function offsetSet($offset, $value) :void
    {
        // agora eu verifico se a chave veio vazia
        if ($offset === null) {
            // se veio, eu adiciono o valor correto
            $offset = count($this->data);
        }
        $this->data[$offset] = $value;
    }

Claro que é uma solução bem simples, mas por enquanto vai servir bem, veremos algo mais “definitivo” daqui a pouco.

Se você testar rodando o arquivo bootstrap.php, vai ver que os nomes estão todos lá. Todavia não precisamos ficar presos nisso, use sua imaginação (essa foi boa).

Eu vou dar dois exemplos, um para ordenação e outro para “converter” o array/classe em uma string quando for usada como tal.

Adicione estes dois métodos ao seu Collection1.php.

    // ordena o array em ordem alfabética
    public function order() :void
    {
        sort($this->data);
    }

    // método mágico, permite que uma classe
    // seja usada como string
    public function __toString() :string
    {
        return json_encode($this->data);
    }

O arquivo final:

<?php

namespace App;

use ArrayAccess;

class Collection1 implements ArrayAccess
{
    private $data;

    // Não é necessário para ArrayAccess funcionar
    // adicionei para facilitar o uso com um array
    // já existente
    public function __construct(array $data = [])
    {
        $this->data = $data;
    }

    // OBRIGATÓRIO para a interface ArrayAccess
    public function offsetExists($offset) :bool
    {
        return isset($this->data[$offset]);
    }

    // OBRIGATÓRIO para a interface ArrayAccess
    public function offsetGet($offset)
    {
        return $this->data[$offset] ?? null;
    }

    // OBRIGATÓRIO para a interface ArrayAccess
    public function offsetSet($offset, $value) :void
    {
        if ($offset === null) {
            $offset = count($this->data);
        }
        $this->data[$offset] = $value;
    }

    // OBRIGATÓRIO para a interface ArrayAccess
    public function offsetUnset($offset) :void
    {
        unset($this->data[$offset]);
    }

    // ordena o array em ordem alfabética
    public function order() :void
    {
        sort($this->data);
    }

    // método mágico, permite que uma classe
    // seja usada como string
    public function __toString() :string
    {
        return json_encode($this->data);
    }
}

No fim do bootstrap.php, em vez de dar var_dump vou substitui para:

$collection->order();

echo $collection;

O arquivo bootstrap.php final:

<?php

// carrego o autoload do Composer
// para carregamento das classes
require __DIR__ . '/vendor/autoload.php';

// um array qualquer
$names = [
    'Erik',
    'Leticia'
];

// instancio minha classe e passo o array
// como parâmetro
$collection = new App\Collection1($names);

// uso o método offsetSet para adicionar
// um item ao array
$collection->offsetSet(null, 'Geovana');

// mas eu posso usar a classe como um array também
$collection[] = 'Fernanda';

// em vez do var_dump
$collection->order();
echo $collection;

O resultado será um JSON ordenado em ordem alfabética (independente da ordem em que os nomes forem incluídos) convertido automáticamente pela nossa collection.

ArrayObject – mais que uma implementação do ArrayAccess.

Indo além do que vimos, existe outro recurso nativo que é “MUITO mais completo que o ArrayAccess”, uma classe PRONTA para você usar, ela se chama ArrayObject.

A classe ArrayObject já é uma implementação PRONTA da interface ArrayAccess, mas além dela também implementa as interfaces IteratorAggregate, Traversable e Countable.

Para simplificar este artigo e eu não ter que escrever um curso completo aqui, as interfaces acima permitem (em uma descrição MUITO simplificada):

Claro que cada interface faz mais que isso, de uma olhada nos links que adicionei nos nomes da lista, além disso vou deixar mais dois links úteis:

Aqui um exemplo simples de utilização da classe ArrayObject:

<?php

// um array qualquer
$names = [
    'Erik',
    'Leticia'
];

$collection = new ArrayObject($names);
$collection->offsetSet(null, 'Geovana');
$collection[] = 'Fernanda';
$collection->asort();

echo json_encode($collection);

Você pode notar que a classe ArrayObject não implementa o método mágico __toString, você ainda pode criar uma classe que herde a ArrayObject e adicionar seus próprios recursos.

Eu criei a Collection2 ao lado da Collection1:

<?php

namespace App;

use ArrayObject;

class Collection2 extends ArrayObject
{
    public function __toString() :string
    {
        return json_encode($this->getArrayCopy());
    }
}

E o bootstrap.php, agora sem o json_encode.

<?php

// carrego o autoload do Composer
// para carregamento das classes
require __DIR__ . '/vendor/autoload.php';

// um array qualquer
$names = [
    'Erik',
    'Leticia'
];

$collection = new App\Collection2($names);
$collection->offsetSet(null, 'Geovana');
$collection[] = 'Fernanda';
$collection->asort();

echo $collection;

Só o PHP com ArrayAccess e ArrayObject já são incríveis, imagina se pudessemos fazer mais.

E podemos…

Gerenciando arrays com a Illuminate\Support\Collections do Laravel.

Eu poderia ter vindo direto para este tópico, ensinado a instalar o Illuminate\Support\Collections e resolver todos os seus problemas de uma vez, mas a verdade é que essa base é importante, conhecer os recursos nativos é MUITO importante, na hora certa você vai saber quando usar ArrayObject ou Illuminate\Support\Collections.

O Illuminate\Support\Collections é o mesmo pacote usado no Laravel para gerenciar coleções de dados e possui, no momento em que eu escrevo, 116 métodos disponíveis para manipular os itens das coleções. Da pra fazer MUITO com ele!

Neste link você encontra a documentação oficial e todos os recursos disponíveis:

https://laravel.com/docs/6.x/collections

Para instalar no seu projeto é bem simples, basta usar o Composer.

composer require illuminate/support

O pacote Illuminate\Support traz outros recursos interessantes, como o Illuminate\Support\Str, que lida com strings, mas esse assunto é para outro dia.

Agora que já temos instalada a biblioteca, basta usar a classe Illuminate\Support\Collection:

<?php

// carrego o autoload do Composer
// para carregamento das classes
require __DIR__ . '/vendor/autoload.php';

// um array qualquer
$names = [
    'Erik',
    'Leticia'
];

$collection = Illuminate\Support\Collection($names);
$collection->push('Geovana');
$collection[] = 'Fernanda';
$collection = $collection->sort();

echo $collection;

Sinceramente, prefiro usar o helper collect, é mais simples e prático:

<?php

require __DIR__ . '/vendor/autoload.php';

// um array qualquer
$names = [
    'Erik',
    'Leticia'
];

$collection = collect($names);
$collection->push('Geovana');
$collection[] = 'Fernanda';
$collection = $collection->sort();

echo $collection;

O helper collect é só um atalho para a classe Illuminate\Support\Collection, o resultado é melhor e não existe um mais indicado, veja o que você mais gosta.

Uma dica é que a classe Illuminate\Support\Collection implementa a interface ArrayAccess, então o método offsetSet e todos os outros 3 também podem ser usados.

Como disse acima, esta classe tem MUITOS recursos e praticamente pode lidar com tudo o que você imaginar, mas se você conseguir pensar em algo que ela não suporte (mesmo ela tendo MAIS DE 100 features), basta adicionar o recurso:

<?php

require __DIR__ . '/vendor/autoload.php';

// um array qualquer
$names = [
    'Erik',
    'Leticia'
];

// adiciono um novo recurso
Illuminate\Support\Collection::macro('mudaSobrenome', function () {
    // puso o próprio recurso map para adicionar o sobrenome
    return $this->map(function ($value) {
        return $value . ' Figueiredo';
    });
});

$collection = collect($names);
$collection->push('Geovana');
$collection[] = 'Fernanda';
// depois de ordenar, executo meu novo método
// usei interface fluente (-> depois do método)
// mas poderia ter feito assim: $collection->mudaSobrenome()
$collection = $collection->sort()->mudaSobrenome();

echo $collection;

// o resultado será:
// {"0":"Erik Figueiredo","3":"Fernanda Figueiredo","2":"Geovana Figueiredo","1":"Leticia Figueiredo"}

Simples e poderoso, do jeito que eu gosto.

Se você gostou da dica, se inscreve na newsletter pra não perder outras, em breve vou falar de Flutter, fora que tem muito mais Laravel por vir.

Autor: Erik Figueiredo

Músico, gamer amador, tutor de programação, desenvolvedor freelancer full cycle, com foco em PHP (Laravel e CakePHP), Javascript (Front e Node.js), Dart (Front e Flutter) e infra.

4 comentários em “Collections no PHP – Array com super poderes”

  1. Erik, os artigos estão muito bons. Parabéns pelo conteúdo e #obrigadoPorCompartilhar.

    Já me cadastrei na sua lista para receber novidades.

    Abraço.

  2. Cara, muito bom. Você acertou em cheio, acredito que os desenvolvedores em geral desconhecem a maioria das ferramentas que o PHP nos traz por padrão e muita vez recorrem a bibliotecas e/ou implementações de algo que já existe nativo.

    PS: As vezes eu também me incluo nessa lista de desenvolvedores.

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *