404 Not Found

404 Not Found


nginx

Laravel Controllers and Request Handling

The controller is Laravel's "business commander"—it receives requests, coordinates models and views, and returns responses; all business logic is orchestrated from here.

1. What You'll Learn


2. A True Story of a Backend Developer

(1) Pain Point: A 2,000-line "God Controller"

Early on, Bob crammed all of ShopMetrics' logic into a single ShopController—product management, order processing, user authentication, and report generation were all bundled together. Once the code ballooned to 2,000 lines, changing one feature could potentially break another. When Charlie took over, it took him three days just to untangle the code structure, and Alice had to wait two weeks for a minor feature request.

(2) Solution for the Resource Controller

The Laravel Resource Controller breaks down CRUD operations into seven separate methods, with each method performing a single task. When combined with route bindings, the URLs and methods are automatically mapped to one another.

BASH
# One command generates a complete CRUD controller
php artisan make:controller ShopController --resource
# Creates: index(), create(), store(), show(), edit(), update(), destroy()

(3) Revenue

After Bob refactored the code using the resource controller, each method was limited to 30 lines or fewer; Alice's small feature was completed in two days instead of two weeks; and Charlie no longer worried about breaking existing functionality when taking on new features.


3. Basic Controller

(1) Creation and Structure

BASH
php artisan make:controller HomeController
# Creates: 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) Single-Action Controller __invoke

When a controller needs only one method, use __invoke instead of a named method.

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);
    }
}

// Route registration
Route::post('/reports/generate', GenerateReportController::class);
Dimension Standard Controller Single-Action Controller
Number of methods Multiple 1 __invoke
Route Registration [Ctrl::class, 'method'] Ctrl::class
Use Cases Related Operations Single-Responsibility Operations
Example ShopController GenerateReportController

(1) ▶ Example: ShopMetrics Single-Action Controller

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');

Output:

TEXT
// Execution Successful

4. Resource Controller

(1) Create a Resource Controller

BASH
php artisan make:controller ShopController --resource

Automatically generate 7 CRUD methods:

HTTP Verb URI Method Purpose
GET /shops index list
GET /shops/create create Create Form
POST /shops store Save New Record
GET /shops/{shop} show Details
GET /shops/{shop}/edit edit Edit Form
PUT/PATCH /shops/{shop} update Update
DELETE /shops/{shop} destroy Delete

(2) Route Registration

PHP
// Single line registers all 7 routes
Route::resource('shops', ShopController::class);

// Limit to specific methods only
Route::resource('shops', ShopController::class)->only([
    'index', 'show', 'store', 'update', 'destroy',
]);

// Exclude specific methods
Route::resource('shops', ShopController::class)->except([
    'create', 'edit',
]);

(1) ▶ Example: ShopMetrics Shop Resource Controller

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', 'Shop created successfully.');
    }

    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', 'Shop updated successfully.');
    }

    public function destroy(Shop $shop): RedirectResponse
    {
        $shop->delete();
        return redirect()->route('shops.index')
            ->with('success', 'Shop deleted successfully.');
    }
}

Output:

TEXT
// Execution Successful

5. API Resource Controller

(1) Create an API controller

BASH
php artisan make:controller Api/ShopController --api

--api is equivalent to --resource --except=create,edit, because the API does not require a form page.

Method Web Resources API Resources
index
create
store
show
edit
update
destroy

(1) ▶ Example: ShopMetrics API Controller

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();
    }
}

Output:

TEXT
// Execution Successful

6. Dependency Injection

(1) Constructor Injection

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) Method-Level Injection

PHP
class ReportController extends Controller
{
    public function show(Request $request, Report $report): View
    {
        // $request injected by container
        // $report resolved via Route Model Binding
        return view('reports.show', compact('report'));
    }
}

(3) Route Model Binding

PHP
// Implicit binding — type-hint in controller method
Route::get('/shops/{shop}', [ShopController::class, 'show']);

public function show(Shop $shop): View
{
    // $shop is automatically fetched from DB by {shop}
    // Equivalent to: Shop::findOrFail($shop)
    return view('shops.show', compact('shop'));
}

// Custom key — bind by slug instead of id
Route::get('/shops/{shop:slug}', [ShopController::class, 'show']);
// Now: /shops/alice-store → Shop where slug = 'alice-store'
Injection Method Use Cases Lifecycle
Constructor Required by all controller methods Entire request
Method-level Required only for specific methods Single method
Route Model Binding Automatically Retrieve Models from URLs Single Method

(1) ▶ Example: ShopMetrics Order Controller with Dependency Injection

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'));
    }
}

Output:

TEXT
// Execution Successful

7. Controller Middleware

(1) Constructor Assignment

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) Route-Level Allocation

PHP
Route::middleware(['auth', 'admin'])->group(function () {
    Route::resource('plans', PlanController::class);
});
Allocation Location Granularity Use Cases
Constructor Method-level Different methods within a controller require different middleware
Route Definition Route Group A group of routes that share middleware
Kernel Global Global Must be executed for all requests

(1) ▶ Example: ShopMetrics Admin Panel Controller

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', 'Plan created.');
    }
}

Output:

TEXT
// Execution Successful

8. Request → Controller → Model → View Response Chain

100%
sequenceDiagram
    participant C as Client
    participant R as Router
    participant M as Middleware
    participant Ctrl as Controller
    participant Model as Model
    participant V as View

    C->>R: HTTP Request
    R->>M: Run middleware pipeline
    M->>Ctrl: Call controller method
    Ctrl->>Model: Query data
    Model-->>Ctrl: Return results
    Ctrl->>V: Pass data to view
    V-->>Ctrl: Rendered HTML
    Ctrl-->>C: HTTP Response

9. Comprehensive Example: ShopMetrics Product CRUD Controller

PHP
// ============================================
// Comprehensive: ShopMetrics ProductController
// Covers: resource controller, 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', 'Product created successfully.');
    }

    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', 'Product updated successfully.');
    }

    public function destroy(Product $product): RedirectResponse
    {
        $this->authorize('delete', $product);
        $product->delete();
        return redirect()->route('products.index')
            ->with('success', 'Product deleted successfully.');
    }
}

❓ FAQ

Q How "lean" should a controller be?
A A controller should only act as a "coordinator"—receiving requests, calling the Service/Model, and returning responses. Business logic should be placed in the Service class, and data access in the Model/Repository; controllers should be kept to 10-30 lines per method.
Q When should you use a single-action controller?
A When an operation does not belong to any CRUD resource, such as "export a report," "send an email," or "generate an invoice." If a controller has only one public method, use __invoke.
Q What should I do if Route Model Binding cannot find a record?
A By default, a 404 error is returned. You can customize this behavior by using Route::bind() in RouteServiceProvider to define custom parsing logic, or by overriding the resolveRouteBindingQuery() method on the model.
Q What is the difference between dependency injection and new?
A new manually creates objects, requiring you to manage the dependency chain yourself; with DI, the Laravel container automatically resolves and injects dependencies, supporting interface binding, singleton management, and mock testing.
Q Why do API controllers return a Resource instead of JSON directly?
A The Resource class standardizes the JSON output format, allowing you to hide sensitive fields, rename fields, and nest related data. Returning the model directly would expose all fields and result in an inconsistent format.
Q Can I use DB::raw to write SQL in the controller?
A Yes, but it's not recommended. For complex queries, you should use Eloquent Scopes or the Query Builder methods. DB::raw bypasses Eloquent's security layer, making it prone to SQL injection.

📖 Summary


📝 Exercises

  1. Basic Exercise (⭐): Use make:controller --resource to create the ProductController for ShopMetrics, register resource routes, and implement the index and show methods to return simple views.

  2. Advanced Exercise (⭐⭐): Create a ExportOrdersController single-action controller that implements CSV export functionality, injects the OrderService to handle data conversion, and uses streamDownload to return the file.

  3. Challenge (⭐⭐⭐): Design a TenantProductController that uses Route Model Binding to parse {tenant} and {product}, and implement tenant-isolated product CRUD operations to ensure that users can only manage products within their own tenant.

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏