Laravel Collections & Eloquent ORM, uma breve introdução (Parte 1)
O Laravel é um framework PHP popular, robusto e completo. Dentre as suas facilidades e características se encontram as collections.
Segundo a própria documentação do Laravel, em tradução livre
“A
Illuminate\Support\Collection
é uma classe que provê um wrapper fluente e conveniente para trabalhar com arrays de dados.”
O funcionamento das collections do Laravel lembra em grande parte os métodos disponibilizados pela classe Array do Javascript. Em termos de quantidade, porém, eles estão mais próximos dos métodos disponibilizados pela biblioteca lodash. Eles são baseados no encadeamento de métodos, em que cada método na cadeia recebe uma função de callback e retorna, na maior parte dos casos, uma nova collection, de modo que a collection original permanece imutável.
Também vale ressaltar que o PHP disponibiliza uma série de métodos de array bem diversificada, e para parte dos métodos das collections do Laravel há uma contraparte nativa do próprio PHP. A vantagem, porém, no uso das collections, dentre outras, é a forma como elas são utilizadas com outras partes do próprio framework, como é o caso dos resultados de consultas em bancos de dados utilizando as facilidades disponibilizadas pelo Laravel — algo que será discutido na parte 2 desta séries de posts.
O Laravel disponibiliza um helper para criação de collections, a partir de arrays PHP, o collect
. Com ele, criar uma nova collection é tão simples como executar as linhas abaixo.
$bronze = collect(['Seya', 'Shiryu', 'Ikki', 'Hyoga', 'Shun', 'Aldebaran']);
Como exemplo prático do poder das collections, o código abaixo elimina o elemento ‘Aldebaran’ e transforma para caixa alta o texto dos itens restantes.
$bronzeDeFato = $bronze->reject(function ($cavaleiro) {
return $cavaleiro === 'Aldebaran';
})->map(function($cavaleiro) {
return strtoupper($cavaleiro);
});
Realizar um dump da variável $bronzeDeFato
traz o resultado:
Illuminate\Support\Collection {#624
#items: array:5 [
0 => "SEYA"
1 => "SHIRYU"
2 => "IKKI"
3 => "HYOGA"
4 => "SHUN"
]
}
Na versão 5.6 do Laravel, a classe Illuminate\Support\Collection
disponibiliza 100 métodos. Uma abordagem minuciosa de cada um deles está além dos propósitos deste post, e para isso serve a documentação. Abordaremos portanto alguns dos que, em minha humilde experiência com o framework, mais utilizo/utilizei. Cada um deles será discutido em uma seção específica, são eles:
each
filter
first
has
keyBy
map
pluck
reduce
each
O método each
, como o próprio nome denota, permite iterar pelos elementos de uma determinada collection. Ele recebe como argumento um callback de assinatura function ($item, $key)
, em que $item
é o elemento da collection sendo percorrido e $key
é a sua chave (opcional), que no caso de um array simples redunda no índice do elemento na collection. Um exemplo de sua utilização é mostrado abaixo.
$bronze = collect([
[
'name' => 'Seya',
'constellation' => 'Pegasu',
], [
'name' => 'Shiryu',
'constellation' => 'Dragão',
], [
'name' => 'Ikki',
'constellation' => 'Fênix',
], [
'name' => 'Hyoga',
'constellation' => 'Cisne',
], [
'name' => 'Shun',
'constellation' => 'Ândromeda',
],
]);$bronze->each(function ($cavaleiro){
echo 'Eu sou o '
. $cavaleiro['name']
. ' de '
. $cavaleiro['constellation']
. '!!!!'
. PHP_EOL;
});
Esse código produz a saída mostrada abaixo.
Eu sou o Seya de Pegasu!!!!
Eu sou o Shiryu de Dragão!!!!
Eu sou o Ikki de Fênix!!!!
Eu sou o Hyoga de Cisne!!!!
Eu sou o Shun de Ândromeda!!!!
Vale ressaltar que um código que itera sobre um array $bronze
, com os mesmo elementos da collection mostrada — ou até mesmo a utilização de um foreach
nos elementos dessa collection — , produziria o mesmo resultado, como é o caso do seguinte código:
foreach ($bronze as $cavaleiro) {
echo 'Eu sou o '
. $cavaleiro['name']
. ' de '
. $cavaleiro['constellation']
. '!!!!'
. PHP_EOL;
}
A vantagem da utilização do each
ou de outros métodos de collections em contextos semelhantes é a interface fluente da classe Illuminate\Support\Collection
, que permite o encadeamento de diversos métodos, o que deve se tornar evidente nas próximas discussões.
filter
O método filter
permite filtrar todos os elementos de uma collection que satisfazem um determinado predicado. Por exemplo, é possível eliminar todas as fake news de um conjunto de posts de uma determinado portal/rede social, como mostra o poderoso exemplo abaixo.
$news = collect([
[
'name' => 'Fake 1',
'type' => 'fake_new'
], [
'name' => 'Fake 2',
'type' => 'fake_new'
], [
'name' => 'Fake 3',
'type' => 'fake_new'
], [
'name' => 'Fake 4',
'type' => 'fake_new'
], [
'name' => 'True new',
'type' => 'true_new'
],
]);$news->filter(function ($new) {
return $new['type'] !== 'fake_new';
})->each(function ($new) {
echo 'Temos uma notícia verdadeira:'
. PHP_EOL
. ' '
. $new['name']
. PHP_EOL;
});
O resultado da execução do código acima é mostrado abaixo.
Temos uma notícia verdadeira:
True new
Não só foram filtradas todas as fake news como foi impressa a única notícia verdadeira, através do método each
encadeado ao resultado do filtro.
Caso não tenha percebido, filtramos 4/5 (ou 80%) de todas as notícias disponíveis. Trata-se de um feito e tanto! Principalmente na época em que vivemos.
first
Trata-se de um fato curioso este ser o terceiro método discutido. O first
é talvez um dos métodos mais intuitivos apresentados aqui, com exceção, talvez, do isEmpty
e do seu irmão, o isNotEmpty
. Como esperado, ele retorna o primeiro elemento de uma collection. Um exemplo de sua utilização é mostrado abaixo.
$collection = collect(['Último', 'Segundo', 'Primeiro']);echo 'O '
. $collection->first() . ' é o primeiro!!!'
. PHP_EOL;
Que produz como saída:
O Último é o primeiro!!
has
O método has
permite a verificação da existência de uma determinada chave em uma collection. Adentrando mais uma vez no nosso conjunto de exemplos poderosos, o código abaixo busca pela existência da chave para o sucesso.
$chaves = collect([
'chave0' => 'valor bacana',
'chave1' => 'valor mais bacana isso',
'para o sucesso' => 'é isso que queremos buscar!',
]);if ($chaves->has('para o sucesso')) {
echo 'A CHAVE PARA O SUCESSO FOI ENCONTRADA!' . PHP_EOL;
}
O código acima imprime como saída o resultado abaixo.
A CHAVE PARA O SUCESSO FOI ENCONTRADA!
O fato da chave para o sucesso estar associada a uma collection, no Laravel, não é coincidência.
keyBy
O método keyBy
permite trocar as chaves de uma determinada collection por um campo específico dos elementos desta mesma collection. Por exemplo, é possível, propositalmente, trocar os pés pelas mãos, como feito no código abaixo.
$byFoot = collect([
'pé direito' => [
'pé' => 'pé direito',
'mão' => 'mão direita',
],
'pé esquerdo' => [
'pé' => 'pé esquerdo',
'mão' => 'mão esquerda',
],
);$byHand = $handByFoot->keyBy('mão');
um dump na variável $byHand
produz como saída o resultado abaixo.
Illuminate\Support\Collection {#624
#items: array:2 [
"mão direita" => array:2 [
"pé" => "pé direito"
"mão" => "mão direita"
]
"mão esquerda" => array:2 [
"pé" => "pé esquerdo"
"mão" => "mão esquerda"
]
]
}
Caso esteja se perguntando “o que aconteceria, caso houvessem chaves repetidas?”, a resposta é que nesse caso o Laravel utiliza o úlitmo valor associado à chave repetida.
map
Chegamos a um dos métodos mais interessantes das collections. O map
permite transformar os elementos de uma determinada collection, arbitrariamente, com base em um callback. Por exemplo, podemos transformar qualquer elemento em ouro! Isso com as simples linhas do exemplo abaixo, capaz de deixar qualquer alquimista de boca aberta.
$elements = collect([
'Cobre',
'Chumbo',
'Âmbar',
'ELEMENTO X',
]);$ouro = $elements->map(function ($element) {
return [
'to' => 'ouro',
'from' => $element,
];
})->each(function ($newElement) {
echo 'Criamos '
. $newElement['to']
. ' a partir do '
. $newElement['from']
. PHP_EOL;
});
Esse código produz a saída abaixo.
Criamos ouro a partir do Cobre
Criamos ouro a partir do Chumbo Criamos ouro a partir do Âmbar
Criamos ouro a partir do ELEMENTO X
Um dump na collection $ouro
produz a saída mostrada abaixo.
Illuminate\Support\Collection {#624
#items: array:4 [
0 => array:2 [
"to" => "ouro"
"from" => "Cobre"
]
1 => array:2 [
"to" => "ouro"
"from" => "Chumbo"
]
2 => array:2 [
"to" => "ouro"
"from" => "Âmbar"
]
3 => array:2 [
"to" => "ouro"
"from" => "ELEMENTO X"
]
]
}
pluck
O método pluck
permite extrair de uma collection todos os valores de um determinado atributo. Utilizando mais uma vez a collection $bronze
utilizada na discussão do método each
, podemos extrair o nome de todos os cavaleiros de bronze, como mostra o exemplo abaixo.
$nomes = $bronze->pluck('name');
um dump $nomes
produz o resultado abaixo.
Illuminate\Support\Collection {#623
#items: array:5 [
0 => "Seya"
1 => "Shiryu"
2 => "Ikki"
3 => "Hyoga"
4 => "Shun"
]
}
Alternativamente, pode ser definida a chave da nova collection, o nome do atributo cujo valor será utilizado como chave é passado como segundo parâmetro do pluck. É o que mostra o exemplo abaixo.
$nameByConstellation = $bronze->pluck('name', 'constellation');
que produz, como resultado da aplicação do dump em $nameByConstellation
Illuminate\Support\Collection {#623
#items: array:5 [
"Pegasu" => "Seya"
"Dragão" => "Shiryu"
"Fênix" => "Ikki"
"Cisne" => "Hyoga"
"Ândromeda" => "Shun"
]
}
reduce
O método reduce
permite reduzir uma collection a um valor único. Dos métodos aqui abordados, trata-se do ligeiramente mais complexo. Como pretendo mostrar, porém, sua utilização é simples.
Junto a utilização do map
e outros métodos, é possível chegar a diversos resultados interessantes com o reduce
. O caso de uso mais simples é o da soma de todos os itens de uma collection de números inteiros, como mostrado abaixo.
$numbers = collect([690, 132, 155, 1024, 2028]);$total = $numbers->reduce(function ($carry, $number) {
return $carry + $number;
});echo 'total: ' . $total . PHP_EOL;
O código acima imprime o resultado
total: 4029
De modo flexível, caso se queira somar o quadrado de todos os números na lista, acrescentar um map
ao código acima, resultando em
$numbers = collect([690, 132, 155, 1024, 2028]);$total = $numbers->map(function ($item) {
return $item * $item;
})->reduce(function ($carry, $number) {
return $carry + $number;
});echo 'total: ' . $total . PHP_EOL;
Desta vez, com o resultado
total: 5678909
Existem diversos casos de uso criativos para o reduce
e, de fato, métodos como ele merecem um post inteiramente dedicado a sua utilização.
Concluindo…
As collections do Laravel provêm um conjunto de métodos flexíveis, expressivos e robustos para lidar com coleções de dados arbitrárias. Aliar essa flexibilidade à resultados de queries é uma característica marcante do framework, que utiliza como ORM (Object-relational mapping) o Eloquent ORM. Ele será abordado na parte 2 desta série.
Até breve!