O Route Model Binding não é um recurso novo no Laravel, porém, o pessoal que está começando no framework costuma ter dificuldade em entender qual problema ele resolve e como ele funciona.
Ele existe desde a versão 4.2 do Laravel e de lá para cá já passou por algumas pequenas mudanças e da versão 5.2 em diante não teve mais o modo de usar alterado, com isso podemos chegar à conclusão de que ele alcançou uma boa maturidade.
Curso Laravel - Desenvolvimento de APIs REST
Conhecer o cursoQual problema o Route Model Binding tenta resolver?
Antes de falarmos o que é o recurso, é importante sabermos qual problema ele resolve. É muito comum em aplicações web recebermos a partir da URL o código de um registro que vamos precisar realizar uma ação no banco de dados. Veja abaixo alguns exemplos:
http://meusistema.dev/produto/1/mostrar
http://meusistema.dev/produto/1/editar
http://meusistema.dev/produto/1/atualizar
http://meusistema.dev/produto/1/deletar
No caso acima o número 1
representa o código do produto que desejamos realizar determinada ação. Pensando no nosso código, dentro de cada uma dessas ações precisaremos buscar o registro com código 1
no banco de dados. Mas, e se baseado nesse código o próprio framework já buscasse esse registro no banco de dados e injetasse ele prontinho para ser usado, evitando a repetição de código desnecessário? É exatamente isso que o Route Model Binding faz!
Como o Laravel faz isso?
O Laravel faz isso usando o parâmetro na rota e um model do Eloquent. Veja a explicação baseado no nome do recurso:
- Route: utiliza o nome do parâmetro especificado na rota para saber qual recursos ele deve usar;
- Model: utiliza o model injetado na ação para realizar a busca baseado no valor passado na rota;
- Bind: faz o bind entre o parâmetro passado na rota e a variável injetada na ação.
Esse processo por padrão é feito implicitamente, basta passar o nome do parâmetro, o model que será usado e o nome da variável que receberá a instância do model. Existem situações onde não é possível fazer isso de forma automática, pois o valor passado na URL precisa ser filtrado de um modo diferente e não baseado na chave padrão. Nesse caso podemos fazer esse filtro explicitamente.
Curso Laravel - Eloquent ORM Avançado
Conhecer o cursoImplicit Binding
No modo implícito o próprio Laravel faz todo trabalho automaticamente, entregando a instância do model já corretamente populada. Se o valor passado na URL não for encontrado no banco de dados, o Laravel retorna um erro de página não encontrada, evitando assim que a execução chegue sequer à ação.
Primeira coisa que precisamos fazer é declarar o parâmetro na rota:
Route::get('products/{product}', 'ProductController@edit');
Agora, na ação do controller, basta injetarmos o model referente ao produto:
/**
* Show the form for editing the specified resource.
*
* @param AppProduct $product
* @return IlluminateHttpResponse
*/
public function edit(Product $product)
{
//aqui temos o produto já filtrado na variável $product
}
Alguns detalhes importantes para evitar problemas na hora de usar o recurso:
- O nome do parâmetro passado na rota deve ser o mesmo nome da variável injetada da ação:
//nome do parâmetro product
Route::get('products/{product}', 'ProductController@edit');
//injeção na ação
public function edit(Product $product)
-
Se quiser alterar o nome da variável precisa alterar também o nome do parâmetro e vice-versa;
-
Também deve sempre se atentar se está usando a classe correta do model na injeção do método da ação.
Explicit Binding
Existem situações onde nem tudo segue as regras para realização do bind automático. Nesse caso, podemos informar para o framework como fazer o Route Model Binding manualmente.
Essa lógica pode ser criada dentro do método boot
em um service provider qualquer. Veja o exemplo abaixo:
Route::bind('product', function ($value) {
$id = decode($value);
return AppProduct::where('id', $id)->first() ?? abort(404);
});
No exemplo acima estamos usando uma função hipotética chamada decode
que decodifica o valor passado na URL para o ID válido do banco de dados. Poderia usar qualquer outra lógica, como filtrar por outro campo e etc.
Veja como fica essa configuração usando o RouteServiceProvider
:
<?php
namespace AppProviders;
use IlluminateSupportFacadesRoute;
use IlluminateFoundationSupportProvidersRouteServiceProvider as ServiceProvider;
class RouteServiceProvider extends ServiceProvider
{
/**
* This namespace is applied to your controller routes.
*
* In addition, it is set as the URL generator's root namespace.
*
* @var string
*/
protected $namespace = 'AppHttpControllers';
/**
* Define your route model bindings, pattern filters, etc.
*
* @return void
*/
public function boot()
{
parent::boot();
Route::bind('product', function ($value) {
$id = decode($value);
return AppProduct::where('id', $id)->first() ?? abort(404);
});
}
/**
* Define the routes for the application.
*
* @return void
*/
public function map()
{
$this->mapApiRoutes();
$this->mapWebRoutes();
//
}
/**
* Define the "web" routes for the application.
*
* These routes all receive session state, CSRF protection, etc.
*
* @return void
*/
protected function mapWebRoutes()
{
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web.php'));
}
/**
* Define the "api" routes for the application.
*
* These routes are typically stateless.
*
* @return void
*/
protected function mapApiRoutes()
{
Route::prefix('api')
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
}
}
Conclusão
Esse é mais um recurso do Laravel que nos ajuda a tornar nosso controllers muito mais limpos, legíveis e fáceis de realizar manutenção. Além de tudo isso, ele permite personalizar totalmente a lógica na hora de filtrar o registro, o que o torna muito flexível.
Caso queira continuar seus estudos em Laravel e Eloquent, temos artigos aqui no blog ensinando a usar escopos do Eloquent para construir consultas mais limpas no Laravel, como remover a lógica das views com presenters no Laravel e como aliviar seus controllers com os eventos do Eloquent no Laravel, confira!