Capítulo 02: Roteamento RESTful, Parâmetros e Route Model Binding
Especialização em Backend com PHP & Laravel • Laravel 11 & PHP 8.3+ • Eloquent ORM, Sanctum, Filas, Horizon e APIs Corporativas
🗺️ Mapa Conceitual do Tópico
flowchart TD
A["Cliente HTTP"] --> B["routes/api.php"]
B --> C["Route::prefix('v1')->group()"]
C --> D["Route::apiResource() ou verbo explícito"]
D --> E["Route Model Binding (implícito/customizado)"]
E --> F["Controller recebe Model já resolvido"]
F --> G["404 automático se não encontrado"]
style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style B fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style C fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style D fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style E fill:#fce4ec,stroke:#e91e63,stroke-width:2px
style F fill:#f3e5f5,stroke:#9c27b0,stroke-width:2px
🏛️ 1. Roteamento RESTful: apiResource vs. Rotas Explícitas
O padrão REST define sete ações canônicas por recurso. Em APIs, duas delas (create e edit, que servem formulários HTML) não fazem sentido, então o Laravel oferece Route::apiResource() — que registra apenas as cinco rotas relevantes (index, store, show, update, destroy) em uma única linha:
<?php
// routes/api.php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Api\V1\ProdutoController;
Route::prefix('v1')->middleware('auth:sanctum')->group(function () {
Route::apiResource('produtos', ProdutoController::class);
});
Isso equivale a declarar manualmente GET /produtos, POST /produtos, GET /produtos/{produto}, PUT|PATCH /produtos/{produto} e DELETE /produtos/{produto}, todas mapeadas por convenção de nome de método no controller. O encadeamento fluente (prefix→middleware→group) evita duplicar versionamento e segurança em cada rota individualmente — um ponto central quando a API cresce para dezenas de recursos.
🧩 2. Route Model Binding Implícito
Sem binding, todo método de controller começaria com um Model::findOrFail($id) repetitivo. O Laravel resolve isso via reflexão de tipos: se o parâmetro da rota ({produto}) tem o mesmo nome de uma variável type-hintada com um Eloquent Model, o framework injeta a instância já consultada — ou lança ModelNotFoundException (convertida em 404 HTTP automaticamente) se o registro não existir:
<?php
namespace App\Http\Controllers\Api\V1;
use App\Models\Produto;
use Illuminate\Http\JsonResponse;
class ProdutoController extends Controller
{
public function show(Produto $produto): JsonResponse
{
return response()->json($produto); // $produto já foi buscado pelo ID da URL
}
}
Binding por campo customizado
Quando a chave primária numérica não deve vazar na URL (SEO, segurança por obscuridade), o binding pode ser direcionado a outra coluna diretamente na definição da rota, usando a sintaxe {parametro:coluna}:
Route::get('/produtos/{produto:slug}', function (Produto $produto) {
return response()->json($produto);
});
Isso gera a query WHERE slug = ? em vez de WHERE id = ?, sem nenhuma alteração no controller.
🔐 3. Scoped Bindings em Rotas Aninhadas
Em rotas aninhadas (users/{user}/posts/{post}), o binding implícito por padrão resolve {post} de forma independente — permitindo, por erro de URL, acessar um post que pertence a outro usuário. O método scoped() resolve isso automaticamente, adicionando a restrição de relacionamento na query com base na convenção de nome do método de relacionamento no model pai:
Route::apiResource('users.posts', PostController::class)->scoped([
'post' => 'slug',
]);
Com isso, o Laravel garante em uma única chamada que o post resolvido realmente pertence ao user da URL (via $user->posts()), evitando uma checagem manual de autorização redundante — e ainda permite customizar a coluna de binding (slug) por parâmetro.