🔴

Top 100 Laravel Interview Questions

Top 100 Laravel interview questions covering routing, Eloquent ORM, middleware, queues, authentication, testing, and advanced patterns.

100 Questions
Filter:

Laravel is an open-source PHP web application framework created by Taylor Otwell, first released in 2011. It follows the MVC (Model-View-Controller) architectural pattern and is built on top of several Symfony components. Laravel is known for its elegant, expressive syntax that aims to make web development enjoyable and productive. It provides out-of-the-box features for routing, authentication, sessions, caching, queues, events, database ORM (Eloquent), and more. It is consistently the most popular PHP framework, with a large ecosystem of first-party packages (Sanctum, Passport, Horizon, Telescope) and a thriving community.

Beginner

MVC (Model-View-Controller) is the architectural pattern Laravel is built on. The Model represents the data layer — it interacts with the database via Eloquent ORM and contains business logic. The View is the presentation layer — Blade templates that render HTML output to the user. The Controller is the intermediary — it receives HTTP requests, processes data (using models), and returns a response (usually a view or JSON). This separation of concerns makes code more organized, maintainable, and testable. In Laravel, requests flow: Router → Middleware → Controller → Model → View → Response.

Beginner

Artisan is Laravel's built-in command-line interface. It provides dozens of helpful commands for common development tasks. Examples: php artisan make:model User (generate a model), php artisan make:controller UserController --resource (generate a resource controller), php artisan migrate (run database migrations), php artisan tinker (REPL for interacting with the app), php artisan serve (start a local dev server), php artisan queue:work (start queue worker). You can create custom Artisan commands with php artisan make:command. Artisan drastically speeds up development by automating repetitive file creation and maintenance tasks.

Beginner

Laravel's routing system maps incoming HTTP requests to specific controller actions or closures. Define routes in routes/web.php (for web routes with session/CSRF) or routes/api.php (for stateless API routes). Example: Route::get("/users", [UserController::class, "index"]);. Laravel supports all HTTP verbs: get, post, put, patch, delete. Route parameters: Route::get("/users/{id}", ...). Optional parameters: {id?}. Constraints: ->where("id", "[0-9]+"). Route naming: ->name("users.index") lets you generate URLs with route("users.index") instead of hardcoding paths.

Beginner

A Controller in Laravel handles the HTTP request logic — it sits between routes and models/views. Generate with php artisan make:controller UserController. Controllers live in app/Http/Controllers/. A resource controller (--resource flag) generates seven RESTful methods automatically: index (list all), create (show create form), store (save new), show (show one), edit (show edit form), update (save edits), destroy (delete). Register all seven with one line: Route::resource("users", UserController::class). Controllers can use constructor injection — Laravel's service container automatically resolves dependencies.

Beginner

Blade is Laravel's powerful, lightweight templating engine. Blade files use the .blade.php extension and live in resources/views/. Blade compiles to plain PHP and caches the result. Key features: template inheritance with @extends("layout") and @section("content"), includes with @include("partial"), components with @component or <x-button>, directives like @if, @foreach, @forelse, @while, @switch, output escaping with {{ $var }} (auto-escaped) vs {!! $var !!} (raw), and CSRF field with @csrf. You can also create custom Blade directives.

Beginner

Migrations are like version control for your database schema. Instead of writing raw SQL, you define schema changes in PHP using Laravel's Schema Builder. Generate: php artisan make:migration create_users_table. Each migration has an up() method (apply changes) and a down() method (rollback). Run: php artisan migrate. Rollback: php artisan migrate:rollback. Migrations allow your team to share and synchronize the database structure — the schema is in version control alongside the code. The migrations table tracks which migrations have been run. Use php artisan migrate:fresh --seed during development to reset and re-seed the database.

Beginner

Eloquent is Laravel's built-in ORM (Object-Relational Mapper) that maps database tables to PHP classes. Each Eloquent model represents a database table. By convention, User model maps to the users table. Eloquent provides a beautiful ActiveRecord implementation — CRUD operations: User::find(1), User::create(["name" => "Alice"]), $user->update(["name" => "Bob"]), $user->delete(). It has a fluent query builder: User::where("active", true)->orderBy("name")->paginate(15). Eloquent handles relationships (hasOne, hasMany, belongsTo, belongsToMany, morphTo), casting, scopes, observers, events, and more. It dramatically reduces database boilerplate.

Beginner

Middleware is code that runs between an incoming HTTP request and the controller. It is a filtering mechanism — inspecting and optionally transforming requests or responses. Built-in Laravel middleware includes: Authenticate (redirects unauthenticated users), VerifyCsrfToken (validates CSRF tokens on POST requests), ThrottleRequests (rate limiting), EncryptCookies. Create custom middleware: php artisan make:middleware EnsureEmailVerified. Apply to routes: Route::middleware(["auth"])->group(...). Global middleware runs on every request. Middleware can run before the request (guard checks, request modification) or after the response (adding headers, logging).

Beginner

The .env file stores environment-specific configuration values that should not be committed to version control (database credentials, API keys, mail settings). Laravel uses the dotenv library to load this file. Access values with env("DB_HOST", "localhost") (second argument is the default). Configuration files in config/ use env() to read .env values — always access configuration through config("database.connections.mysql.host") in application code, not env() directly (config values are cached). The .env.example file is committed to VCS as a template. Never commit the actual .env file — add it to .gitignore.

Beginner

Service Providers are the central place for bootstrapping Laravel applications. They register service container bindings, event listeners, middleware, and routes. Every service provider extends Illuminate\Support\ServiceProvider and has two main methods: register() (bind things into the container — only bindings here, no access to other services) and boot() (called after all providers are registered — safe to use any service). All service providers are registered in config/app.php under the providers array. When you install a Laravel package, it often registers its own service provider automatically via the extra.laravel.providers key in its composer.json (package discovery).

Beginner

The Service Container (IoC Container) is a powerful tool for managing class dependencies and performing dependency injection. When you type-hint a dependency in a controller constructor or method, Laravel's container automatically resolves and injects it. Bind a class: $this->app->bind(UserRepository::class, EloquentUserRepository::class). Singleton (one instance): $this->app->singleton(Cache::class, RedisCache::class). Resolve manually: app(UserRepository::class) or resolve(UserRepository::class). The container uses PHP Reflection to inspect constructor type-hints and automatically resolve nested dependencies. This is the foundation of how Laravel wires up controllers, commands, middleware, and every other component.

Beginner

Seeders populate your database with test or initial data. Generate: php artisan make:seeder UserSeeder. The seeder's run() method contains the database insert logic. Run a specific seeder: php artisan db:seed --class=UserSeeder. Run all seeders registered in DatabaseSeeder: php artisan db:seed. Combine with factories: User::factory()->count(50)->create(). Seeders are useful for: populating initial data (admin users, categories, settings), creating test data for development, and resetting the database to a known state. Use php artisan migrate:fresh --seed to rebuild and reseed from scratch.

Beginner

Model Factories generate fake model instances for testing and seeding. Generate: php artisan make:factory UserFactory --model=User. Factories use the Faker library to generate realistic fake data. Define state: public function definition(): array { return ["name" => $this->faker->name, "email" => $this->faker->email, "password" => bcrypt("password")]; }. Create instances: User::factory()->make() (not persisted), User::factory()->create() (saved to DB), User::factory()->count(10)->create() (bulk). Override specific attributes: User::factory()->create(["name" => "Alice"]). Define states for special conditions: User::factory()->admin()->create(). Factories are essential for feature tests and development data.

Beginner

Named routes allow you to give a route a memorable name and then generate URLs or redirects by name instead of hardcoding paths. Assign a name with ->name("users.index"): Route::get("/users", [UserController::class, "index"])->name("users.index"). Generate a URL: route("users.index"). With parameters: route("users.show", ["id" => 1]) or route("users.show", $user) (Eloquent model route-model binding). Redirect by name: redirect()->route("users.index"). Named routes are crucial because if you change a URL path, you only change it in the route definition — all route() calls automatically reflect the new path without updating every view and controller.

Beginner

Route Model Binding automatically injects model instances into route handlers based on route parameters. Instead of manually fetching a model: $user = User::findOrFail($id), type-hint the model in the controller method: public function show(User $user). Laravel automatically resolves the User model by matching the route parameter ({user}) to the model's primary key. If no record exists, a 404 response is returned automatically. You can customize the resolution column: Route::get("/users/{user:slug}", ...) resolves by the slug column. Custom resolution logic can be added in the model's resolveRouteBinding() method. This eliminates repetitive findOrFail boilerplate.

Beginner

Laravel automatically protects all web routes against Cross-Site Request Forgery (CSRF) attacks via the VerifyCsrfToken middleware. For every HTML form, add the @csrf Blade directive which generates a hidden input field containing a unique token: <input type="hidden" name="_token" value="...">. Laravel validates this token on every POST, PUT, PATCH, and DELETE request — if the token is missing or invalid, a 419 (Page Expired) response is returned. For AJAX requests, include the token in the request headers: X-CSRF-TOKEN. Axios (included in Laravel's frontend scaffolding) automatically includes the CSRF token from the meta tag in all AJAX requests. Exclude specific URIs with $except in the middleware class (e.g., webhook endpoints).

Beginner

The Request object in Laravel (Illuminate\Http\Request) wraps the current HTTP request and provides a clean API for accessing all request data. Inject it in a controller: public function store(Request $request). Access input: $request->input("name"), $request->get("name"), $request->post("name"). Check if input exists: $request->has("name"), $request->filled("name") (exists and not empty). Get all input: $request->all(). Only specific fields: $request->only(["name", "email"]). Except fields: $request->except(["password"]). File uploads: $request->file("avatar"). Check request type: $request->isJson(), $request->expectsJson(). Get headers: $request->header("Accept").

Beginner

Laravel provides a powerful validation system. The simplest approach is $request->validate(["name" => "required|string|max:255", "email" => "required|email|unique:users"]). If validation fails, Laravel automatically redirects back with errors and old input. In Blade, display errors with @error("name") and repopulate with old("name"). For complex validation, use Form Requests: php artisan make:request StoreUserRequest — the validation logic lives in the rules() method, keeping controllers thin. Laravel has 70+ built-in validation rules: required, email, min:3, max:255, unique:table,column, exists:table,column, confirmed (password confirmation), image, mimes:jpg,png, date, numeric, regex, and many more.

Beginner

A resource controller handles CRUD operations for a resource using a standard set of actions that map to HTTP verbs. Generate with: php artisan make:controller PostController --resource. This creates seven methods: index() (GET /posts), create() (GET /posts/create), store() (POST /posts), show($id) (GET /posts/{id}), edit($id) (GET /posts/{id}/edit), update($id) (PUT/PATCH /posts/{id}), destroy($id) (DELETE /posts/{id}). Register all routes at once: Route::resource("posts", PostController::class). Use ->only([...]) or ->except([...]) to limit which routes are registered. API resource controllers exclude create and edit (no HTML forms needed).

Beginner

The redirect() helper generates a redirect response. Common uses: return redirect("/dashboard") (redirect to URL), return redirect()->route("dashboard") (redirect to named route), return redirect()->back() (redirect to previous page — useful after form validation failures), return redirect()->route("login"). Flash data to the next request: return redirect()->route("users.index")->with("success", "User created!") — access in Blade with session("success"). For redirects with old input (form re-population): return redirect()->back()->withErrors($validator)->withInput(). The Redirect facade provides the same functionality for non-response contexts.

Beginner

Blade Components (Laravel 7+) provide a powerful way to create reusable UI components similar to Vue or React components. Create with php artisan make:component Alert — generates a class in app/View/Components/ and a Blade view in resources/views/components/. Use in Blade templates: <x-alert type="success" :message="$msg" />. The component class receives the attributes and exposes data to the view. For simple, class-less components, create just the Blade file in resources/views/components/. Components support slots for passing HTML content: <x-card><x-slot:title>Title</x-slot>Body content</x-card>. Anonymous components (just the Blade file, no class) are great for simple presentational UI.

Beginner

dd() (Dump and Die) outputs a well-formatted, collapsible debug dump of one or more variables and immediately stops execution. dd($user, $posts) is essential for quick debugging. dump() does the same but does not stop execution — the script continues running after the dump. Both use Symfony's VarDumper component to format output beautifully (nested arrays, object properties, etc.). In testing, use $this->dump() to debug response data. Laravel also provides ddd() in combination with Laravel Telescope and Ray (a separate debugging tool by Spatie). For production debugging, never leave dd() in code — use Laravel's logging system (Log::info()) instead.

Beginner

The config() helper accesses configuration values from the files in the config/ directory using dot notation. config("app.name") reads the name key from config/app.php. config("database.connections.mysql.host") reads a nested value. Provide a default: config("app.locale", "en"). Set at runtime: config(["app.locale" => "fr"]) (only for the current request). Configuration files read environment variables via env() — but in application code, always call config(), not env() directly, because configuration values are cached with php artisan config:cache, and env() returns null in a cached environment. Clear config cache: php artisan config:clear.

Beginner

Laravel Mix is a wrapper around Webpack that provides a fluent API for defining asset compilation — compiling SASS/Less to CSS, bundling JavaScript, versioning assets. Configured in webpack.mix.js. Example: mix.js("resources/js/app.js", "public/js").sass("resources/sass/app.scss", "public/css").version(). Run: npm run dev (development) or npm run prod (production, with minification). Mix has been largely replaced by Vite (the default since Laravel 9) — which is significantly faster (no bundling during development, uses native ES modules) and configured in vite.config.js. The @vite Blade directive includes compiled assets. Vite provides instant hot module replacement (HMR) during development.

Beginner

The storage/ directory contains files generated by the framework: storage/app/ (user-uploaded files), storage/framework/ (cache, sessions, compiled views), and storage/logs/ (application log files). To make uploaded files publicly accessible, create a symbolic link: php artisan storage:link — this creates public/storage pointing to storage/app/public. Store files with the Storage facade: Storage::put("avatars/user.jpg", $fileContents) or Storage::disk("s3")->put(...). Laravel's filesystem abstraction (Flysystem) supports local, Amazon S3, Rackspace, SFTP, and other drivers — switch by changing the FILESYSTEM_DISK env variable, with no code changes needed.

Beginner

Laravel provides a complete authentication system. The Auth facade handles login: Auth::attempt(["email" => $email, "password" => $password]). Check authentication: Auth::check(). Get current user: Auth::user(). Logout: Auth::logout(). Laravel offers starter kits for scaffolding authentication UI: Laravel Breeze (simple, Blade-based), Laravel Jetstream (advanced, supports Livewire or Inertia.js, two-factor auth, API tokens). The auth middleware protects routes from unauthenticated access. The auth.php config defines guards (web, api) and providers (Eloquent, database). Guards determine how users are authenticated per request; providers determine how users are retrieved from storage.

Beginner

Route Groups allow you to share attributes (middleware, prefix, namespace) across multiple routes without repeating them. Route::prefix("admin")->middleware(["auth", "admin"])->name("admin.")->group(function() { Route::get("/dashboard", [AdminController::class, "index"])->name("dashboard"); }) — this creates a route accessible at /admin/dashboard, named admin.dashboard, protected by both auth and admin middleware. Common grouping attributes: prefix (URL prefix), middleware (stack of middleware), name (route name prefix), domain (subdomain routing), controller (default controller for the group). Route groups are essential for organizing API versioning (/api/v1/...) and admin panels.

Beginner

Query Scopes allow you to encapsulate commonly-used query constraints in reusable, named methods on your Eloquent model. Local scopes are prefixed with scope and can be chained fluently: public function scopeActive($query) { return $query->where("active", true); } — called as User::active()->get(). Local scopes can accept arguments: public function scopeOfType($query, $type) { return $query->where("type", $type); } — called as User::ofType("admin")->get(). Global scopes apply automatically to every query on the model (e.g., soft delete filtering). Create a global scope class and register in booted(): static::addGlobalScope(new ActiveScope). Global scopes can be removed for a query with ::withoutGlobalScope().

Beginner

Laravel provides elegant pagination out of the box. $users = User::paginate(15) returns a LengthAwarePaginator with the current page's results. In a Blade view, render pagination links with {{ $users->links() }} — this generates Bootstrap or Tailwind-styled pagination links automatically. Customize per-page: User::paginate(request("per_page", 15)). For simple prev/next navigation (more performant on large datasets): User::simplePaginate(15). For cursor-based pagination (fastest for large datasets, no total count): User::cursorPaginate(15). For APIs, paginate() returns JSON with data (current page items), current_page, last_page, per_page, total, and navigation links.

Beginner

Laravel provides a unified API for various session backends. Configure the driver in .env: SESSION_DRIVER=database (or file, redis, memcached, cookie, array). Store data: session()->put("key", "value") or $request->session()->put(...). Retrieve: session("key", "default"). Flash data (available only for next request — great for success messages): session()->flash("status", "Profile updated!"). Retrieve and delete: session()->pull("key"). Delete: session()->forget("key"). Clear all: session()->flush(). The all() method returns all session data. Increment/decrement: session()->increment("views"). Use Redis for sessions in production for performance and to support multiple servers.

Beginner

Accessors and Mutators allow you to transform Eloquent attribute values when reading and writing. In Laravel 9+ (using the newer syntax), define them as a method returning an Attribute: protected function firstName(): Attribute { return Attribute::make(get: fn($value) => ucfirst($value), set: fn($value) => strtolower($value)); }. Access: $user->first_name automatically applies the getter. Assign: $user->first_name = "ALICE" stores "alice". Accessors can compute values from multiple fields: fn() => $this->first_name . " " . $this->last_name. Cast attributes to types in the $casts property: "is_admin" => "boolean", "options" => "array", "created_at" => "datetime".

Beginner

Laravel provides dozens of global helper functions for common tasks. String helpers: Str::slug("Hello World") → "hello-world", Str::studly(), Str::camel(), Str::limit($text, 100), str()->of($text)->upper()->limit(50). Array helpers: Arr::get($array, "user.name", "default"), Arr::only(), Arr::except(), Arr::flatten(). URL helpers: url("path"), secure_url("path"), asset("img/logo.png"). Path helpers: base_path(), app_path(), public_path(), storage_path(), resource_path(). Other: abort(404), back(), now(), today(), optional($user)->name (null-safe), filled($value), blank($value), tap($value, $callback).

Beginner

Laravel's Queue system allows you to defer time-consuming tasks (sending emails, processing images, generating reports) to background workers, keeping HTTP responses fast. Create a job: php artisan make:job ProcessPodcast. Dispatch it: ProcessPodcast::dispatch($podcast) or dispatch(new ProcessPodcast($podcast)). Queue drivers: database, redis, sqs (Amazon SQS), beanstalkd, sync (no actual queue, runs immediately). Start a worker: php artisan queue:work. Delay a job: ProcessPodcast::dispatch($podcast)->delay(now()->addMinutes(10)). Failed jobs are stored and can be retried: php artisan queue:retry all. php artisan queue:failed lists failed jobs. Supervisor is used to keep queue workers running in production.

Beginner

A Mailable is a class representing an email message in Laravel. Generate: php artisan make:mail WelcomeEmail. The class defines the email content using the fluent envelope() and content() methods (Laravel 9+): public function envelope(): Envelope { return new Envelope(subject: "Welcome!"); } and public function content(): Content { return new Content(view: "emails.welcome"); }. Pass data to the view through constructor properties. Send synchronously: Mail::to($user)->send(new WelcomeEmail($user)). Send to queue: Mail::to($user)->queue(new WelcomeEmail($user)). Preview in browser by returning the Mailable from a route. Configure mail drivers in config/mail.php: SMTP, Mailgun, Postmark, Amazon SES, or Sendmail.

Beginner

Soft deletes allow you to "delete" records without actually removing them from the database — a deleted_at timestamp is set instead. To enable: add use SoftDeletes; trait to the model and add a $table->softDeletes() column to the migration. When you call $user->delete(), the deleted_at field is set to the current timestamp. Normal queries automatically filter out soft-deleted records. Include soft-deleted records: User::withTrashed()->get(). Only soft-deleted: User::onlyTrashed()->get(). Restore: $user->restore(). Permanently delete: $user->forceDelete(). Soft deletes are useful for audit trails, accidental deletion recovery, and "trash" features. The deleted_at column must be nullable.

Beginner

Laravel's logging system is built on Monolog and configured via config/logging.php. Log messages with the Log facade using PSR-3 severity levels: Log::emergency(), Log::alert(), Log::critical(), Log::error(), Log::warning(), Log::notice(), Log::info(), Log::debug(). Add context data: Log::info("User logged in", ["user_id" => $user->id]). Channels: stack (combine multiple channels), single (one log file), daily (separate file per day), slack, syslog, errorlog, cloudwatch. Log to a specific channel: Log::channel("slack")->critical("Server is down!"). For production, use a dedicated logging service (Papertrail, Loggly, Sentry).

Beginner

Collections are a fluent, chainable wrapper around arrays providing dozens of powerful methods for transforming, filtering, and aggregating data. Eloquent queries return collections automatically. Create a collection: collect([1, 2, 3, 4, 5]). Useful methods: filter(fn($item) => $item > 2), map(fn($item) => $item * 2), reject(fn($item) => $item % 2 === 0), sum(), avg(), max(), min(), pluck("name") (extract a column), groupBy("role"), sortBy("name"), unique("email"), chunk(10), first(), last(), contains(), each(fn($item) => ...), toArray(), toJson(). Collections are lazy-evaluated in many cases and are much more expressive than raw array functions.

Beginner

Laravel provides a unified caching API supporting multiple backends. Configure in config/cache.php. Drivers: file, database, redis, memcached, array (in-memory, testing), dynamodb. Common operations: Cache::put("key", $value, now()->addMinutes(60)), Cache::get("key", "default"), Cache::has("key"), Cache::forget("key"), Cache::flush(). The Remember pattern: Cache::remember("users", 3600, fn() => User::all()) — retrieves from cache if exists, otherwise executes the closure and caches the result. Cache::rememberForever("key", $closure) caches indefinitely. Use cache tags with Redis/Memcached to group and invalidate related items. Caching is critical for improving performance of expensive database queries and API calls.

Beginner

The env() helper reads values from the .env file. Example: env("DB_HOST", "127.0.0.1") — the second argument is the default if the key is not set. However, you should never call env() directly in application code. Once php artisan config:cache is run (which is done in production for performance), all config values are cached as a PHP file and the .env file is no longer read — env() would return null. Instead, use env() only inside config/ files, and access those values throughout your application via the config() helper. This ensures config caching works correctly. Always add any new .env variables to .env.example as a template for other developers.

Beginner

The Query Builder provides a fluent, database-agnostic interface for constructing and executing SQL queries without writing raw SQL. Access via the DB facade: DB::table("users")->where("active", 1)->orderBy("name")->get(). Key methods: select(), where(), orWhere(), whereIn(), whereBetween(), whereNull(), join(), leftJoin(), orderBy(), groupBy(), having(), limit(), offset(), insert(), update(), delete(), count(), max(), min(), avg(), sum(). Use DB::raw() for raw SQL expressions. The Query Builder automatically handles parameter binding to prevent SQL injection.

Beginner

Laravel Tinker is a powerful REPL (Read-Eval-Print Loop) for interacting with your Laravel application from the command line. Launch with php artisan tinker. In Tinker, you can run any PHP code with full access to your app: create Eloquent models, test helper functions, run queries, dispatch jobs, and inspect data without writing temporary controllers or routes. Examples: User::factory()->create(), User::where("email", "alice@example.com")->first(), $user = User::find(1); $user->name = "Bob"; $user->save();. Tinker uses the PsySH library and supports tab completion for Laravel classes. It is invaluable for debugging, exploring data, and testing snippets of code during development.

Beginner

Eloquent provides several relationship types for defining how models relate to each other. One-to-One: hasOne() / belongsTo(). One-to-Many: hasMany() / belongsTo(). Many-to-Many: belongsToMany() — requires a pivot table. Has Many Through: hasManyThrough() (access distant relations via an intermediate model). Polymorphic: morphTo() / morphMany() — a model belongs to multiple types. Access relations as properties (lazy loaded): $user->posts. Or as methods for chaining: $user->posts()->where("published", true)->get(). Each relationship method returns a query builder, enabling further constraints. Define foreign keys explicitly: hasMany(Post::class, "author_id", "id").

Intermediate

The N+1 query problem occurs when loading a collection of models and then accessing a relationship on each one, triggering one query to load the collection and then N additional queries (one per model) to load the relationship. Example: $posts = Post::all(); (1 query) then foreach ($posts as $post) { echo $post->user->name; } executes one query per post. For 100 posts, that is 101 queries. Eager loading solves this with the with() method: $posts = Post::with("user")->get(); — this executes exactly 2 queries total (one for posts, one for all their users). Load multiple: with(["user", "tags", "comments"]). Nested eager loading: with("comments.author"). Use withCount() to load relationship counts without loading the related models.

Intermediate

Form Requests are dedicated classes that encapsulate validation and authorization logic, keeping controllers clean. Generate: php artisan make:request StorePostRequest. The class has two methods: authorize() (return true/false — can this user perform this action?) and rules() (return validation rules array). Type-hint the Form Request in your controller method: public function store(StorePostRequest $request) — Laravel automatically validates and authorizes before the method is called. If authorization fails, a 403 response is returned. If validation fails, the user is redirected back with errors. Access validated data with $request->validated() — only returns data that passed validation, never extra fields. Add custom messages with messages() method.

Intermediate

Laravel's event system implements the Observer pattern, providing a way to decouple various aspects of your application. Create an event: php artisan make:event UserRegistered. Create a listener: php artisan make:listener SendWelcomeEmail --event=UserRegistered. Register in EventServiceProvider: protected $listen = [UserRegistered::class => [SendWelcomeEmail::class]]. Fire the event: event(new UserRegistered($user)) or UserRegistered::dispatch($user). Listeners can be queued by implementing ShouldQueue. Model events fire automatically: creating, created, updating, updated, deleting, deleted, saved, restored. Use php artisan event:list to see all registered events and listeners.

Intermediate

Jobs represent units of work that can be queued for asynchronous background processing. Create: php artisan make:job SendWelcomeEmail. The handle() method contains the job logic. Implement ShouldQueue to queue it; without it, it runs synchronously. Dispatch: SendWelcomeEmail::dispatch($user). Dispatch with delay: ::dispatch()->delay(now()->addMinutes(5)). Specify queue: ::dispatch()->onQueue("emails"). Set maximum tries: public int $tries = 3. Set timeout: public int $timeout = 60. On failure (after all retries): public function failed(Throwable $e) method is called. Process a specific queue: php artisan queue:work --queue=emails,default. Use Laravel Horizon for a Redis-powered queue dashboard.

Intermediate

Laravel's task scheduler allows you to fluently define scheduled commands within the application itself, instead of managing multiple cron entries. Define schedules in app/Console/Kernel.php's schedule() method: $schedule->command("emails:send")->daily(), $schedule->job(new ReportJob)->weekdays()->at("08:00"), $schedule->call(fn() => DB::table("logs")->delete())->monthly(). Frequency methods: everyMinute(), hourly(), daily(), weekly(), cron("* * * * *"). Constraints: ->weekdays(), ->between("8:00", "17:00"), ->when(fn() => $condition). Only one cron entry needed on the server: * * * * * php /path/to/artisan schedule:run >> /dev/null 2>&1. Prevent overlapping: ->withoutOverlapping().

Intermediate

API Resources (Laravel 5.5+) provide a transformation layer between Eloquent models and JSON responses, giving you full control over what is included and how data is formatted. Generate: php artisan make:resource UserResource. In the toArray() method, define the JSON structure: return ["id" => $this->id, "name" => $this->name, "email" => $this->email, "role" => $this->whenLoaded("role")]. Use: return new UserResource($user) or for collections: return UserResource::collection(User::paginate(15)). Conditional fields: $this->when($this->isAdmin(), "admin_data"). Load relationships only if already eager-loaded: $this->whenLoaded("posts") — prevents N+1 in APIs. Resource collections support custom wrapper keys and meta data.

Intermediate

Laravel Sanctum is a lightweight authentication system for SPAs (Single Page Applications), mobile applications, and simple token-based APIs. Install: composer require laravel/sanctum. It provides two authentication mechanisms: API tokens (issue long-lived tokens with specific abilities: $user->createToken("mobile-app", ["read"])->plainTextToken) and SPA authentication (cookie-based session authentication for same-domain SPAs, combining CSRF protection with session auth). Validate ability: $request->user()->tokenCan("read"). Revoke tokens: $user->tokens()->delete(). Compared to Passport (OAuth2), Sanctum is simpler and covers 90% of use cases. It is the default authentication for Laravel starter kits.

Intermediate

Policies are classes that organize authorization logic around a particular model or resource. Generate: php artisan make:policy PostPolicy --model=Post. Define authorization methods: public function update(User $user, Post $post): bool { return $user->id === $post->user_id; }. Register in AuthServiceProvider or use auto-discovery. Check in controllers: $this->authorize("update", $post) — throws 403 if unauthorized. In Blade: @can("update", $post) ... @endcan. Via Gate: Gate::allows("update", $post). Policies support viewAny, view, create, update, delete, restore, forceDelete methods. The before() method can grant blanket permissions to admins before any other check.

Intermediate

Notifications provide a unified API for sending notifications via multiple channels (email, SMS, Slack, database, push) from a single class. Generate: php artisan make:notification InvoicePaid. Specify channels in via(): return ["mail", "database", "slack"]. Define each channel's content: toMail(), toDatabase(), toSlack(). Send to a user: $user->notify(new InvoicePaid($invoice)) (requires Notifiable trait). Send to multiple via Notification facade: Notification::send($users, new InvoicePaid($invoice)). Database notifications are stored in a notifications table and can be retrieved: $user->notifications, $user->unreadNotifications. Queue notifications by implementing ShouldQueue.

Intermediate

Laravel Telescope is a debug assistant for the local development environment. Install: composer require laravel/telescope --dev. It provides a beautiful web dashboard showing: all HTTP requests and responses, database queries (with slow query detection), queued jobs and their status, scheduled task execution, cache operations, mail sent, notifications triggered, log entries, exceptions, model events, and Redis commands. Access at /telescope. Telescope stores all this data in your database, so you can inspect what happened during a request even after it completed. It is invaluable for debugging N+1 query problems (seeing all queries per request), understanding job failures, and tracking application behavior during development. Never run Telescope in production without restricting access.

Intermediate

Laravel Horizon provides a beautiful dashboard and code-driven configuration for Redis-powered queues. Install: composer require laravel/horizon. Access dashboard at /horizon. Horizon lets you configure queue workers, supervisor processes, and queue priorities in code (config/horizon.php) rather than server config files. The dashboard shows: real-time job throughput, failed jobs (with full stack traces), job retry, wait times, and process monitoring. Horizon uses a balanced load strategy — it automatically rebalances workers as queue load changes. Run: php artisan horizon. In production, use Supervisor to keep Horizon running. php artisan horizon:pause / horizon:continue to pause/resume processing. Only works with Redis queue driver.

Intermediate

Observers group event listeners for a single model into a dedicated class. Create: php artisan make:observer UserObserver --model=User. The observer class has methods for each Eloquent event: creating(), created(), updating(), updated(), saving(), saved(), deleting(), deleted(), restoring(), restored(). Register in the model with the ObservedBy attribute (#[ObservedBy(UserObserver::class)]) or in a service provider: User::observe(UserObserver::class). Example use: automatically hashing passwords on creating, sending welcome emails on created, updating a search index on saved, logging deletes on deleted. Observers are cleaner than model event closures for complex logic.

Intermediate

Broadcasting allows server-side events to be sent to the browser in real-time via WebSockets. Laravel supports Pusher, Ably, Laravel WebSockets (self-hosted), and Soketi. Make an event broadcastable by implementing ShouldBroadcast and define the channel in broadcastOn(): return new Channel("orders.{$this->order->id}"). On the frontend, subscribe with Laravel Echo: Echo.channel("orders.1").listen("OrderShipmentStatusUpdated", (e) => { ... }). Private channels require authentication (defined in routes/channels.php): Broadcast::channel("orders.{orderId}", fn($user, $orderId) => $user->id === Order::find($orderId)->user_id). Broadcasting enables live notifications, chat features, real-time dashboards, and collaborative editing.

Intermediate

Laravel's Pipeline passes a subject through a series of "pipes" (classes or closures), each transforming the subject or short-circuiting the chain. It is the mechanism behind middleware. The Pipeline class: app(Pipeline::class)->send($user)->through([ValidateAge::class, HashPassword::class, LogCreation::class])->thenReturn(). Each pipe class implements a handle($passable, $next)` method — call `$next($passable) to pass to the next pipe or return early to short-circuit. Use cases: multi-step form processing, data transformation pipelines, middleware outside the HTTP layer, and complex validation chains. The thenReturn() method returns the final result; then($callback) passes the result to a closure.

Intermediate

Global scopes add constraints to every query on a model automatically. The most common built-in global scope is the soft delete scope (automatically filtering out records where deleted_at is not null). Create a custom scope: implement Scope interface with an apply(Builder $builder, Model $model) method. Register in the model's booted() method: static::addGlobalScope(new ActiveScope). Or use an anonymous scope: static::addGlobalScope("active", fn($q) => $q->where("active", true)). Remove a global scope for a specific query: User::withoutGlobalScope(ActiveScope::class)->get(). Global scopes are useful for multi-tenancy (automatically filtering by tenant_id), data partitioning, and any constraint that should always apply to a model.

Intermediate

Polymorphic relationships allow a model to belong to more than one other type of model on a single association. Example: a Comment model that can belong to either a Post or a Video. The comments table has commentable_id and commentable_type columns. Define in Comment: public function commentable() { return $this->morphTo(); }. Define in Post: public function comments() { return $this->morphMany(Comment::class, "commentable"); }. Access comments on a post: $post->comments. Access the parent: $comment->commentable. Use morph map to map type strings to class names: Relation::morphMap(["post" => Post::class]) — prevents class name coupling in the database.

Intermediate

Database transactions ensure that a series of database operations either all succeed or all fail together — maintaining data integrity. In Laravel, wrap operations in a transaction: DB::transaction(function() { $user = User::create([...]); $user->wallet()->create(["balance" => 0]); }). If any exception is thrown inside the closure, Laravel automatically rolls back all changes. Manually control: DB::beginTransaction(); then DB::commit(); or DB::rollBack();. Set retry attempts for deadlocks: DB::transaction($callback, 5). Eloquent operations within a transaction are also rolled back. Always use transactions when multiple related writes must succeed or fail together — such as creating an order and updating inventory simultaneously.

Intermediate

Rate limiting restricts how many times a user or IP can hit an endpoint within a time window. Apply the built-in throttle middleware: Route::middleware("throttle:60,1")->group(...) — 60 requests per minute. In Laravel 8+, define named rate limiters in RouteServiceProvider (or AppServiceProvider): RateLimiter::for("api", fn($request) => $request->user() ? Limit::perMinute(100)->by($request->user()->id) : Limit::perMinute(10)->by($request->ip())). Apply: Route::middleware("throttle:api"). Rate limiters can return custom responses on exceeded limits. The RateLimiter facade also provides attempt(), hit(), and tooManyAttempts() for manual rate limiting logic (e.g., login throttling).

Intermediate

The Repository pattern abstracts the data access layer from business logic. Define an interface: interface PostRepositoryInterface { public function findAll(): Collection; public function findById(int $id): Post; }. Implement it: class EloquentPostRepository implements PostRepositoryInterface { public function findAll() { return Post::all(); } }. Bind in a service provider: $this->app->bind(PostRepositoryInterface::class, EloquentPostRepository::class). Inject the interface in controllers. Benefits: easy to switch data sources (swap Eloquent for an API or flat files), unit-testable (inject a mock repository), and business logic never references Eloquent directly. Laravel's tight Eloquent integration means repositories are optional — many teams skip them and use Eloquent directly, using it with service classes for complex logic.

Intermediate

Laravel has excellent built-in testing support via PHPUnit and Pest. Feature tests test HTTP endpoints: $this->get("/home")->assertStatus(200)->assertSee("Welcome"). $this->actingAs($user)->post("/posts", $data)->assertRedirect("/posts"). Unit tests test individual classes in isolation. Database testing: use RefreshDatabase or DatabaseTransactions traits to reset state between tests. Create test data: User::factory()->create(). Mock dependencies: Mail::fake(), Event::fake(), Queue::fake(), Notification::fake(), Storage::fake(). Assert they were triggered: Mail::assertSent(WelcomeEmail::class). Run tests: php artisan test or vendor/bin/phpunit. Pest (a modern PHP testing framework) is now the default in new Laravel apps.

Intermediate

Laravel Octane supercharges application performance by using high-performance application servers — Swoole or RoadRunner — to keep the Laravel application in memory between requests. In traditional PHP-FPM, the entire application bootstraps (loads service providers, binds services, reads config) on every request — wasting time. With Octane, the app is booted once and kept in memory; each request reuses the already-bootstrapped application, eliminating bootstrap overhead. Results: 10-30x faster throughput compared to PHP-FPM. Caveats: since state persists between requests, you must be careful with singleton-bound objects that may become stale or carry state. Octane provides tools like $app->flush() for cleanup. It also supports concurrent tasks via Swoole coroutines.

Advanced

Contextual binding allows you to inject different implementations of the same interface into different classes. Example: two controllers both type-hint FileSystem but one needs local storage and the other needs S3. In a service provider: $this->app->when(PhotoController::class)->needs(FileSystem::class)->give(fn() => Storage::disk("local")) and $this->app->when(VideoController::class)->needs(FileSystem::class)->give(fn() => Storage::disk("s3")). The container inspects which class is being constructed and provides the appropriate binding. You can also bind to a specific implementation only when a particular tagged service is needed. Contextual binding is essential for large applications where different parts of the app need different implementations of the same abstraction.

Advanced

Deferred service providers are not loaded on every request — they are loaded only when one of their bindings is actually needed. Implement the DeferrableProvider interface and define a provides() method that lists the container bindings the provider registers. Laravel's bootstrap/cache/services.php file caches this mapping. When something in the container resolves a listed binding, only then is the provider loaded. This improves bootstrap performance for large applications with many service providers. Example: a PDF generation service provider is deferred — it only loads when you actually use the PDF generator, not on every request. Most first-party Laravel packages use deferred providers. Always run php artisan optimize in production to regenerate the cached services manifest.

Advanced

Laravel Contracts are a set of interfaces that define the core services provided by the framework, located in Illuminate\Contracts. They define the "public API" of each service: Illuminate\Contracts\Cache\Repository, Illuminate\Contracts\Queue\Queue, Illuminate\Contracts\Mail\Mailer, Illuminate\Contracts\Auth\Guard. Type-hinting against Contracts rather than concrete classes makes code more loosely coupled and easier to test — swap the entire cache implementation by rebinding the contract in the container. Contracts differ from Facades: Facades provide a static API to concrete classes, while Contracts define an interface that can have multiple implementations. All Facades implement a corresponding Contract. Always prefer Contract type-hints in package development.

Advanced

Laravel Livewire is a full-stack framework for building dynamic, reactive UIs using PHP and Blade — without writing JavaScript. Each Livewire component is a PHP class with a Blade view. Public properties are automatically data-bound: changes in the browser trigger PHP method calls via AJAX, and updated properties re-render the Blade view. Directives: wire:model (two-way data binding), wire:click (click handler), wire:submit, wire:loading. Features: real-time validation, file uploads, pagination, polling, URL query parameters, flash messages. Livewire allows building complex interactive UIs (search-as-you-type, modal forms, data tables) entirely in PHP. It is the server-side alternative to Vue/React for teams that prefer PHP. Works seamlessly with Alpine.js for additional client-side interactivity.

Advanced

Inertia.js is a modern approach to building server-driven single-page applications. It lets you build SPAs with a modern frontend framework (Vue.js, React, Svelte) while keeping all your routing and controllers in Laravel — no separate API needed. Instead of returning JSON from controllers, return Inertia responses: return Inertia::render("Users/Index", ["users" => User::paginate(10)]). The frontend renders Vue/React components with the provided props. Navigation happens without full page reloads via Inertia's client-side adapter. It combines the best of both worlds: server-side routing/auth/validation (Laravel's strengths) with modern reactive frontend components. Laravel Jetstream uses Inertia.js as one of its stack options. Unlike Livewire, UI interactivity is fully in JavaScript.

Advanced

The Macroable trait allows adding custom methods to classes at runtime without subclassing. Many core Laravel classes (Response, Request, Collection, Str, Arr, Builder) use the Macroable trait. Add a macro in a service provider: Collection::macro("sumField", function($field) { return $this->sum(fn($item) => $item[$field]); }). Now $collection->sumField("price") works everywhere. Str::macro("initials", fn($name) => collect(explode(" ", $name))->map(fn($w) => strtoupper($w[0]))->implode("")). Macro methods have access to $this (the object the macro is called on). The mixin() method adds all public methods of a class as macros at once. Macros are a powerful extension mechanism — packages like Spatie use them extensively to add methods to Laravel core classes without forking them.

Advanced

Chunking processes large database result sets in smaller batches to avoid exhausting PHP memory. User::chunk(200, function($users) { foreach ($users as $user) { /* process */ } }) executes SELECT queries with LIMIT/OFFSET 200 records at a time. If you modify records being chunked (update/delete), use chunkById() instead — it uses cursor-based pagination (keyed on the primary key) rather than offset, which is both faster and safe when modifying records. For maximum efficiency: User::cursor() returns a lazy collection using PHP generators — only one model is in memory at a time (reads row-by-row using PDO cursor). For write-heavy operations on millions of rows, combine chunking with queue jobs for parallel processing. Never load millions of records with User::all().

Advanced

Laravel Pennant (first-party package) is a feature flag library for gradually rolling out new application features. Define flags in a service provider: Feature::define("new-checkout", fn($user) => $user->isInBeta()). Check the flag: Feature::active("new-checkout") or in Blade: @feature("new-checkout") ... @endfeature. Deactivate: Feature::deactivate("new-checkout", $user). Pennant stores flag values (by default in the database) per user, so you can activate a feature for specific users or segments. Flags can be percentage-based for gradual rollouts: fn($user) => Hash::check($user->id, config("pennant.seed")) % 100 < 20 (20% of users). Feature flags enable A/B testing, canary deployments, and safe gradual feature rollouts without deploying new code.

Advanced

Both load relationships but at different stages. with() is called on the query before execution — it is eager loading, including the relationship data in the initial query. It executes 2 queries: one for the main models, one for all related models. $posts = Post::with("author")->get(). load() is called on an already-retrieved collection or model — it is lazy eager loading, loading the relationship after the fact. $posts->load("author") executes one additional query after you already have the posts. Use with() when you know in advance you will need the relationship. Use load() when you have already retrieved models and conditionally need to load a relationship (e.g., an API resource that loads relationships based on request parameters). Both prevent N+1; load() offers flexibility at the cost of an extra query round-trip.

Advanced

Custom Artisan commands extend the CLI with application-specific commands. Generate: php artisan make:command GenerateReport. The command class defines a $signature (command name and arguments/options): protected $signature = "report:generate {type} {--format=csv : Output format}". The $description is shown in php artisan list. The handle() method contains the logic. Access arguments: $this->argument("type"). Options: $this->option("format"). Output: $this->info("Done!"), $this->error("Failed!"), $this->table($headers, $rows), $this->progressBar(100). Ask user input: $this->ask("Enter name"), $this->confirm("Are you sure?"). Call from code: Artisan::call("report:generate", ["type" => "monthly"]).

Advanced

Beyond simple binding, Laravel's container supports advanced techniques. Binding interfaces to implementations: $this->app->bind(LoggerInterface::class, FileLogger::class). Singleton: $this->app->singleton() — same instance every time. Scoped singletons (Laravel 8+): $this->app->scoped() — singleton within a single request lifecycle, reset between Octane requests. Instance binding: $this->app->instance(Config::class, $configObject) — bind a specific object. Tagging: $this->app->tag([FileLogger::class, SlackLogger::class], "loggers") then resolve all: $this->app->tagged("loggers"). Extending: $this->app->extend(Logger::class, fn($service) => new DecoratedLogger($service)) — wrap a resolved service. These techniques enable sophisticated dependency management in complex applications.

Advanced

Laravel provides built-in fakes for common services that make testing clean without hitting real external services. Mail::fake() — prevent emails from sending, assert with Mail::assertSent(WelcomeEmail::class, fn($mail) => $mail->hasTo("alice@example.com")). Queue::fake() — prevent job dispatching, assert with Queue::assertPushed(ProcessPodcast::class). Event::fake() — prevent events from firing. Notification::fake() — prevent notifications. Storage::fake("s3") — use a fake in-memory disk. Http::fake() — mock outgoing HTTP requests: Http::fake(["example.com/*" => Http::response(["name" => "Alice"])]). Beyond fakes, use $this->mock(UserRepository::class) for class mocking via Mockery. These tools enable fast, deterministic tests without external dependencies.

Advanced

The HasFactory trait (added to Eloquent models) provides a static factory() method for creating model factory instances. When you call User::factory(), Laravel resolves the corresponding factory class (Database\Factories\UserFactory) by convention. The trait provides: User::factory()->make() (new instance without saving), create() (save to DB), count(5)->create() (create 5 instances), state(["role" => "admin"]) (override specific attributes), create(["name" => "Alice"]) (override on creation). Factories support relationships: User::factory()->has(Post::factory()->count(3))->create() creates a user with 3 posts, handling all foreign keys automatically. Factory sequences allow cycling through values: factory()->sequence(["active" => true], ["active" => false]).

Advanced

firstOrCreate() attempts to find the first record matching the given attributes; if not found, it creates and persists a new record with those attributes combined with any additional values. User::firstOrCreate(["email" => $email], ["name" => $name, "password" => bcrypt("default")]) — searches by email, creates with email + name + password if not found. firstOrNew() is identical but does NOT save to the database — it returns a new unsaved model instance if not found, allowing you to modify it further before calling save(). Related: updateOrCreate(["email" => $email], ["name" => $name]) — finds by email and updates, or creates if not found. These methods prevent duplicate entries and reduce boilerplate for "upsert" patterns, like syncing external data or registering users with third-party auth providers.

Intermediate

Laravel Passport is a full OAuth2 server implementation for Laravel. It is used when you need OAuth2 capabilities: issuing access tokens for third-party applications, supporting multiple grant types (Authorization Code, Client Credentials, Password, Implicit, Refresh Token), and building a platform where external developers authenticate via your API. Install: composer require laravel/passport, then php artisan passport:install (creates encryption keys and default clients). Issue tokens: $user->createToken("MyApp")->accessToken. Compared to Sanctum: Passport is heavier and appropriate for OAuth2 scenarios where you want to be an OAuth provider (like Google, Facebook). Sanctum is lighter and better for first-party SPAs and mobile apps where you control both the client and server. Most applications should start with Sanctum and add Passport only if they need OAuth2.

Intermediate

Laravel supports sub-queries for adding computed columns from related tables without loading full relationships. Select sub-queries: User::addSelect(["last_login_at" => Login::select("created_at")->whereColumn("user_id", "users.id")->latest()->limit(1)])->get() — adds a last_login_at column to each user from the logins table in a single query. Order by sub-query: User::orderByDesc(Login::select("created_at")->whereColumn("user_id", "users.id")->latest()->limit(1)). Where sub-query: User::whereIn("id", Post::select("user_id")->where("published", true)). Sub-queries avoid N+1 problems for computed/aggregate data and express complex queries more cleanly than raw SQL. Use toSql() to inspect the generated query.

Intermediate

View Composers are callbacks or class methods that are called when a specific view is rendered. They allow you to automatically bind data to views every time they are rendered, without the controller needing to explicitly pass the data. Register in a service provider: View::composer("partials.sidebar", function($view) { $view->with("categories", Category::all()); }). Class-based: View::composer("partials.sidebar", SidebarComposer::class) — the composer class has a compose(View $view) method. Register for all views: View::composer("*", ...). Use cases: navigation menus that need the current user's permissions, sidebars with dynamic content, breadcrumbs, and any data shared across many views. View Composers are cleaner than controller constructor code for data that is needed across many unrelated views.

Intermediate

Job batching (Laravel 8+) allows dispatching multiple queued jobs and executing callbacks when all jobs in the batch have completed, or when any job fails. Create a batch: Bus::batch([new ProcessVideo(1), new ProcessVideo(2), new ProcessVideo(3)])->then(fn($batch) => $this->notifyComplete())->catch(fn($batch, $e) => $this->notifyFailure($e))->finally(fn($batch) => $this->cleanup())->dispatch(). Batches are stored in the job_batches table. Monitor progress: $batch->totalJobs, $batch->processedJobs(), $batch->failedJobs, $batch->progress(). Cancel a batch: $batch->cancel(). Jobs can add more jobs to the batch from within: $this->batch()->add([new NextJob()]). Allow partial failure: ->allowFailures(). Batching is ideal for processing large datasets in parallel with completion notifications.

Advanced

Lazy loading prevention forces you to always eager-load relationships, turning the common N+1 problem into a detectable error during development. Enable in AppServiceProvider: Model::preventLazyLoading(!app()->isProduction()). When enabled, accessing a relationship that was not eager-loaded throws a LazyLoadingViolationException — forcing developers to add with() to their queries. In production, log the violation instead of throwing (by passing false). This is particularly valuable in team environments where developers may forget to eager-load and the performance problem only surfaces at scale. Related safety features: Model::preventSilentlyDiscardingAttributes() — throws when assigning a non-fillable attribute, and Model::preventAccessingMissingAttributes() — throws when accessing an attribute not in the model's attributes array.

Advanced

Signed routes generate URLs with a cryptographic signature that verifies the URL has not been tampered with. Create a signed URL: URL::signedRoute("unsubscribe", ["user" => $user->id]). Temporary signed URL (expires): URL::temporarySignedRoute("password.reset", now()->addHours(2), ["token" => $token]). Validate in a controller: use the signed middleware: Route::get("/unsubscribe/{user}", [UnsubscribeController::class, "unsubscribe"])->name("unsubscribe")->middleware("signed") — if the signature is invalid or expired, a 403 response is returned. Use cases: email unsubscribe links, password reset links, email verification links, one-time action URLs in notifications. Signed routes prevent parameter tampering (e.g., changing the user ID in an unsubscribe link to unsubscribe someone else).

Advanced

Model pruning (Laravel 8+) automatically deletes models that are no longer needed. Implement the Prunable trait and define a prunable() method that returns a query builder for records to delete: public function prunable(): Builder { return static::where("created_at", "<=", now()->subMonth()) }. Schedule pruning: $schedule->command("model:prune")->daily(). Prune specific models: php artisan model:prune --model=User. For soft-deleted models, use MassPrunable for more efficient bulk deletion (single DELETE query rather than loading each model). Add a pruning() method for cleanup logic before deletion. Model pruning replaces custom scheduled commands for database maintenance tasks like removing old log entries, expired tokens, temporary uploads, and stale verification codes.

Advanced

The HasUuids trait (Laravel 9+) automatically generates UUID (Universally Unique Identifier) primary keys for Eloquent models instead of auto-incrementing integers. Add the trait to your model: use HasUuids;. Create the column as uuid type in migration: $table->uuid("id")->primary(). When you create a model, Eloquent automatically generates and sets a UUID: User::create(["name" => "Alice"]) gets a UUID like 550e8400-e29b-41d4-a716-446655440000. Generate UUID for specific columns: public function uniqueIds(): array { return ["id", "invitation_token"]; }. Benefits of UUIDs: no sequential IDs leaking business information, safe to generate on the client side, merge-friendly for distributed systems. Downside: slightly larger storage and slower indexed lookups than integers. Use HasUlids for ULID support (sortable, URL-safe unique IDs).

Advanced

Multi-authentication allows different types of users (e.g., admins and regular users) to authenticate separately with their own guards and models. Configure in config/auth.php: add a new guard ("admin" => ["driver" => "session", "provider" => "admins"]) and a provider ("admins" => ["driver" => "eloquent", "model" => Admin::class]). Authenticate via specific guard: Auth::guard("admin")->attempt($credentials). Check authentication: Auth::guard("admin")->check(). Get current admin: Auth::guard("admin")->user(). Protect admin routes with the guard middleware: Route::middleware("auth:admin")->group(...). Each guard has separate session keys, so logging in as admin does not affect the user session and vice versa. Sanctum supports multiple guards as well for API token-based multi-auth.

Advanced

The when() method applies a query clause conditionally, only when a given value is truthy. This avoids messy if-else blocks when building dynamic queries. Example: $query = User::query(); $query->when($request->name, fn($q, $name) => $q->where("name", "like", "%$name%"))->when($request->role, fn($q, $role) => $q->where("role", $role))->when($request->active, fn($q) => $q->where("active", true)). The callback receives the query builder and the value. If the value is falsy (null, false, 0, empty string), the callback is skipped entirely. You can also provide an "else" callback as the third argument: ->when($isAdmin, fn($q) => $q->withTrashed(), fn($q) => $q->where("active", true)). This pattern keeps dynamic query building clean and readable without if-statements scattered through the code.

Intermediate

The tap() helper passes a value to a callback and then returns the original value. It is designed for performing side effects without interrupting a chain. Example: return tap(User::create($data), fn($user) => $user->sendWelcomeEmail()) — creates the user, sends the email (side effect), and returns the user. Without tap: $user = User::create($data); $user->sendWelcomeEmail(); return $user;. In Eloquent: $user->tap(fn() => event(new UserSaved($user)))->save(). Tap is also used for debugging in method chains: User::where("active", true)->tap(fn($q) => info($q->toSql()))->paginate(). The $this->tap() shorthand in models: return $model->tap->sendWelcomeEmail(). Tap encourages side effects to be named and explicit rather than hidden inside the main expression.

Intermediate

Constrained eager loading allows you to add additional query constraints to a relationship being eager-loaded. Instead of loading all comments, load only approved ones: Post::with(["comments" => fn($query) => $query->where("approved", true)->orderBy("created_at")])->get(). Multiple constrained relationships: Post::with(["comments" => fn($q) => $q->latest(), "tags" => fn($q) => $q->where("active", true)])->get(). Nested constrained loading: Post::with(["comments.author" => fn($q) => $q->select("id", "name")])->get(). Load counts with conditions: Post::withCount(["comments" => fn($q) => $q->where("approved", true)]). Without constraints, eager loading always loads the full relationship — constraints let you fetch only the data you need, reducing memory usage and query time for relationships with many records.

Intermediate

Laravel Scout is a driver-based full-text search package for Eloquent models. Install: composer require laravel/scout. Add the Searchable trait to a model and define toSearchableArray() to specify searchable fields. Scout syncs model data to the search index automatically via Eloquent model events. Drivers: Algolia (cloud, most features), Meilisearch (self-hosted, fast), Typesense, and database (built-in using LIKE — no external service, good for small datasets). Search: Post::search("Laravel best practices")->get(). Combine with Eloquent constraints: Post::search($query)->where("published", true)->paginate(10). Index all existing records: php artisan scout:import "App\Models\Post". Scout removes the complexity of integrating search engines directly, providing a clean, unified API.

Intermediate

Lazy Collections use PHP generators to keep memory consumption low when working with very large datasets. User::cursor() returns a lazy collection — it uses a server-side cursor so only one model is held in memory at a time instead of loading all records. LazyCollection::make(fn() => yield from User::cursor())->filter(fn($u) => $u->age > 18)->take(100)->all(). File::lines(storage_path("large.csv")) reads a file line by line lazily. Key methods: takeWhile(), skipWhile(), remember() (cache already iterated items). The standard Collection loads everything into memory; LazyCollection is a drop-in replacement for memory-constrained operations. Always prefer cursor() over all() when processing millions of records in a command or queue job.

Advanced

Custom cast types allow you to define reusable type transformations for Eloquent model attributes. Implement CastsAttributes: class MoneyCast implements CastsAttributes { public function get($model, $key, $value, $attributes) { return new Money($value, $attributes["currency"]); } public function set($model, $key, $value, $attributes) { return ["amount" => $value->getAmount(), "currency" => $value->getCurrency()]; } }. Register in model: protected $casts = ["price" => MoneyCast::class]. Inbound-only casts (transform only on set) implement CastsInboundAttributes. Castable classes implement Castable with a castUsing() method that returns the cast class name. Use cases: Money value objects, encrypted fields, custom date formats, geographic coordinates. Custom casts make the model the single source of truth for data transformations, keeping controllers and services clean.

Advanced

Laravel provides several tools for inspecting and debugging queries. Log all queries: DB::enableQueryLog() then DB::getQueryLog() returns all executed queries with bindings and execution times. Listen to queries: DB::listen(fn($query) => Log::info($query->sql, ["bindings" => $query->bindings, "time" => $query->time])). Inspect the SQL of a query builder without executing: User::where("active", true)->toSql(). Get bindings: ->getBindings(). Combined: ->dd() dumps and dies, ->dump() outputs the SQL and continues. Count queries in a test: DB::countQueryLog() after enabling the log. Telescope automatically logs all queries with execution time and caller information. For long-running queries, enable MySQL's slow query log or set: DB::whenQueryingForLongerThan(500, fn() => alert()) to detect slow queries in real-time.

Advanced

withCount() adds a {relation}_count attribute to each model containing the number of related records, in a single SQL query using a subquery — no N+1. Post::withCount("comments")->get() adds $post->comments_count. Constrained count: Post::withCount(["comments" => fn($q) => $q->where("approved", true)]). withSum() adds an aggregate of a related column: Post::withSum("votes", "score")->get() adds $post->votes_sum_score. Other aggregates: withMin(), withMax(), withAvg(). These methods are far more efficient than loading the entire relationship just to get a count. Use them in list views to show "42 comments" without N+1 queries. Alias the attribute: withCount("comments as num_comments") adds $post->num_comments. Order by these attributes: Post::withCount("comments")->orderBy("comments_count", "desc")->get().

Intermediate

Laravel provides several Artisan queue commands for managing background jobs. php artisan queue:work starts a worker that processes jobs continuously. php artisan queue:listen starts a worker that re-boots on each job (slower, catches code changes). php artisan queue:work --queue=emails,default processes specific queues in priority order. php artisan queue:work --tries=3 --timeout=60 sets retry count and timeout. php artisan queue:failed lists all failed jobs. php artisan queue:retry all retries all failed jobs. php artisan queue:retry {id} retries a specific failed job. php artisan queue:flush deletes all failed jobs. php artisan queue:forget {id} deletes a specific failed job. php artisan queue:monitor emails,default --max=100 alerts when a queue exceeds 100 jobs. In production, use Supervisor to keep workers running and restart them after code deployments: php artisan queue:restart gracefully restarts workers.

Intermediate

Both persist model data but work differently. save() is an instance method — call it on a model object to either INSERT (if new) or UPDATE (if the model has a primary key). It fires creating/created events for new models and updating/updated events for existing ones. $user = User::find(1); $user->name = "Alice"; $user->save(). update() is a mass-update method that can be called on a query builder or a model instance: User::where("active", false)->update(["banned" => true]) — updates all matching rows with a single SQL UPDATE. On an instance: $user->update(["name" => "Alice"]) is a shortcut for fill+save. Key difference: query builder update() does NOT fire Eloquent model events or run model observers — it goes directly to the database. Use save() when you need events and observers to fire; use query builder update() for bulk updates.

Intermediate

Global scopes apply automatically to every query on a model — you never need to remember to add the constraint. Create a scope class implementing Scope: class ActiveScope implements Scope { public function apply(Builder $builder, Model $model) { $builder->where("active", true); } }. Register in model's booted(): static::addGlobalScope(new ActiveScope). Remove for a specific query: User::withoutGlobalScope(ActiveScope::class)->get(). Soft delete's automatic filtering is a built-in global scope. Local scopes must be explicitly called in each query. They accept the query builder as the first argument and return it after adding constraints. Define as scopeActivePaid($query), call as User::activePaid()->get(). Local scopes accepting arguments: User::ofType("admin")->get(). Global scopes are best for security and data isolation (multi-tenancy, soft deletes). Local scopes are best for reusable, optional query constraints that make query intent explicit.

Advanced

The Service Layer pattern extracts complex business logic from controllers into dedicated service classes, keeping controllers thin and logic testable. A service class contains methods for domain operations: class UserRegistrationService { public function register(array $data): User { DB::transaction(function() use ($data, &$user) { $user = User::create([...]); $user->profile()->create([...]); event(new UserRegistered($user)); Mail::to($user)->queue(new WelcomeEmail($user)); }); return $user; } }. Inject in controller: public function store(StoreUserRequest $request, UserRegistrationService $service) { $user = $service->register($request->validated()); return redirect()->route("dashboard"); }. Benefits: business logic is reusable across controllers, commands, and queue jobs; logic is unit-testable without HTTP overhead; controllers become thin orchestrators. Laravel's service container automatically injects service dependencies. Combine with repositories for full separation: Controller → Service → Repository → Database.

Advanced

hasMany represents a one-to-many relationship: one Post has many Comments. The foreign key (post_id) lives in the comments table. Direct relationship — no intermediate table. belongsToMany represents a many-to-many relationship: a User has many Roles, and a Role has many Users. Requires a pivot table (role_user) containing both foreign keys. Define on both models: User::roles() returns $this->belongsToMany(Role::class). Attach: $user->roles()->attach($roleId). Detach: detach(). Sync (replace all): sync([$roleId1, $roleId2]). Access pivot data: $user->roles()->first()->pivot->created_at. Store extra pivot columns: ->withPivot("level")->withTimestamps(). Create a custom Pivot model with ->using(UserRole::class) for custom pivot logic.

Advanced
Back to All Topics 100 questions total