Eloquent

Como melhorar o retorno das suas APIs no Laravel com API Resource do Eloquent

Quando estamos construindo uma API, alguns pontos são extremamente importantes para que o cliente consiga consumir corretamente as informações. Um desses pontos é o modo como retornamos dados da nossa API.

Em aplicações web existe uma preocupação muito grande com a aparência e usabilidade na hora de mostrar as informações. Quando estamos trabalhando com APIs nossa preocupação não deve ser diretamente ligada a esses detalhes, porém precisamos cuidar de outras características para que os clientes da nossa API consigam utilizá-la de maneira correta. Um desses cuidados deve ser a estrutura que as informações são retornadas.

Como os dados são transferidos em uma API

Geralmente as APIs são utilizadas para integração entre sistemas diferentes. Esses sistemas podem apresentar arquiteturas completamente distintas, por esse motivo os dados são transferidos para um formato intermediário conhecido tanto pela API quanto pelo cliente. Os formatos mais utilizados são JSON, XML e YAML.

O processo de transformação dos dados para esse formato intermediário normalmente é chamado de serialização. No Laravel temos recursos bem importantes para serialização, aconselho a leitura do post onde falamos o que é e como utilizar serialização JSON no Laravel. O grande detalhe é que mesmo com os recursos específicos que a serialização possui ele não permite personalizar totalmente a resposta, nesse ponto entra o API Resource.

Laravel - Eloquent ORM
Curso de Laravel - Eloquent ORM
CONHEÇA O CURSO

O que é API Resource

O API Resource é uma camada extra que usamos na API para transformar os dados que vamos enviar ao cliente. Ela permite que a estrutura de retorno seja totalmente personalizada, isso nos permite formatar os dados na melhor maneira para entregar ao cliente. Além de garantir o desacoplamento com o model, uma vez que podemos definir a estrutura de forma separada.
O conceito da camada de transformação não é exclusividade do Laravel. Inclusive no próprio PHP temos algumas outras bibliotecas que facilitam esse trabalho, uma das mais conhecidas é o Fractal. O API Resource do Laravel facilita bastante a vida quando estamos dentro do Framework, mas também é possível utilizar qualquer outra biblioteca.

Resource

O primeiro conceito que precisamos conhecer é o resource. Ele é uma classe que usamos para transformar um único item de um recurso. Falando assim parece um pouco complicado, mas na prática o que ele faz é pegar um item que precisamos converter e transformar para o formato especificado nele. Vamos imaginar que estamos buscando o produto Iphone X no banco de dados e precisamos devolver ele na API com uma formatação específica, é basicamente isso que o Resource faz.

A classe do resource deve possuir um método chamado toArray, esse método precisa retornar um array com os dados que deseja que o recurso possua após a conversão. O Laravel injeta automaticamente as propriedades do nosso model no resource, isso nos permite acessá-las através do escopo this. Veja o exemplo abaixo:

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class ProductResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request)
    {
        return [
            'codigo' => $this->id,
            'nome'   => $this->name,
            'preco'  => $this->price,
        ];
    }
}

Basicamente o que estamos fazendo é pegar os dados vindos do model (id, name e price) e colocando respectivamente nos itens do array (código, nome e preco).

O modo como usamos o resource é extremamente simples, basta passarmos 1 item do model no construtor da classe:

new App\Http\Resources\ProductResource(App\Product::find(10));

A instância acima pode ser retornada diretamente em uma action do controller ou em uma rota que utiliza função anônima.

A classe acima parece não fazer muito sentido olhando à primeira vista, porém ganhamos algumas vantagens imprescindíveis em usar essa camada:

  • Liberdade para definição da estrutura
  • Possibilidade de enviar dados extras, como, relações ou gerados através de outros métodos da aplicação
  • Podemos alterar o nome das propriedades do nosso model sem problemas, pois agora definimos o nome que será retornado ao usuário diretamente na estrutura;
  • O nosso model não está preso a somente um modo de exibição, caso necessário podemos criar várias classes de Resource para o mesmo model.
Laravel - Eloquent ORM
Curso de Laravel - Eloquent ORM
CONHEÇA O CURSO

Resource Collection

A classe que vimos acima é usada para conversão de apenas um item do model. É comum, porém, precisamos retornar uma coleção de itens na nossa API. Nesse caso utilizamos o Resource Collection. Ele é muito útil para retornar meta informações sobre o conjunto de dados que estamos retornando.
Nele também usamos o método toArray para montar o retorno da nossa aplicação. E usamos a propriedade collection para retornar as informações da coleção:

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\ResourceCollection;

class ProductCollection extends ResourceCollection
{
    /**
     * Transform the resource collection into an array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request)
    {
        return [
            'data' => $this->collection,
            'links' => [
                'self' => 'link-value',
            ],
        ];
    }
}

O modo de usar é basicamente igual ao Resource, porém precisamos passar um conjunto de informações:

new App\Http\Resources\ProductCollection(App\Product::all());

Os dados buscados no banco serão colocados dentro da chave data do nosso resultado. O Laravel automaticamente utiliza o Resource específico para converter cada item da coleção com base na convenção do nome, com isso terá a estrutura definida tanto para cada item específico da coleção como da estrutura geral de retorno.

Conclusão

O modo como retornamos as informações na nossa API é um dos itens mais importantes. Isso juntamente com o uso correto e semântico HTTP dos verbos na requisição e dos códigos na resposta são os itens onde os desenvolvedores mais pecam e que precisa de atenção no desenvolvimento de APIs. Não deixe também de explorar um pouco a documentação do API Resouce no site do Laravel, ele possui recursos para ajudar com relações, meta informações, personalização da resposta HTTP entre outros.

Usando escopos do Eloquent para construir consultas mais limpas no Laravel

É comum precisarmos por diversas vezes fazer consultas que envolvam o mesmo trecho de código. Pensando nisso, o Eloquent possui os escopos, que permitem aplicar modificadores nas consultas. Esses modificadores podem ser aplicados em dois níveis ao model: local e global.

Laravel - Framework PHP (Parte 1/3)
Curso de Laravel - Framework PHP (Parte 1/3)
CONHEÇA O CURSO

Escopo local

O escopo local permite criar métodos com modificadores. Esses métodos podem ser aplicados a uma consulta como se fossem métodos próprios do Eloquent.

Vamos supor que em um model chamado Client exista uma coluna limit que indica o limite de compra do cliente. É possível imaginar que diversas vezes no projeto precisaremos buscar clientes que possuam limite maior que 0.

Poderíamos ter como exemplo as seguintes consultas:

1) Buscar por nome dos clientes que possuem limite:

Client::where('limit', '>', 0)->where('name', 'like', $nome)->get();

2) Buscar por cidade dos clients que possuem limite:

Client::where('limit', '>', 0)->where('city', $cidade)->get();

Note que repetimos o mesmo código where('limit', '>', 0) nas duas consultas. Isso pode não ser muito bom pois, caso a regra de negocio mude, será necessário alterar dentro de cada local do projeto.

A criação de um escopo pode resolver o problema acima facilmente. Para criar um novo escopo basta definir um método publico na classe do model, ele deve ter o nome no padrão scopeNomeEscopo e receber um parâmetro que contém a instancia do Builder.

Veja o exemplo de escopo para definir o limite > 0:

namespace App;

use Illuminate\Database\Eloquent\Model;

class Client extends Model
{
    /*
     * Escopo que busca clientes com limite
     *
     * @param \Illuminate\Database\Eloquent\Builder $query
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeHasLimit($query)
    {
        return $query->where('limit', '>', 0);
    }
}

Agora podemos usar o escopo facilmente na consulta como se fosse um método do próprio Eloquent:

Busca por nome dos clientes que possuem limite:

Client::hasLimit()->where('name', 'like', $nome)->get();

Busca por cidade dos clients que possuem limite:

Client::hasLimit()->where('city', $cidade)->get();

À primeira vista, parece não ter muita diferença, mas já ganhamos na legibilidade da leitura das consultas, quantidade de código digitado e também na definição única do código.

Vamos supor que agora ao invés de 0 o limite deve ser maior que 100 e que precise também sempre ordenar pelo maior limite. Provavelmente você ficaria muito feliz em precisar alterar em apenas um local ao invés de vários, além de ter que ficar olhando cada consulta para conferir se está tudo certo.

Escopo global

O escopo global é muito útil quando necessário aplicar um modificador a todos os registros que forem buscados no model. Ao invés de definirmos em cada consulta os modificadores, ou até mesmo criar escopos locais e aplicar em cada consulta, podemos declarar o escopo global.

Vamos supor devemos mostrar apenas clientes ativos e que eles sejam ordenados pela data de cadastro. Podemos adicionar dois escopos:

namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;

class Client extends Model
{
    /**
     * The "booting" method of the model.
     *
     * @return void
     */
    protected static function boot()
    {
        parent::boot();

        //filtra apenas por clientes ativos
        static::addGlobalScope('active', function (Builder $builder) {
            $builder->where('active', true);
        });

        //ordena pela data de cadastro
        static::addGlobalScope('created', function (Builder $builder) {
            $builder->orderBy('created_at');
        });
    }
}

Agora, toda vez que consultar os clientes automaticamente esses dois modificadores são aplicados.

Consulta sem utilizar o escopo global

Caso não queira aplicar em uma consulta especifica basta chamar o método withoutGlobalScopes():

Client::withoutGlobalScopes()->get();

Se quiser deixar de aplicar apenas um escopo global especifico, basta passar o nome dele:

Client::withoutGlobalScope('created')->get();

Escopo global usando classe própria

É possível também passar uma classe ao invés de uma função anônima para o método addGlobalScope():

static::addGlobalScope(new \App\Scopes\ActiveScope);

Nesse caso a classe para aplicar a condição de ativo ao cliente fica:

namespace App\Scopes;

use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;

class ActiveScope implements Scope
{
    /**
     * Apply the scope to a given Eloquent query builder.
     *
     * @param  \Illuminate\Database\Eloquent\Builder  $builder
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @return void
     */
    public function apply(Builder $builder, Model $model)
    {
        $builder->where('active', true);
    }
}

Conclusão

O uso de escopo nos ajuda na legibilidade das nossas buscas e torna o código menos repetitivo. Se aplicado de maneira correta, ele pode ajudar bastante na qualidade do código e facilitar futuras manutenções.

Laravel - Framework PHP (Parte 2/3)
Curso de Laravel - Framework PHP (Parte 2/3)
CONHEÇA O CURSO
Laravel - Framework PHP (Parte 3/3)
Curso de Laravel - Framework PHP (Parte 3/3)
CONHEÇA O CURSO

Como aliviar seus controllers com os eventos do Eloquent no Laravel

O sonho de todo programador é desenvolver código limpo, organizado, legível, manutenível e muitos outros adjetivos que poderia colocar em uma lista enorme. O Laravel possui vários recursos que nos ajuda a organizar nosso código para conseguir alcançar alguns desses atributos que descrevi acima, se usados de maneira correta. Um desses recursos são os eventos do Eloquent.

Esses eventos são executados sempre que uma ação acontece no model. Isso possibilita removermos responsabilidade dos controllers e executar quando o evento for acionado, com isso, conseguimos um controller mais simples e organizado.

O Eloquent disponibiliza os eventos:

  • Retrieved
  • Creating e Created
  • Updating e Updated
  • Saving e Saved
  • Deleting e Deleted
  • Restoring e Restored

Os eventos com final ing são executados antes da ação no banco de dados e os eventos com final ed são executados após a ação no banco de dados.

Relacionando os eventos no Model

É possível relacionar um evento do Eloquent a um método que fará a ação de três modos diferentes.

Via o método boot

O primeiro modo que podemos adicionar uma ação em um evento do model é através do método estático boot. Por exemplo, para executar uma ação antes da criação podemos fazer:

protected static function boot()
{
    parent::boot();

    static::creating(function ($nomeDoModel) {
        //Ação a ser executada 
    });

}

O Eloquent sempre injeta uma instância do model automaticamente no evento. Nos eventos executados antes da ação no banco é possível alterar os valores do model que serão persistidos.

A utilização dos eventos dentro do próprio model pode não ser a mais aconselhável, pois dependendo da quantidade de eventos pode sobrecarregar, tirando a complexidade dos controllers e transferindo para o models, o que não é bom, pois a solução de um problema criaria outro.

Via classe de evento

Um modo mais limpo de executar os eventos do Eloquent é através de classes específicas para cada um dos eventos. Para isso, basta declarar no model uma propriedade chamada dispatchesEvents com um array contendo o nome do evento e a respectiva classe que será responsável por executar a ação:

protected $dispatchesEvents = [
        'creating' => NomeDoModelCreating::class,
        'deleting' => NomeDoModelDeleting::class,
    ];

Via Observer

Na minha opinião, esse é o modo mais prático de executar ações quando precisamos executar ações em vários eventos do mesmo model.

Essa abordagem consiste basicamente em criar uma classe e declarar métodos públicos com o mesmo nome dos eventos, com isso, o próprio Eloquent verifica se o método existe, se sim, ele executa.

<?php

namespace App\Observers;

use App\NomeDoModel;

class NomeDoModelObserver
{

    public function creating(NomeDoModel $nomeDoModel)
    {
        //Executa antes de criar no banco
    }

    public function deleting(NomeDoModel $nomeDoModel)
    {
        //Executa antes de deletar no banco
    }
}

Único detalhe é que para o Eloquent fazer a referência entre a classe do model e a classe do Observer precisamos declarar dentro do método boot do AppServiceProvider essa relação:

NomeDoModel::observe(NomeDoModelObserver::class);

Note que usamos o método observe() do próprio model que recebe o caminho da classe do observer.

Exemplo prático

Vamos supor que temos uma relação 1 para 1 entre festa e cliente. Baseado nisso, a cada evento do model festa precisamos executar uma ação:

  • Quanto uma festa for criada para um cliente precisamos alterar o status do cliente para com-proposta;
  • Quando uma festa for atualizada, se o atributo valor for maior que zero precisamos alterar o status do cliente para festa-agendada;
  • Por fim, se a festa for excluída, alteramos o status do cliente para sem-proposta;

Nesse caso, podemos criar um observer com os eventos created, updated e deleted para realizar essas ações:

<?php

namespace App\Observers;

use App\Festa;

class FestaObserver
{

    /**
     * Evento executado após a festa ser criada
     *
     * @param Festa $festa
     */
    public function created(Festa $festa)
    {
        $festa->cliente->update(['status' => 'com-proposta']);
    }


    /**
     * Evento executado após a festa ser atualizada
     *
     * @param Festa $festa
     */
    public function updated(Festa $festa)
    {
        if ($festa->valor > 0) {
            $festa->cliente->update(['status' => 'festa-agendada']);
        }
    }

    /**
     * Evento executado após a festa ser deletada
     *
     * @param Festa $festa
     */
    public function deleted(Festa $festa)
    {
        $festa->cliente->update(['status' => 'sem-proposta']);
    }

}

Conclusão

Esse é apenas um dos recursos que podemos usar no Laravel para manter a organização do código. Podemos usar outros como View Composer, Route Model Bind, Policies para permissões, Presenters para remover lógica das views e muito mais.

Quais dos recursos você tem utilizado em seus projetos para melhorar a qualidade do seu código? Fique à vontade para nos contar aqui nos comentários.

Ah, temos um curso muito bom de Laravel no TreinaWeb. Dê uma espiadinha? 🙂

Até a próxima! 🙂