404 Not Found

404 Not Found


nginx

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


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.

BASH
# 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

BASH
php artisan make:controller HomeController
# Cria: app/Http/Controllers/HomeController.php
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.

BASH
php artisan make:controller GenerateReportController --invokable
PHP
// 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

PHP
// 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:

TEXT
// Execução bem-sucedida

4. Controlador Resource

(1) Criar um Controlador Resource

BASH
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

PHP
// 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

PHP
// 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:

TEXT
// Execução bem-sucedida

5. Controlador Resource de API

(1) Criar um controlador de API

BASH
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

PHP
// 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:

TEXT
// Execução bem-sucedida

6. Injeção de Dependência

(1) Injeção no Construtor

PHP
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

PHP
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

PHP
// 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

PHP
// 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:

TEXT
// Execução bem-sucedida

7. Middleware do Controlador

(1) Alocação no Construtor

PHP
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

PHP
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

PHP
// 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:

TEXT
// Execução bem-sucedida

8. Cadeia Requisição → Controlador → Modelo → View Resposta

100%
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

PHP
// ============================================
// 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

P Quão "enxuto" deve ser um controlador?
R Um controlador deve atuar apenas como um "coordenador"—recebendo requisições, chamando o Service/Model e retornando respostas. A lógica de negócio deve ser colocada na classe Service, e o acesso a dados no Model/Repository; controladores devem ter de 10 a 30 linhas por método.
P Quando se deve usar um controlador de ação única?
R Quando uma operação não pertence a nenhum recurso CRUD, como "exportar um relatório", "enviar um e-mail" ou "gerar uma fatura". Se um controlador tem apenas um método público, use __invoke.
P O que devo fazer se o Route Model Binding não encontrar um registro?
R Por padrão, um erro 404 é retornado. Você pode personalizar esse comportamento usando Route::bind() no RouteServiceProvider para definir lógica de análise personalizada, ou sobrescrevendo o método resolveRouteBindingQuery() no modelo.
P Qual é a diferença entre injeção de dependência e new?
R 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.
P Por que controladores de API retornam um Resource em vez de JSON diretamente?
R A classe 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.
P Posso usar DB::raw para escrever SQL no controlador?
R Sim, mas não é recomendado. Para consultas complexas, você deve usar Eloquent Scopes ou os métodos do Query Builder. DB::raw contorna a camada de segurança do Eloquent, tornando-o propenso a injeção de SQL.

📖 Resumo


📝 Exercícios

  1. Exercício Básico (⭐): Use make:controller --resource para criar o ProductController para o ShopMetrics, registre rotas resource e implemente os métodos index e show para retornar views simples.

  2. Exercício Avançado (⭐⭐): Crie um controlador de ação única ExportOrdersController que implemente funcionalidade de exportação CSV, injete o OrderService para lidar com a conversão de dados e use streamDownload para retornar o arquivo.

  3. Desafio (⭐⭐⭐): Projetar um TenantProductController que 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.

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%