Thiago Cantero

Tecnologia e Entretenimento

APIArquitetura de SoftwareBanco de DadosDesenvolvimento WebLaravelPHPProgramação

Aplicando Padrão Repository no Laravel

Olá, mundo!
Tudo bem?

Deixar o código mais limpo e legível, realmente deixa a manutenção, e por consequência a documentação mais eficiente.
Existem diversos padrões que são empregados, sob as diferentes necessidades, hoje falaremos sob a abstração da camada de acesso aos dados.

Introdução

Quando trabalhamos em uma aplicação com certa complexidade, é importante deixarmos cada classe com sua responsabilidade única, para atendermos cada especificidade que o domínio da aplicação exige.

Imagine um cenário em que você tem cerca de cem tabelas, e tem que trabalhar com todas elas de maneira estruturada, seria um caos, concorda?
Cada tabela sendo chamada nos diversos Controllers que você tem, para obter uma coleção, uma consulta mais enxuta, ficaria extremamente oneroso, e também levaria a eventuais falhas, já que perderia o foco de cada consulta, enfim. Para isso aplicamos o Padrão Repository.

Um Repositório realiza a mediação entre as camadas mapeadoras de dados e as camadas de domínio, agindo como uma coleção de objetos de domínio em memória.
Os objetos clientes criam especificações de consultas declarativamente e as submetem ao Repositório para que sejam satisfeitas. Objetos podem ser adicionados e removidos do Repositório, assim como o podem ser de uma simples coleção de objetos, e o código de mapeamento encapsulado pelo Repositório executará as operações apropriadas por trás dos panos. Conceitualmente, um Repositório encapsula o conjunto de objetos persistidos e as operações executadas sobre eles, fornecendo uma visão mais orientada a objetos da camada de persistência. O Repositório também dá suporte ao objetivo de alcançar uma separação limpa e uma dependência unidirecional entre as camadas de domínio e as camadas mapeadoras de dados. – Padrões de Arquiteturas de Aplicações Corporativas, pág. 309, Martin Fowler, Editora Bookman 2006.

Com os conceitos bem definidos, com a excelente e sempre explanação de Martin Fowler, vamos para a prática.

Vamos ao Trabalho//

Criamos um novo projeto no Laravel

composer create-project laravel/laravel laravel_repo

configuramos nosso arquivo .env

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_repo
DB_USERNAME=<SEU USUÁRIO DE BANCO DE DADOS>
DB_PASSWORD=<SENHA DO USUÁRIO DO BANCO DE DADOS>

nosso próximo passo é a criação de nosso Controller, Model, Migration, Seeder e Factory com este simples comando:

php artisan make:model Pedido -a

Em nossa migration, colocamos para a criação da tabela os seguintes campos.

database/migrations/2023_01_23_123058_create_pedidos_table.php

public function up()
{
    Schema::create('pedidos', function (Blueprint $table) {
        $table->id();
        $table->text('detalhes');
        $table->string('cliente');
        $table->boolean('foi_concluida')->default(false);
        $table->timestamps();
    });
 }

Em nossa tabela teremos o ID como chave primária, o nome do Cliente, os detalhes da compra, e um valor booleano de se a compra foi concluída ou não.

Próximo passo é alterar nossa Factory para criação de dados fictícios para nossa base:

database/factories/PedidoFactory.php

public function definition() 
{
    return [
        'detalhes'       => $this->faker->sentences(4, true),
        'cliente'         => $this->faker->name(),
        'foi_concluida' => $this->faker->boolean(),
    ];
}

Agora vamos abrir nossa classe Seeder para a criação de 25 registros:

database/seeders/PedidoSeeder.php

/*
* Faça a importação da Model Pedido
*/

use App\Models\Pedido;

public function run() 
{
    Pedido::factory()->times(25)->create();
}

Após a inclusão em nossa Seeder, vamos fazer a chamada na Classe que executa propriamente a Seeder, no método run().

database/seeders/DatabaseSeeder.php

$this->call(
                [
                    PedidoSeeder::class
                ]
            );

e executamos o comando:

php artisan migrate --seed

Nosso próximo passo agora é o de criar nossa Interface que servirá para acionar o repositório, através de nosso futuro Repositório. Para isso, na pasta \app criamos um subdiretório Interfaces e este código:

app/Interfaces/PedidoRepositoryInterface.php

<?php

namespace App\Interfaces;

interface PedidoRepositoryInterface 
{
    public function getAllPedidos();
    public function getPedidoById($pedidoId);
    public function deletePedido($pedidoId);
    public function createPedido(array $pedidoDetalhes);
    public function updatePedido($pedidoId, array $novoDetalhes);
    public function getFoiConcluidoPedidos();
}

Agora sim, criamos a subpasta Repositories na pasta App, e criamos nosso repositório.

app/Repositories/PedidoRepository.php

<?php

namespace App\Repositories;

use App\Interfaces\PedidoRepositoryInterface;
use App\Models\Pedido;

class OrderRepository implements PedidoRepositoryInterface 
{
    public function getAllPedidos() 
    {
        return Pedido::all();
    }

    public function getPedidoById($pedidoId) 
    {
        return Pedido::findOrFail($pedidoId);
    }

    public function deletePedido($pedidoId) 
    {
        Pedido::destroy($pedidoId);
    }

    public function createPedido(array $pedidoDetalhes) 
    {
        return Pedido::create($pedidoDetalhes);
    }

    public function updatePedido($pedidoId, array $novoDetalhes) 
    {
        return Pedido::whereId($pedidoId)->update($novoDetalhes);
    }

    public function getFoiConcluidoPedidos()
    
    {
        return Pedido::where('foi_concluido', true);
    }
}

Agora façamos a alteração de nosso Controller.

app/Http/Controllers/PedidoController.php

<?php

namespace App\Http\Controllers;

use App\Interfaces\PedidoRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;

class PedidoController extends Controller 
{
    private PedidoRepositoryInterface $pedidoRepository;

    public function __construct(PedidoRepositoryInterface $pedidoRepository) 
    {
        $this->pedidoRepository = $pedidoRepository;
    }

    public function index(): JsonResponse 
    {
        return response()->json([
            'data' => $this->pedidoRepository->getAllPedidos()
        ]);
    }

    public function store(Request $request): JsonResponse 
    {
        $pedidoDetalhes = $request->only([
            'cliente',
            'detalhes'
        ]);

        return response()->json(
            [
                'data' => $this->pedidoRepository->createPedido($pedidoDetalhes)
            ],
            Response::HTTP_CREATED
        );
    }

    public function show(Request $request): JsonResponse 
    {
        $pedidoId = $request->route('id');

        return response()->json([
            'data' => $this->pedidoRepository->getPedidoById($pedidoId)
        ]);
    }

    public function update(Request $request): JsonResponse 
    {
        $pedidoId = $request->route('id');
        $pedidoDetalhes = $request->only([
            'cliente',
            'detalhes'
        ]);

        return response()->json([
            'data' => $this->pedidoRepository->updatePedido($pedidoId, $pedidoDetalhes)
        ]);
    }

    public function destroy(Request $request): JsonResponse 
    {
        $pedidoId = $request->route('id');
        $this->pedidoRepository->deletePedido($pedidoId);

        return response()->json(null, Response::HTTP_NO_CONTENT);
    }
}

Como podemos observar nosso Controller tem a injeção da Interface, e constrói o objeto no início da classe, posteriormente temos os métodos index(), que retorna toda a coleção de registros, com a chamada do Eloquent. Posteriormente o método store(), requisitando apenas o cliente e os detalhes. Logo em seguida tempos o método show() que retorna o valor com o Id do Pedido. O método update() que irá tratar requisições POST em nossas rotas para atualização, e  por fim o método destroy() que irá obter o Id e apagar o registro de nossa base de dados.

Agora vamos criar nossas rotas para consumir através de um api.

routes/api.php

<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\PedidoController;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/

Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
    return $request->user();
});
Route::get('pedidos', [PedidoController::class, 'index']);
Route::get('pedidos/{id}', [PedidoController::class, 'show']);
Route::post('pedidos', [PedidoController::class, 'store']);
Route::put('pedidos/{id}', [PedidoController::class, 'update']);
Route::delete('pedidos/{id}', [PedidoController::class, 'delete']);

Agora, devemos fazer com que o contâiner de serviços do Laravel escute nosso repositório.

php artisan make:provider RepositoryServiceProvider

E no método register() devemos incluir nosso repositório, tanto a interface, quanto ele propriamente dito.

app/Providers/RepositoryServiceProvider.php

use App\Interfaces\PedidoRepositoryInterface;
use App\Repositories\PedidoRepository;
  
public function register()
    {
        $this->app->bind(PedidoRepositoryInterface::class, PedidoRepository::class);
    }

E por fim, adicionamos o Serviço no array associativo providers em config/app.php.

'providers' => [

        /*
         * Demais configurações...
         */
         App\Providers\RepositoryServiceProvider::class,

Agora vamos testar nossa aplicação.

php artisan serve

 

Vamos fazer uma consulta no Insomnia:

Perfeito, nossa aplicação está utilizando corretamente a camada de interface e a abstração de nosso banco, através de um repositório.

Código fonte no Github.

 

Por hoje é só, pessoal!Até breve! ; )

 

Ser grande é abraçar uma grande causa.