Você está na página 1de 9

Um repositrio um servio

Isso mesmo que voc leu, repositrios fazem parte da camada de servios de um sistema.
Eles so uma classe/categoria de servio. Quando se esta estudando design
patterns esbarramos muito em services, se voc entendeu bem como servios funcionam
sabe que neles que concentramos nossas regras de negcio.
Quando falamos de regras de negcio tambm estamos falando de acesso a dados e/ou
entidades, que basicamente so nossos models. Repositrios existem para que o acesso e
interao com nossos models no seja feito diretamente em outras partes do sistema
como controllers.
Repositrios so os servios da camada de acesso e interao com as entidades do banco
de dados.

Qual o objetivo real de um repositrio?


Antes de ir para a parte prtica, existe um pouco mais de teoria em repositrios.
Basicamente existem objetivos que devem ser alcanados quando usamos repositrios.

Centralizar regras de recuperao e persistncia de dados.


Abstrair a utilizao de ORMs possibilitando a troca por outros ORMs
Abstrair o tipo de banco de dados, que assim como ORMs pode ser trocado.
Devolver dados em um formato agnstico (array ou StdClass), deixando zero
acoplamento de ORM.
Lindo, no? Porm a vida real te d limes, e com limes se faz uma limonada.

No mundo real, dificilmente voc muda de ORM no meio do projeto, muito menos de
banco de dados, j que a maioria dos ORMs j do suporte a essa troca de banco de dados.
O maior problema seria trocar de paradigma de banco de dados, de relacional para no
relacional (NoSQL).
Quantas vezes voc trocou um banco de dados do projeto? Quantas vezes voc trocou
de ORM em um projeto? Quantas vezes voc migrou de MySQL/PostgreSQLpara MongoDB?
Eu apostaria que na maioria esmagadora dos projetos nenhuma dessas situaes
aconteceu, das vezes que isso aconteceu apostaria que a troca para um banco de
dados NoSQL quase inexistente.
Se isso uma realidade, porque ento usar repositrios???

Repositrios em projetos do mundo real


Mesmo que a maioria esmagadora dos projetos no precise de uma camada de
abstrao to grande no que tange acesso a entidades do nosso banco de dados, ainda h
muita utilidade para esse design pattern.
Model, View e Controller
Em algum momento ou outro da sua vida como desenvolvedor voc aprendeu MVC. Com
isso voc aprendeu o que so os Models um jeito muito legal e (por que no?) eficiente de
acessar os dados de uma database.

Com o tempo essa maravilha tambm se tornou um problema, muitas regras de negcio
(leitura e escrita) acabam ficando espalhadas ou melhor jogadas
Acima de qualquer conceito ou filosofia que repositrios possam ter eles devem agregar valor
e qualidade ao projeto. Isso se traduz em facilidade de manuteno!
Como comear a usar repositrios
Sabe criar uma classe? Ento voc est apto a criar um repositrio.

<?php

class UsersRepository
{
public function getList($limit = 15, $offset = 0)
{
return mysql_query("SELECT * FROM `users` LIMIT {$offset} , {$limit}");
}
}

$repo = new UsersRepository();


$page = (int) $_GET['page'];

$resource = $repo->getList(10, $page * 10);

Sem firulas ou boas prticas, este cdigo apenas para provar um ponto: No precisa de
muito para criar uma classe de repositrio.
Somente aps o conceito ser entendido podemos falar de S.O.L.I.D., DRY, PSRs ou Clean Code.
Agora vamos ao Laravel
O Laravel dispensa apresentaes e o Eloquent (seu ORM) tambm, h muita
documentao e contedo sobre isso. O ponto que importa aqui organizao.
Como dito anteriormente, usar nosso model diretamente pode (e provavelmente
vai) espalhar nossas regras ao vento. Os repositrios existem para resolver esse problema.
Por isso partimos da premissa que:
Somente no repositrio executaremos consultas e interaes no banco de dados
Com isso em mente e a ideia de que no precisamos ser conservadores podemos fazer
MUITAS coisas.
Seja genrico antes de ser especifico
Um repositrio seria muito parecido com isso:
<?php

namespace App\Domains\Users;

class UsersRepository
{
public function create(array $data)
{
return User::create($data);
}

public function update($id, array $data)


{
$user = User::find($id);
$user->fill($data);
return $user->save();;
}
}

O problema dessa abordagem basicamente o reso, toda nova implementao


precisa copiar, colar e modificar quando poderia ser apenas estender e modificar.
Para isso precisamos aprender alguns truques que o Laravel e o Eloquent nos
proporcionam.
Eloquent de modo dinmico
O primeiro como o Eloquent funciona internamente, porm sem entrar em muitos
detalhes (mais informaes em um post futuro).

<?php

$user = User::where('email', 'jon.snow@westeros.com')->where('is_dead', false)->first();


$bastards = User::where('country', 'westeros')->whereNull('father')->get();

$query = User::query();
$query->where('email', 'jon.snow@westeros.com');
$query->where('is_dead', false);
$user = $query->first();

$query = (new User())->newQuery();


$query->where('country', 'westeros');
$query->whereNull('father');
$bastards = $query->get();

Maneiras diversas de se criar e executar uma query com eloquent


Em resumo e indo contra o que muitos argumentam, quando voc invocadeterminados
mtodos em um model Eloquent, ele cria um novo objeto query builder (Eloquent\Builder),
e com ele que voc concatena seus argumentos, at executar a query e obter o retorno
da mesma.
Isso abre muitas possibilidades, como mostrado na linha 15 do exemplo, eu crio (e
descarto) um objeto Eloquent, apenas para obter o objeto Eloquent\Builder, sem em
nenhum momento usar uma chamada esttica.
<?php
$query = app(User::class)->newQuery();
$query = app()->make(User::class)->newQuery();
$query = app()->make('\App\Domains\Users\User')->newQuery();

$modelClass = User::class;
$query = app($modelClass)->newQuery();

$query = app()->call('\App\Domains\Users\User@newQuery');
$query = app()->call($modelClass, [], 'newQuery');
$query = app()->call(User::class, [], 'newQuery');

Criando um novo objeto Eloquent\Builder dinamicamente


Como podem ver, usando o Container do Laravel ainda mais fcil criar um
objeto Eloquent\Builder.
Criando nossa abstrao
Agora voc tem todo o conhecimento necessrio para criar uma base slida de repositrio,
que atenda as suas necessidades. Vamos ver como seria um exemplo simples de como usar
tudo isso a seu favor.
<?php

namespace App\Support\Repositories;

use Illuminate\Database\Eloquent\Builder as EloquentQueryBuilder;


use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Database\Query\Builder as QueryBuilder;
use Illuminate\Pagination\AbstractPaginator as Paginator;

abstract class BaseRepository


{
/**
* Model class for repo.
*
* @var string
*/
protected $modelClass;

/**
* @return EloquentQueryBuilder|QueryBuilder
*/
protected function newQuery()
{
return app($this->modelClass)->newQuery();
}

/**
* @param EloquentQueryBuilder|QueryBuilder $query
* @param int $take
* @param bool $paginate
*
* @return EloquentCollection|Paginator
*/
protected function doQuery($query = null, $take = 15, $paginate = true)
{
if (is_null($query)) {
$query = $this->newQuery();
}

if (true == $paginate) {
return $query->paginate($take);
}

if ($take > 0 || false !== $take) {


$query->take($take);
}

return $query->get();
}

/**
* Returns all records.
* If $take is false then brings all records
* If $paginate is true returns Paginator instance.
*
* @param int $take
* @param bool $paginate
*
* @return EloquentCollection|Paginator
*/
public function getAll($take = 15, $paginate = true)
{
return $this->doQuery(null, $take, $paginate);
}

/**
* @param string $column
* @param string|null $key
*
* @return \Illuminate\Support\Collection
*/
public function lists($column, $key = null)
{
return $this->newQuery()->lists($column, $key);
}

/**
* Retrieves a record by his id
* If fail is true $ fires ModelNotFoundException.
*
* @param int $id
* @param bool $fail
*
* @return Model
*/
public function findByID($id, $fail = true)
{
if ($fail) {
return $this->newQuery()->findOrFail($id);
}

return $this->newQuery()->find($id);
}
}

Nossos repositrios ficariam assim:


<?php

namespace App\Domains\Users;

use App\Support\Repositories\BaseRepository;
use Carbon\Carbon;

class UsersRepository extends BaseRepository


{
protected $modelClass = User::class;

public function getPaying($limit = 15, $paginate = true)


{
$now = Carbon::now();

$query = $this->newQuery();
$query->where('is_subscriber', true);
$query->where('subscription_ends_in', '<=', $now);
$query->orderBy('name');

return $this->doQuery($query, $limit, $paginate);


}
}
Repositrio simples
<?php

namespace App\Domains\Clients

use Artesaos\Warehouse\BaseRepository;

class ClientsRepository extends BaseRepository


{
public function getInvoices(Client $client, $take = 15, $paginate = false)
{
$query = $client->invoices()->getQuery();
$query->orderBy('date');
$query->with('products');

return $this->doQuery($query, $take, $paginate);


}
}

Repositrio com relacionamentos

Este um exemplo extremamente simples, no h limites para as combinaes que voc


pode criar. H ainda mais abstraes possveis, voc pode ter toda uma metodologia
de crud que te permita extrema liberdade e versatilidade.
Por exemplo, no warehouse existe uma classe base para crud, com vrios pedaos dela
separados. No so todos os repositrios que usam crud, e muitos deles dependem de
mais coisas, por isso o warehouse no insere mtodos crud por padro, voc precisa
importar esses mtodos manualmente para seu repositrio.

Se voc possui uma base slida e distribuda de abstrao o nvel de reaproveitamento de


cdigo tende ao infinito.

Por exemplo, em um sistema multi-tenant existem duas reas, a administrativa, onde se


tem acesso a todos as entidades de todos os tenants. Porm, quando um usurio comum
acessa a rea de um tenant (tenantspodem ter vrios usurios associados a si) ele s pode
ter acesso e criar coisas que pertenam ao seu tenant.

A rea administrativa j possui uma srie de repositrios que por sua vez tem muitos
mtodos que so reaproveitados na rea do tenant. Para no reimplementar todos esses
repositrios novamente e no criar monstrinhos, cria-se repositrios que estendem os
repositrios originais. Assim por herana eles possuem os mesmos mtodos que os
repositrios de administrao. E como toda consulta feita criando um novo
objeto Eloquent\Builder pelo mtodo newQuery basta modificar o comportamento desse
mtodo.
A mesma tcnica pode ser aplicada quando se cria uma nova entidade, definindo
o tenant_id dela automaticamente. Para deixar as coisas mais fceis ainda, toda essa lgica
feita em um trait. O resultado voc confere abaixo.
<?php

namespace App\Domains\Tenants\Repositories\Users;

use App\Domains\Users\Repositories\UsersRepository as OriginalUsersRepository;


use App\Domains\Tenants\Repositories\Traits\RegularTenantRepository;

class UsersRepository extends OriginalUsersRepository


{
use RegularTenantRepository;
}
<?php

namespace App\Domains\Tenants\Repositories\Traits;

use App\Domains\Tenants\Tenant;

trait RegularTenantRepository
{
/**
* @var Tenant
*/
protected $tenant;

public function __construct(Tenant $tenant)


{
$this->tenant = $tenant;
}

/**
* @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder
*/
public function newQuery()
{
$query = parent::newQuery();

return $query->where('tenant_id', $this->tenant->id);


}

/**
* @param array $data
*
* @return \Illuminate\Database\Eloquent\Model
*/
public function factory(array $data = [])
{
$model = parent::factory($data);
$model->tenant_id = $this->tenant->id;

return $model;
}
}

Como voc pode ver, modificar o comportamento de um repositrio, e at mesmo


qualquer classe se torna algo simples se pensamos primeiro na abstrao e depois na
implementao.

Este post ficou bem maior do que eu esperava, porm no quis dividir ele em duas partes,
pois poderia perder parte do foco. Ainda h mais contedo a ser tratado como por
exemplo iterfaces e bind de classes, porm isso ter que ficar para outro momento.