Controladores do Laravel e Tratamento de Requisições
O controlador é o "comandante de negócios" do Laravel—ele recebe requisições, coordena models e views, e retorna respostas; toda a lógica de negócio é orquestrada a partir daqui.
1. O Que Você Vai Aprender
- Controladores Básicos e Controladores de Ação Única
__invoke - Controlador Resource: Mapeamento da flag
--resourcepara métodos CRUD - Controlador Resource de API: Flag
--apivinculada a uma rota - Injeção de Dependência: DI em nível de construtor e de método
- Alocação de Middleware do Controlador
2. Uma História Real de um Desenvolvedor Backend
(1) Problema: Um "Controlador Deus" de 2.000 linhas
No início, Bob enfiou toda a lógica do ShopMetrics em um único ShopController—gerenciamento de produtos, processamento de pedidos, autenticação de usuários e geração de relatórios estavam todos reunidos. Quando o código chegou a 2.000 linhas, alterar uma funcionalidade podia quebrar outra. Quando Charlie assumiu, levou três dias apenas para desenredar a estrutura do código, e Alice teve que esperar duas semanas por uma solicitação de funcionalidade menor.
(2) Solução com o Controlador Resource
O Controlador Resource do Laravel divide as operações CRUD em sete métodos separados, com cada método realizando uma única tarefa. Quando combinado com vinculação de rotas, as URLs e métodos são mapeados automaticamente um ao outro.
# Um comando gera um controlador CRUD completo
php artisan make:controller ShopController --resource
# Cria: index(), create(), store(), show(), edit(), update(), destroy()
(3) Resultado
Depois que Bob refatorou o código usando o controlador resource, cada método ficou limitado a 30 linhas ou menos; a pequena funcionalidade de Alice foi concluída em dois dias em vez de duas semanas; e Charlie não se preocupava mais em quebrar funcionalidades existentes ao assumir novas funcionalidades.
3. Controlador Básico
(1) Criação e Estrutura
php artisan make:controller HomeController
# Cria: app/Http/Controllers/HomeController.php
// app/Http/Controllers/HomeController.php
class HomeController extends Controller
{
public function index(): View
{
return view('home.index');
}
public function about(): View
{
return view('home.about');
}
}
(2) Controlador de Ação Única __invoke
Quando um controlador precisa de apenas um método, use __invoke em vez de um método nomeado.
php artisan make:controller GenerateReportController --invokable
// app/Http/Controllers/GenerateReportController.php
class GenerateReportController extends Controller
{
public function __invoke(Request $request): RedirectResponse
{
$report = ReportGenerator::create($request->all());
return redirect()->route('reports.show', $report->id);
}
}
// Registro de rota
Route::post('/reports/generate', GenerateReportController::class);
| Dimensão | Controlador Padrão | Controlador de Ação Única |
|---|---|---|
| Número de métodos | Múltiplos | 1 __invoke |
| Registro de Rota | [Ctrl::class, 'method'] |
Ctrl::class |
| Casos de Uso | Operações Relacionadas | Operações de Responsabilidade Única |
| Exemplo | ShopController | GenerateReportController |
(1) ▶ Exemplo: Controlador de Ação Única do ShopMetrics
// app/Http/Controllers/ExportOrdersController.php
class ExportOrdersController extends Controller
{
public function __invoke(Request $request): StreamedResponse
{
$shop = Shop::findOrFail($request->shop_id);
$csv = OrderExporter::toCsv($shop->orders);
return response()->streamDownload(
callback: fn () => print($csv),
name: "orders-{$shop->slug}.csv",
headers: ['Content-Type' => 'text/csv'],
);
}
}
// routes/web.php
Route::post('/shops/{shop}/export', ExportOrdersController::class)
->name('shops.export');
Saída:
// Execução bem-sucedida
4. Controlador Resource
(1) Criar um Controlador Resource
php artisan make:controller ShopController --resource
Gera automaticamente 7 métodos CRUD:
| Verbo HTTP | URI | Método | Finalidade |
|---|---|---|---|
| GET | /shops | index | listar |
| GET | /shops/create | create | Formulário de Criação |
| POST | /shops | store | Salvar Novo Registro |
| GET | /shops/{shop} | show | Detalhes |
| GET | /shops/{shop}/edit | edit | Formulário de Edição |
| PUT/PATCH | /shops/{shop} | update | Atualizar |
| DELETE | /shops/{shop} | destroy | Excluir |
(2) Registro de Rotas
// Uma única linha registra todas as 7 rotas
Route::resource('shops', ShopController::class);
// Limitar apenas a métodos específicos
Route::resource('shops', ShopController::class)->only([
'index', 'show', 'store', 'update', 'destroy',
]);
// Excluir métodos específicos
Route::resource('shops', ShopController::class)->except([
'create', 'edit',
]);
(1) ▶ Exemplo: Controlador Resource de Lojas do ShopMetrics
// app/Http/Controllers/ShopController.php
class ShopController extends Controller
{
public function __construct()
{
$this->middleware('auth');
$this->middleware('tenant.resolve')->except('index', 'show');
}
public function index(): View
{
$shops = Shop::with('tenant')->paginate(15);
return view('shops.index', compact('shops'));
}
public function create(): View
{
return view('shops.create');
}
public function store(StoreShopRequest $request): RedirectResponse
{
$shop = Shop::create($request->validated());
return redirect()->route('shops.show', $shop)
->with('success', 'Loja criada com sucesso.');
}
public function show(Shop $shop): View
{
$shop->load('products', 'orders');
return view('shops.show', compact('shop'));
}
public function edit(Shop $shop): View
{
return view('shops.edit', compact('shop'));
}
public function update(UpdateShopRequest $request, Shop $shop): RedirectResponse
{
$shop->update($request->validated());
return redirect()->route('shops.show', $shop)
->with('success', 'Loja atualizada com sucesso.');
}
public function destroy(Shop $shop): RedirectResponse
{
$shop->delete();
return redirect()->route('shops.index')
->with('success', 'Loja excluída com sucesso.');
}
}
Saída:
// Execução bem-sucedida
5. Controlador Resource de API
(1) Criar um controlador de API
php artisan make:controller Api/ShopController --api
--api é equivalente a --resource --except=create,edit, porque a API não requer página de formulário.
| Método | Resources Web | Resources API |
|---|---|---|
| index | ✅ | ✅ |
| create | ✅ | ❌ |
| store | ✅ | ✅ |
| show | ✅ | ✅ |
| edit | ✅ | ❌ |
| update | ✅ | ✅ |
| destroy | ✅ | ✅ |
(1) ▶ Exemplo: Controlador de API do ShopMetrics
// app/Http/Controllers/Api/ShopController.php
class ShopController extends Controller
{
public function __construct()
{
$this->middleware('auth:sanctum');
}
public function index(Request $request): JsonResponse
{
$shops = Shop::query()
->when($request->search, fn($q, $search) => $q->where('name', 'like', "%{$search}%"))
->paginate($request->per_page ?? 15);
return ShopResource::collection($shops);
}
public function store(StoreShopRequest $request): JsonResponse
{
$shop = Shop::create($request->validated());
return new ShopResource($shop);
}
public function show(Shop $shop): JsonResponse
{
return new ShopResource($shop->load('products', 'orders'));
}
public function update(UpdateShopRequest $request, Shop $shop): JsonResponse
{
$shop->update($request->validated());
return new ShopResource($shop);
}
public function destroy(Shop $shop): Response
{
$shop->delete();
return response()->noContent();
}
}
Saída:
// Execução bem-sucedida
6. Injeção de Dependência
(1) Injeção no Construtor
class OrderController extends Controller
{
public function __construct(
private OrderService $orderService,
private PaymentGateway $payment,
) {}
public function store(StoreOrderRequest $request): RedirectResponse
{
$order = $this->orderService->create($request->validated());
$this->payment->charge($order);
return redirect()->route('orders.show', $order);
}
}
(2) Injeção em Nível de Método
class ReportController extends Controller
{
public function show(Request $request, Report $report): View
{
// $request injetado pelo container
// $report resolvido via Route Model Binding
return view('reports.show', compact('report'));
}
}
(3) Route Model Binding
// Vinculação implícita — dica de tipo no método do controlador
Route::get('/shops/{shop}', [ShopController::class, 'show']);
public function show(Shop $shop): View
{
// $shop é automaticamente buscado do BD por {shop}
// Equivalente a: Shop::findOrFail($shop)
return view('shops.show', compact('shop'));
}
// Chave personalizada — vincular por slug em vez de id
Route::get('/shops/{shop:slug}', [ShopController::class, 'show']);
// Agora: /shops/alice-store → Shop where slug = 'alice-store'
| Método de Injeção | Casos de Uso | Ciclo de Vida |
|---|---|---|
| Construtor | Exigido por todos os métodos do controlador | Requisição inteira |
| Nível de método | Exigido apenas por métodos específicos | Método único |
| Route Model Binding | Recuperar modelos automaticamente das URLs | Método único |
(1) ▶ Exemplo: Controlador de Pedidos do ShopMetrics com Injeção de Dependência
// app/Http/Controllers/OrderController.php
class OrderController extends Controller
{
public function __construct(
private OrderService $orderService,
) {
$this->middleware('auth');
}
public function index(Request $request): View
{
$orders = $request->user()->orders()
->with('shop', 'products')
->latest()
->paginate(15);
return view('orders.index', compact('orders'));
}
public function show(Order $order): View
{
$this->authorize('view', $order);
$order->load('items.product', 'shop', 'payment');
return view('orders.show', compact('order'));
}
}
Saída:
// Execução bem-sucedida
7. Middleware do Controlador
(1) Alocação no Construtor
class ShopController extends Controller
{
public function __construct()
{
$this->middleware('auth');
$this->middleware('tenant.resolve')->except('index');
$this->middleware('can:update,shop')->only('update', 'edit');
}
}
(2) Alocação em Nível de Rota
Route::middleware(['auth', 'admin'])->group(function () {
Route::resource('plans', PlanController::class);
});
| Local de Alocação | Granularidade | Casos de Uso |
|---|---|---|
| Construtor | Nível de método | Métodos diferentes dentro de um controlador exigem middleware diferente |
| Definição de Rota | Grupo de rotas | Um grupo de rotas que compartilha middleware |
| Kernel Global | Global | Deve ser executado para todas as requisições |
(1) ▶ Exemplo: Controlador do Painel Administrativo do ShopMetrics
// app/Http/Controllers/Admin/PlanController.php
class PlanController extends Controller
{
public function __construct()
{
$this->middleware(['auth', 'role:admin']);
}
public function index(): View
{
$plans = Plan::withCount('subscriptions')->get();
return view('admin.plans.index', compact('plans'));
}
public function store(StorePlanRequest $request): RedirectResponse
{
Plan::create($request->validated());
return redirect()->route('admin.plans.index')
->with('success', 'Plano criado.');
}
}
Saída:
// Execução bem-sucedida
8. Cadeia Requisição → Controlador → Modelo → View Resposta
sequenceDiagram
participant C as Cliente
participant R as Roteador
participant M as Middleware
participant Ctrl as Controlador
participant Model as Modelo
participant V as View
C->>R: Requisição HTTP
R->>M: Executar pipeline de middleware
M->>Ctrl: Chamar método do controlador
Ctrl->>Model: Consultar dados
Model-->>Ctrl: Retornar resultados
Ctrl->>V: Passar dados para a view
V-->>Ctrl: HTML renderizado
Ctrl-->>C: Resposta HTTP
9. Exemplo Completo: Controlador CRUD de Produtos do ShopMetrics
// ============================================
// Completo: ProductController do ShopMetrics
// Abrange: controlador resource, DI, middleware, model binding
// ============================================
// app/Http/Controllers/ProductController.php
class ProductController extends Controller
{
public function __construct(
private ProductService $productService,
) {
$this->middleware('auth');
$this->middleware('tenant.resolve');
}
public function index(Request $request): View
{
$products = Product::query()
->where('tenant_id', tenant()->id)
->when($request->search, fn($q, $s) => $q->where('name', 'like', "%{$s}%"))
->when($request->category, fn($q, $c) => $q->where('category_id', $c))
->with('category')
->orderBy($request->sort ?? 'created_at', $request->direction ?? 'desc')
->paginate(20);
return view('products.index', compact('products'));
}
public function create(): View
{
$categories = Category::forTenant(tenant()->id)->get();
return view('products.create', compact('categories'));
}
public function store(StoreProductRequest $request): RedirectResponse
{
$product = $this->productService->create(
tenant()->id,
$request->validated(),
);
return redirect()->route('products.show', $product)
->with('success', 'Produto criado com sucesso.');
}
public function show(Product $product): View
{
$this->authorize('view', $product);
$product->load('category', 'orderItems.order');
return view('products.show', compact('product'));
}
public function edit(Product $product): View
{
$this->authorize('update', $product);
$categories = Category::forTenant(tenant()->id)->get();
return view('products.edit', compact('product', 'categories'));
}
public function update(UpdateProductRequest $request, Product $product): RedirectResponse
{
$this->authorize('update', $product);
$product->update($request->validated());
return redirect()->route('products.show', $product)
->with('success', 'Produto atualizado com sucesso.');
}
public function destroy(Product $product): RedirectResponse
{
$this->authorize('delete', $product);
$product->delete();
return redirect()->route('products.index')
->with('success', 'Produto excluído com sucesso.');
}
}
❓ Perguntas Frequentes
Route::bind() no RouteServiceProvider para definir lógica de análise personalizada, ou sobrescrevendo o método resolveRouteBindingQuery() no modelo.new?new cria objetos manualmente, exigindo que você gerencie a cadeia de dependências; com DI, o container do Laravel resolve e injeta dependências automaticamente, suportando vinculação de interface, gerenciamento de singleton e testes com mock.Resource em vez de JSON diretamente?Resource padroniza o formato de saída JSON, permitindo ocultar campos sensíveis, renomear campos e aninhar dados relacionados. Retornar o modelo diretamente exporia todos os campos e resultaria em um formato inconsistente.DB::raw para escrever SQL no controlador?DB::raw contorna a camada de segurança do Eloquent, tornando-o propenso a injeção de SQL.📖 Resumo
- O controlador é responsável por receber requisições, coordenar o modelo e a view, e retornar respostas
- O controlador de ação única usa
__invokepara lidar com operações únicas que não são operações CRUD - O controlador resource mapeia automaticamente os sete métodos CRUD para verbos HTTP
- O controlador de API omite
createeedite é usado em conjunto comapiResource - A injeção de dependência elimina a necessidade de usar
newao criar objetos nos controladores; o container resolve dependências automaticamente - O Route Model Binding analisa automaticamente parâmetros de URL em instâncias de modelo
📝 Exercícios
-
Exercício Básico (⭐): Use
make:controller --resourcepara criar o ProductController para o ShopMetrics, registre rotas resource e implemente os métodosindexeshowpara retornar views simples. -
Exercício Avançado (⭐⭐): Crie um controlador de ação única
ExportOrdersControllerque implemente funcionalidade de exportação CSV, injete o OrderService para lidar com a conversão de dados e usestreamDownloadpara retornar o arquivo. -
Desafio (⭐⭐⭐): Projetar um
TenantProductControllerque use Route Model Binding para analisar{tenant}e{product}, e implemente operações CRUD de produtos isoladas por tenant para garantir que os usuários só possam gerenciar produtos dentro de seu próprio tenant.



