LearnThatStack Ace your next interview

Laravel

Laravel 13. Older ways are marked. Checked Nov 2026 140 code snippets

The model

  • A request enters at public/index.php, boots the app, passes through middleware to a route, and returns through the same middleware.
  • The service container builds your classes. Type-hint a class in a constructor, and the container passes in the object.
  • Service providers run in two phases. Every register() binds services, then every boot() can safely use them.
  • An Eloquent model maps to one table, and each instance is one row. Reading a relation as a property runs a query, which is how N+1 starts.
  • Cache::get() looks static, but a facade fetches the real object from the container and calls it.
public/index.phpautoloader, appHTTP kernelregister, then bootMiddlewareglobal, then routeControllerthe matched routeINrequestpipeline$next($request)OUTResponseEARLYredirect, 403AFTERterminate()
A request goes IN through the kernel and middleware to the controller, and comes OUT the same way. Middleware can return EARLY. terminate() runs AFTER.

How a Laravel app runs

what Laravel is and what runs when

What Laravel is

Say what Laravel isA free, open-source PHP framework built on MVC.

TipIt favours convention over configuration. Eloquent is the ORM, Blade the template engine and Artisan the command-line tool.

Composer

Install the versions in composer.lockcomposer install
Get newer versions and rewrite the lock filecomposer update
Add a packagecomposer require <pkg>

TipComposer also builds the PSR-4 autoloader, so classes load from their file paths with no require.

Container & providers

how Laravel builds and connects your classes

Container and injection

Get a new object on each resolve$this->app->bind(A::class, B::class)
Build one object and reuse it$this->app->singleton(A::class, B::class)
Inject into a constructor or one action__construct(private Repo $repo)show(Request $r, User $user)

TipA concrete class needs no binding. Bind an interface to swap the class, or a test fake, in one place.

TrapIn a controller method, list injected dependencies before route parameters.

Service providers

Bind services, before other services are readyregister()
Use other services, after all are registeredboot()

TipYour own setup usually goes in AppServiceProvider. A deferred provider loads only when a binding is first requested.

Facades

Mock a facade in a testCache::shouldReceive('get')

Tip__callStatic() asks getFacadeAccessor() for a binding name, resolves that object and calls get() on it.

TrapA facade needs no constructor, so a class can depend on many services without showing it. This is scope creep.

Routes, middleware & input

send a request to the right code

Routes and parameters

Map a verb and URI to a controllerRoute::get('/users', [UserController::class, 'index'])
List every routephp artisan route:list
Create routes/api.php (11+)php artisan install:api
Capture a required or optional segment/user/{id}/hello/{name?}
Limit a segment with a regex, 404 if it fails->where('id', '[0-9]+')

Tipweb.php routes get sessions and cross-site request forgery (CSRF) protection. api.php routes get the /api prefix.

TrapLaravel passes parameters in order, so arguments must follow the placeholders. An optional one needs a default value.

Route groups and model binding

Share middleware across routesRoute::middleware('auth')->group($fn)
Share a URI prefix and a name prefixRoute::prefix('admin')->name('admin.')
Get a model in place of {post}, 404 if nonefn (Post $post) => $post
Look up by another column/posts/{post:slug}

Trap{post} binds only to a variable named $post.

Resource controllers

Generate the controllerphp artisan make:controller PostController --resource
Register the seven CRUD routesRoute::resource('posts', PostController::class)

TipThe seven are index, create, store, show, edit, update and destroy. Route::apiResource() leaves out create and edit.

Middleware and pipelines

Pass the request on, or return early to stop itreturn $next($request)
Add global middleware or an alias in bootstrap/app.php (11+)$middleware->append(A::class)$middleware->alias(['sub' => A::class])
Attach middleware to a route, or skip it->middleware('sub')->withoutMiddleware('sub')
Find middleware in an older appapp/Http/Kernel.php
Run your own pipeline, the class behind middlewareapp(Pipeline::class)->send($v)->through($pipes)->thenReturn()

TipCode before $next() runs on the way in. Code after it can change the response on the way out.

TrapYou can't skip global middleware per route. Only route middleware has withoutMiddleware().

Validate input

Validate in a controller$request->validate(['a' => 'required|max:255'])
Move the rules to a Form Requestphp artisan make:request StorePost$request->validated()
Write a custom rulephp artisan make:rule Uppercase

TipOn failure, a form redirects back with errors and old input. A request that expects JSON gets a 422.

Eloquent models

tables as classes, rows as objects

Eloquent or the query builder

Get rows as model objects, the Active Record wayUser::where('active', 1)->get()
Get plain rows, often faster for big reportsDB::table('users')->get()

TipEloquent also gives you relationships, timestamps, soft deletes, scopes and events.

Relationships

Link one to one or one to many, and backhasOne(Phone::class)hasMany(Post::class)belongsTo(User::class)
Link many to manybelongsToMany(Role::class)
Reach rows through another modelhasManyThrough(A::class, B::class)
Belong to more than one parent typemorphTo()

TipThe default pivot table is role_user. $user->posts() builds a query, and $user->posts loads rows.

N+1 queries

Eager load with the query, or after itPost::with('user')->get()$posts->load('user')
Throw on any lazy load outside productionModel::preventLazyLoading()

Tipwith() fetches every related row in one WHERE id IN (...) query.

TrapReading $post->user in a loop runs one query per post.

Accessors, mutators and scopes

Change a value on read and on write (9+)Attribute::make(get: $g, set: $s)
Recognise the old stylegetNameAttribute()setNameAttribute()
Reuse a condition by namescopeActive($query)User::active()
Apply a global scope, or turn it off for a querystatic::addGlobalScope('a', $fn)withoutGlobalScope('a')

TrapFor JSON to an array, a cast is shorter than an accessor.

Soft deletes

Turn soft deletes onuse SoftDeletes;$table->softDeletes();
Read hidden rowsPost::withTrashed()Post::onlyTrashed()
Restore a row, or delete it for good$post->restore()$post->forceDelete()

Tipdelete() runs an UPDATE that fills deleted_at, and a global scope hides those rows.

TrapA unique index on email still counts trashed rows. Laravel doesn't soft delete related models for you.

Model events

Run code before or after the first savecreatingcreated
Group a model's handlers in a classUser::observe(UserObserver::class)

Tipsaving and saved fire on every save, even when nothing changed.

TrapPost::where(...)->delete() never loads the models, so it fires no delete events.

Database & collections

change the schema, fill it, handle big data

Migrations and seeders

Run pending migrations, or roll back the last batchphp artisan migratephp artisan migrate:rollback
Drop all tables and run every migrationphp artisan migrate:fresh
Save ten users with fake dataUser::factory(10)->create()
Run DatabaseSeederphp artisan db:seed

Tipup() applies a change and down() reverses it.

TrapNever edit a migration that already ran in production. Add a new one.

Transactions

DatabasetransactioncommitPlain jobreads old dataDelayedreads new data

A plain job can run before the commit and read old data. With afterCommit(), the delayed job waits for the commit.

Allow 3 deadlock tries, or control it by handDB::transaction($fn, 3)DB::beginTransaction()
Wait for the commitJob::dispatch()->afterCommit()DB::afterCommit($fn)

TipThe closure commits on return and rolls back on an exception. Nested transactions use savepoints.

Collections and lazy collections

Chain steps on an arraycollect($a)->filter($f)->sum()
Read a huge file through a PHP generatorLazyCollection::make($gen)
Walk a big table by model, or in chunksUser::cursor()User::lazy()

TipA chain reads top to bottom. array_filter() and array_map() nest, and take their arguments in different orders.

Trapcursor() can't eager load, and PDO may still buffer every raw row. A huge table can still exhaust memory.

Blade views

templates, layouts and reusable parts

Blade templates and layouts

Print a value with HTML escaping{{ $name }}
Print HTML you trust{!! $html !!}
Mark a section in the layout@yield('title', 'Default')
Pick the layout and fill a section@extends('layouts.app')@section('title', 'Posts')

TipBlade compiles each template to plain PHP and caches it until the template changes.

Trap{!! !!} skips escaping, so user data there allows cross-site scripting (XSS), a script injected into the page.

View data and components

Pass values from a controllerview('home', compact('user'))
Add data to one view, or to every viewView::composer('home', $fn)View::share('name', $v)
Render a component and pass values<x-alert type="error" :open="true">
Print a component's content and attributes{{ $slot }}{{ $attributes }}

TipA colon prefix passes a PHP expression. An anonymous component is only a Blade file, with no class.

Login, roles & security

who you are and what you may do

Authentication and authorization

Authenticate the user, or give 401Auth::attempt($credentials)
Authorize an action, or give 403can:edit,post
Configure guards and user providersconfig/auth.php
Regenerate the session after login$request->session()->regenerate()
Generate login screens (12+)Starter kits replace Breeze and Jetstream.

TipAuthentication checks who you are, and authorization checks what you may do. Authentication comes first.

TrapIf you don't regenerate the session, an attacker can plant a known session ID before login. This is session fixation.

Gates and policies

Define a gate in a provider's boot()Gate::define('edit', $fn)
Get a boolean, or a 403 responseGate::allows('edit', $post)Gate::authorize('edit', $post)
Generate a policy, with a method per actionphp artisan make:policy PostPolicy --model=Post
Check a rule in code, or in a view$user->can('edit', $post)@can('edit', $post)

TipUse a gate for an action with no model, and a policy for one model's rules. A policy's before() can allow admins everything.

Sanctum

Issue a token to a mobile app$user->createToken('name')->plainTextToken
Log in a same-domain single-page appUse the normal session cookie.

TipUse Sanctum when you control the clients, and Passport only for a full OAuth server for third-party apps.

CSRF and other protection

Add the token to a form@csrf
Pass same-origin requests with no token (13)Sec-Fetch-Site
Exclude webhook URIs$middleware->preventRequestForgery(except: ['stripe/*'])
Hash a password, with Bcrypt by defaultHash::make($password)

TipAnother site can make a browser submit a form, but it can't read the token. A mismatch returns 419.

TrapEloquent and the query builder bind parameters with PDO. Raw SQL with user input can still allow SQL injection.

REST APIs & tests

JSON endpoints, and how to test your app

REST APIs and versions

Turn a model or a page into JSONnew PostResource($post)PostResource::collection($page)
Protect routes and limit their traffic->middleware(['auth:sanctum', 'throttle:60,1'])
Put the version in the URLRoute::prefix('v1')->name('v1.')

TipA header version keeps URLs clean, but it's harder to test, cache and read in logs. So URL versions are more common.

TrapWithout a name prefix, both versions register posts.index, so route:cache fails.

Tests

Test a route$this->get('/a')->assertStatus(200)
Check a row, on a clean database$this->assertDatabaseHas('posts', $row)use RefreshDatabase;
Stop real emails and jobsMail::fake()Queue::fake()

Tipphp artisan test runs Pest or PHPUnit. The kinds are unit, feature and browser tests with Laravel Dusk, and the docs advise mostly feature tests.

TrapUnit tests don't boot Laravel, so they can't use the database or factories.

Queues, events & real time

work that runs outside the request

Queues and batches

Dispatch a job, delayed or to a named queuePod::dispatch($p)->delay(now()->addMinutes(10))->onQueue('mail')
Start a worker, with 3 tries before failed_jobsphp artisan queue:work --tries=3
Pick where jobs waitdatabase by default, or Redis for high volume.
Run jobs as a groupBus::batch($jobs)->dispatch()

TipBatched jobs need the Batchable trait. One failed job cancels the batch by default, unless you call allowFailures().

TrapA worker keeps the booted app in memory, so it runs old code until queue:restart. Keep it running with Supervisor.

Events and broadcasting

Announce an event, the observer patternevent(new Shipped($order))
Find listeners in an older app, in $listenEventServiceProvider
Send an event to the browserimplements ShouldBroadcast
Listen for a custom broadcastAs() name, with a leading dotEcho.channel('orders').listen('.order.shipped', cb)
Limit who can listen, in routes/channels.phpnew PrivateChannel('o.1')

TipLaravel finds listeners by scanning app/Listeners. A listener that implements ShouldQueue runs on the queue.

TrapBroadcasting goes through the queue to a server such as Laravel Reverb, so a queue worker must be running.

Task scheduling

Define a task in routes/console.php (11+)Schedule::command('send')->daily()
Add the one cron entry on the server* * * * * cd /path && php artisan schedule:run
Skip overlaps, or run on one server only->withoutOverlapping()->onOneServer()

TipA long task should dispatch a queued job, so it doesn't delay the other tasks.

Caching & deployment

speed the app up and deploy it

Cache data and routes

Return the cached value, or run and store itCache::remember('k', 3600, $fn)
Store a value for some secondsCache::put('k', $v, $secs)
Serve a stale value while it refreshesCache::flexible('k', [5, 10], $fn)
Cache routes on deploy, or clear themphp artisan route:cachephp artisan route:clear

TipSwitch stores in config. New apps use the database store. File, database and DynamoDB stores don't support tags.

TrapThe route cache is a snapshot. Laravel ignores route file changes until you rebuild it, so leave it off locally.

Find and fix slow code

Profile a slow request firstUse Telescope or the query log.
Keep the booted app in memoryLaravel Octane, on FrankenPHP, Swoole or RoadRunner.

TipSelect only the columns you need, and index the ones in WHERE, JOIN and ORDER BY. Enable OPcache on the server.

TrapUnder Octane, state in singletons or static properties leaks between requests. Reset it on each request.

Deploy

Install without dev packagescomposer install --optimize-autoloader --no-dev
Run migrations with no promptphp artisan migrate --force
Cache config, routes, events and viewsphp artisan optimize
Hide debug pages, which expose secretsAPP_DEBUG=false
Set the app key once, on the first deployphp artisan key:generate

TrapA new APP_KEY breaks encrypted data and sessions. After config:cache, env() returns null outside config files.

Found this useful? Pass it on.
Pro · $10/mo

This sheet stays free. With Pro, you can:

  • Every answer on this sheet. Read the full answer to all 55 questions it links, not just the 7 free ones.
  • My sheet. The cards you star and the ones you miss in Quiz me, from every sheet, on one page to quiz and print.
  • PDFs. Download this sheet and My sheet, laid out for A4, with every question a link to its answer.
  • The rest of Pro. Every answer in the question library, and 1,000 AI credits a month.