Insights into Company Bills & Payments
← PHP & Laravel

Laravel 5-Year Interview (Q26–50): Routing, Validation & Security

Understand the idea. Compare it. Explain it. Test yourself.

Question 1 of 25

Q26. What is routing in Laravel?

Solution and explanation

  • Routing decides which code runs for an incoming HTTP request.
  • Laravel matches the request's method and URL against the routes registered in routes/web.php and routes/api.php, in the order they were defined, and calls the first match.
Route::get('/products', [ProductController::class, 'index']);      // GET /products -> ProductController@index
Route::post('/products', [ProductController::class, 'store']);
Route::put('/products/{product}', [ProductController::class, 'update']);
Route::patch('/products/{product}', [ProductController::class, 'update']);
Route::delete('/products/{product}', [ProductController::class, 'destroy']);
  • HTTP methods communicate intent, and browsers, proxies and caches rely on them:
GET     retrieve; safe and idempotent; may be cached
POST    create or trigger an action; not idempotent (two POSTs can create two orders)
PUT     replace a resource completely; idempotent
PATCH   change part of a resource
DELETE  remove a resource; idempotent
  • E-commerce example: a GET must never change data.
  • If "add to cart" were a GET link, a browser prefetching links or a search-engine crawler could fill carts.
  • Changes always go through POST/PUT/PATCH/DELETE, which also get CSRF protection in web routes.
  • Route parameters and constraints:
Route::get('/orders/{order}', [OrderController::class, 'show'])->whereNumber('order');
Route::get('/blog/{slug}', [BlogController::class, 'show'])->where('slug', '[a-z0-9-]+');
Route::get('/reports/{year?}', [ReportController::class, 'yearly']);   // optional parameter
  • Fallback and redirects:
Route::redirect('/shop', '/products', 301);
Route::fallback(fn () => response()->view('errors.404', [], 404));
  • Performance in production: php artisan route:cache compiles all routes into one file so Laravel does not rebuild the route table on every request.
  • Closure routes cannot be cached, which is one reason production apps use controller routes.
  • Common mistakes: defining /products/{product} before /products/export (the second never matches because "export" is treated as a product id), and using GET for actions that change state.
  • Interview answer: "Routing maps an HTTP method and URI to a controller action.
  • I keep routes thin, use the correct verbs, constrain parameters, group shared middleware and prefixes, name every route, and cache routes in production."
Practice explaining this
Question 2 of 25

Q27. What is route model binding?

Solution and explanation

  • Route model binding turns a route parameter into an Eloquent model automatically.
  • Instead of looking up the record yourself, you type-hint the model and Laravel fetches it, returning 404 if it does not exist.
  • Without binding:
Route::get('/products/{id}', function ($id) {
    $product = Product::findOrFail($id);
    return $product;
});
With implicit binding (the parameter name must match the variable name):
Route::get('/products/{product}', function (Product $product) {
    return $product;
});

// /products/10  ->  SELECT * FROM products WHERE id = 10 LIMIT 1  ->  Product model, or 404
  • In a controller:
public function show(Product $product)
{
    return new ProductResource($product);
}
  • Scoped bindings are important for security in multi-tenant systems.
  • Banking example: a customer may only open statements of their own account.
Route::get('/accounts/{account}/statements/{statement}', [StatementController::class, 'show'])
    ->scopeBindings();

// Laravel resolves the statement through the account:
// $account->statements()->where('id', $statementId)->firstOrFail()
// so /accounts/1/statements/999 returns 404 if statement 999 belongs to account 2.
  • Scoping prevents one class of bug (mixing parent and child ids) but it is not authorization: you still check that the logged-in user owns the account with a policy.
  • Soft-deleted models are excluded by default; use ->withTrashed() on the route when an admin screen must open deleted records.
  • Interview answer: "Route model binding resolves models from route parameters, keeps controllers short and returns 404 automatically.
  • For nested resources I use scoped bindings, and I still authorize access with policies."
Practice explaining this
Question 3 of 25

Q28. Implicit vs explicit route model binding?

Solution and explanation

  • Implicit binding works by convention: the route parameter name matches a type-hinted model variable.
Route::get('/users/{user}', [UserController::class, 'show']);

public function show(User $user) { return $user; }   // {user} -> User $user
  • Explicit binding registers the resolution yourself, normally in a service provider's boot() method.
  • Use it when the parameter name does not match a model, or when resolving needs custom logic.
// AppServiceProvider::boot()
Route::model('member', User::class);                 // {member} resolves to User

Route::bind('order', function (string $value) {      // custom lookup: order number instead of id
    return Order::where('order_number', $value)->firstOrFail();
});
  • E-commerce example: customers see /orders/ORD-2026-000187 instead of /orders/187, so internal ids are not exposed and order numbers cannot be guessed by counting.
  • Payment example with stricter rules: only resolve payments that are not archived.
Route::bind('payment', fn (string $value) => Payment::query()
    ->where('reference', $value)
    ->whereNull('archived_at')
    ->firstOrFail());
  • When to use which: implicit binding for the normal "id or slug" case; explicit binding when the lookup column, scope or extra conditions differ.
  • For "use slug instead of id" you usually do not need explicit binding at all (see Q29).
  • Interview answer: "Implicit binding is convention-based and covers most cases.
  • I use explicit binding with Route::bind when the URL identifier is not the primary key or the lookup needs extra conditions."
Practice explaining this
Question 4 of 25

Q29. What is custom route key binding?

Solution and explanation

By default binding searches by the primary key (id). You can change the column either for the whole model or for a single route.

For the whole model:

class Product extends Model
{
    public function getRouteKeyName(): string
    {
        return 'slug';
    }
}

// /products/samsung-galaxy-s25  ->  WHERE slug = 'samsung-galaxy-s25'

For one route only (Laravel 7+):

Route::get('/products/{product:slug}', [ProductController::class, 'show']);
Route::get('/admin/products/{product}', [AdminProductController::class, 'edit']);   // still by id

Generating URLs uses the same key automatically: route('products.show', $product) produces /products/samsung-galaxy-s25.

Why it matters in production:

  • SEO: readable URLs rank better and are shareable.
  • Security: sequential ids reveal business volume (order 1001, 1002…) and invite enumeration.
  • Stability: slugs survive data migrations where ids change.

Requirements: the column must be unique and indexed, otherwise every request does a full table scan and two products could match.

$table->string('slug')->unique();

Slug changes break old links. A common pattern stores old slugs and redirects them with 301.

Interview answer: "I override getRouteKeyName or use {model:column} in the route, make the column unique and indexed, and keep ids out of public URLs."

Practice explaining this
Question 5 of 25

Q30. What are named routes?

Solution and explanation

  • A named route gives a URL a stable identifier so the rest of the application never hardcodes paths.
Route::get('/products', [ProductController::class, 'index'])->name('products.index');
Route::get('/products/{product}', [ProductController::class, 'show'])->name('products.show');

route('products.index');                       // http://site.test/products
route('products.show', $product);              // http://site.test/products/samsung-a35
route('products.index', ['sort' => 'price']);  // extra keys become the query string ?sort=price
redirect()->route('orders.show', $order);
  • Why: when marketing renames /products to /catalog, you change one route definition; every link, redirect, email and test that uses route('products.index') keeps working.
  • Checking where you are (navigation highlighting):
@if(request()->routeIs('products.*')) class="active" @endif
  • Signed URLs build on names.
  • Banking example: an emailed link to download a statement that expires after 30 minutes and cannot be tampered with.
$url = URL::temporarySignedRoute('statements.download', now()->addMinutes(30), ['statement' => $statement]);

Route::get('/statements/{statement}/download', [StatementController::class, 'download'])
    ->name('statements.download')->middleware('signed');
  • Naming convention: resource.action (orders.index, orders.store), and group prefixes such as admin. for admin routes. php artisan route:list shows every name.
  • Interview answer: "I name every route and generate URLs with route().
  • It decouples code from URL structure, enables routeIs() checks and signed URLs."
Practice explaining this
Question 6 of 25

Q31. What are route groups?

Solution and explanation

  • Route groups apply shared settings (middleware, URL prefix, name prefix, controller, domain) to many routes at once, so each route does not repeat them.
Route::middleware(['auth', 'verified'])->group(function () {
    Route::get('/dashboard', DashboardController::class)->name('dashboard');
    Route::get('/orders', [OrderController::class, 'index'])->name('orders.index');
});

Route::prefix('admin')->name('admin.')->middleware(['auth', 'can:access-admin'])->group(function () {
    Route::get('/users', [Admin\UserController::class, 'index'])->name('users.index');   // /admin/users, admin.users.index
    Route::get('/orders', [Admin\OrderController::class, 'index'])->name('orders.index');
});

Route::controller(PaymentController::class)->prefix('payments')->name('payments.')->group(function () {
    Route::post('/', 'store')->name('store');
    Route::post('/{payment}/refund', 'refund')->name('refund');
});
  • Nested groups merge settings: middleware accumulates, prefixes join, names concatenate.
  • Banking example with layered protection:
Route::prefix('banking')->middleware(['auth:sanctum', 'throttle:banking'])->group(function () {
    Route::get('/accounts', [AccountController::class, 'index']);
    Route::middleware('two-factor')->group(function () {
        Route::post('/transfers', [TransferController::class, 'store']);         // money movement needs 2FA too
        Route::post('/beneficiaries', [BeneficiaryController::class, 'store']);
    });
});
  • Subdomain routing for multi-tenant SaaS:
Route::domain('{tenant}.shop.test')->group(function () { ... });
  • Common mistake: putting one sensitive route outside the protected group by accident. php artisan route:list -v shows the middleware actually applied to each route; review it before every release.
  • Interview answer: "Groups centralise middleware, prefixes and names.
  • I nest them to layer protection, for example auth for all banking routes and an extra two-factor middleware for transfers."
Practice explaining this
Question 7 of 25

Q32. What is resource routing?

Solution and explanation

  • Route::resource registers the seven conventional CRUD routes for a controller in one line.
Route::resource('products', ProductController::class);
Method      URI                       Action    Route name
GET         /products                 index     products.index
GET         /products/create          create    products.create
POST        /products                 store     products.store
GET         /products/{product}       show      products.show
GET         /products/{product}/edit  edit      products.edit
PUT/PATCH   /products/{product}       update    products.update
DELETE      /products/{product}       destroy   products.destroy
  • APIs do not need the HTML form pages:
Route::apiResource('orders', OrderController::class);                // index, store, show, update, destroy
Route::resource('photos', PhotoController::class)->only(['index', 'show']);
Route::resource('payments', PaymentController::class)->except(['destroy']);   // payments are never deleted
  • Nested and shallow resources:
Route::resource('orders.items', OrderItemController::class)->shallow();
// index/store: /orders/{order}/items   show/update/destroy: /items/{item}
  • Generate a matching controller with php artisan make:controller ProductController --resource --model=Product.
  • Non-CRUD actions get their own route next to the resource (define it before the resource so {product} does not swallow it):
Route::post('/orders/{order}/cancel', CancelOrderController::class)->name('orders.cancel');
Route::apiResource('orders', OrderController::class);
  • Payment design note: financial records are corrected with reversal or refund transactions, never with DELETE, so ->except(['destroy']) documents that rule in the routes.
  • Interview answer: "Resource routes give consistent RESTful URLs and names.
  • I use apiResource for APIs, only/except to remove actions that must not exist, and separate routes or invokable controllers for domain actions like cancel or refund."
Practice explaining this
Question 8 of 25

Q33. What is a single-action (invokable) controller?

Solution and explanation

A single-action controller handles exactly one operation through __invoke(). It suits business operations that are bigger than CRUD and deserve their own class.

php artisan make:controller ProcessPaymentController --invokable

class ProcessPaymentController extends Controller
{
    public function __construct(private PaymentService $payments) {}

    public function __invoke(ProcessPaymentRequest $request, Order $order)
    {
        $this->authorize('pay', $order);

        $payment = $this->payments->charge($order, $request->validated('method'), $request->header('Idempotency-Key'));

        return new PaymentResource($payment);
    }
}

Route::post('/orders/{order}/pay', ProcessPaymentController::class)->name('orders.pay');

Why teams use them:

  • Each class has one reason to change, so a 1,500-line PaymentController with create/refund/cancel/capture/webhook does not happen.
  • Dependencies are specific: the refund controller injects RefundService only.
  • Files are easy to find: app/Http/Controllers/Payments/RefundPaymentController.php.
  • Tests target one action.

Banking examples: TransferFundsController, BlockCardController, DownloadStatementController. E-commerce: ApplyCouponController, CheckoutController.

Keep the controller thin either way: validate (Form Request), authorize (policy), call a service or action class, return a response. Business rules live in the service so the same logic runs from a queue job or console command.

Interview answer: "For operations that are not simple CRUD I use invokable controllers. Each one validates, authorizes and delegates to a service, which keeps controllers small and logic reusable."

Practice explaining this
Question 9 of 25

Q34. What is middleware priority and order?

Solution and explanation

Middleware runs as an onion: each layer runs code before passing the request inward with $next($request), and can run code after the response comes back out. The order therefore matters.

Request
  -> EncryptCookies -> StartSession -> Authenticate -> SubstituteBindings -> Authorize -> Controller
  <- (response travels back out through the same layers in reverse)

Why order matters, with real failures:

  • Authorization before authentication: auth()->user() is null, so the check fails or, worse, a careless check passes.
  • Route model binding before authentication: an anonymous user gets 404 vs 403 differences that reveal whether a record exists.
  • Logging before the request id middleware: logs lack the correlation id.

Where order is defined:

  • The global middleware list and middleware groups (web, api) in the HTTP kernel (app/Http/Kernel.php in Laravel 9/10, bootstrap/app.php in 11+).
  • $middlewarePriority forces a relative order for specific middleware no matter how routes list them.
// Laravel 9/10: app/Http/Kernel.php
protected $middlewarePriority = [
    \Illuminate\Session\Middleware\StartSession::class,
    \App\Http\Middleware\Authenticate::class,
    \Illuminate\Routing\Middleware\SubstituteBindings::class,
    \Illuminate\Auth\Middleware\Authorize::class,
];

Payment API example of a deliberate order:

Route::middleware(['auth:sanctum', 'throttle:payments', 'idempotency'])->post('/payments', ...);
// 1. know who the caller is  2. rate-limit per user  3. return the stored response for a repeated Idempotency-Key

Terminable middleware runs after the response is sent (terminate method), useful for metrics without slowing the user.

Debugging: php artisan route:list -v shows the exact middleware for each route.

Interview answer: "Middleware is an ordered pipeline. Authentication must precede authorization and binding-dependent checks; I control it with groups and middlewarePriority and verify with route:list."

Practice explaining this
Question 10 of 25

Q35. How do you create custom middleware?

Solution and explanation

php artisan make:middleware EnsureActiveSubscription

class EnsureActiveSubscription
{
    public function handle(Request $request, Closure $next, string $plan = 'premium'): Response
    {
        $user = $request->user();

        if (! $user || ! $user->subscription?->allows($plan)) {
            return $request->expectsJson()
                ? response()->json(['success' => false, 'message' => 'An active ' . $plan . ' plan is required.'], 402)
                : redirect()->route('billing.plans')->with('error', 'Upgrade to use this feature.');
        }

        return $next($request);
    }
}
  • Register an alias (Laravel 9/10 in $routeMiddleware or $middlewareAliases; Laravel 11 in bootstrap/app.php), then use it with parameters:
'subscribed' => \App\Http\Middleware\EnsureActiveSubscription::class,

Route::middleware('subscribed:enterprise')->group(function () {
    Route::get('/reports/export', ExportReportController::class);
});
  • Real middleware a payments team writes:
// Idempotency: a retried POST with the same key returns the first result instead of charging twice
public function handle(Request $request, Closure $next): Response
{
    $key = $request->header('Idempotency-Key');
    if (! $key) return response()->json(['message' => 'Idempotency-Key header is required.'], 400);

    $cacheKey = 'idem:' . $request->user()->id . ':' . $key;
    if ($saved = Cache::get($cacheKey)) return response()->json($saved['body'], $saved['status']);

    $response = $next($request);
    if ($response->isSuccessful()) {
        Cache::put($cacheKey, ['body' => $response->getData(true), 'status' => $response->getStatusCode()], now()->addDay());
    }
    return $response;
}
  • Other common examples: forcing JSON responses on API routes, adding security headers, setting the locale from a header, logging a request id, maintenance windows for a single module.
  • Rules: middleware should be fast and generic; anything that needs a specific model is usually a policy instead (see Q45).
  • Interview answer: "I generate middleware, implement handle with $next, return early to block, accept parameters for flexibility and register an alias.
  • Typical ones I have written are idempotency, subscription checks and security headers."
Practice explaining this

Showing questions 1–10 of 25