PHP

Como se usa e o que resolve o recurso de Route Model Binding do Laravel

Saiba o que é o Route Model Binding do Laravel, qual problema ele resolve e como utilizá-lo de forma automática com Implicit Binding e manual com Explicit Binding.

há 5 anos 8 meses

Formação Desenvolvedor PHP
Conheça a formação em detalhes

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.

Laravel - Desenvolvimento de APIs REST
Curso Laravel - Desenvolvimento de APIs REST
Conhecer o curso

Qual 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.

Doctrine ORM - Fundamentos
Curso Doctrine ORM - Fundamentos
Conhecer o curso

Implicit 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:

  1. 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)
  1. Se quiser alterar o nome da variável precisa alterar também o nome do parâmetro e vice-versa;

  2. 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!

Autor(a) do artigo

Elton Fonseca
Elton Fonseca

Professor e desenvolvedor. Formado em análise e desenvolvimento de sistema, pós graduado em engenharia e arquitetura de software. É autor de cursos em diversos temas, como, desenvolvimento back-end, cloud computing e CMSs. Nas horas vagas adora estudar sobre o mercado financeiro, cozinhar e brincar com pequeno Daniel. @eltonfonsecadev

Todos os artigos

Artigos relacionados Ver todos