Introduction to Laravel
Laravel is a sophisticated PHP web framework designed for the development of web applications
following the Model-View-Controller (MVC) architectural pattern. Since its inception,
Laravel has focused on "Developer Happiness," prioritizing a syntax that is both expressive
and elegant. It abstracts the complex, repetitive aspects of web development—such as
authentication, routing, sessions, and caching—allowing developers to focus on building
unique application logic rather than reinventing foundational components.
By leveraging a modular packaging system and a robust dependency manager (Composer), Laravel
provides a rich ecosystem of tools that scale from small hobby projects to enterprise-level
applications. It stands out in the PHP ecosystem by integrating modern functional
programming concepts, a powerful inversion of control (IoC) container, and an intuitive
database abstraction layer.
Core Architectural Philosophy
At its heart, Laravel is built upon the concept of Service Providers. These are the central
place where the application is "bootstrapped." When a request enters a Laravel application,
the framework initializes these providers to bind various components into the Service
Container. This architecture ensures that the framework remains highly decoupled and
testable. Developers can easily swap out core framework behavior by re-binding interfaces to
custom implementations within the container.
Laravel also emphasizes the use of Middleware, which provides a convenient mechanism for
filtering HTTP requests entering your application. For example, Laravel includes middleware
that verifies the user of your application is authenticated. If the user is not
authenticated, the middleware will redirect the user to the login screen. However, if the
user is authenticated, the middleware will allow the request to proceed further into the
application.
Key Technical Features
Laravel provides a comprehensive suite of built-in features that handle the heavy lifting of
modern web development. The following table details the primary components that form the
framework's backbone:
| Component |
Description |
Primary Benefit |
| Eloquent ORM |
An advanced ActiveRecord implementation for working with your database. |
Allows database interaction using PHP syntax instead of raw SQL. |
| Blade Templating |
A powerful, zero-overhead templating engine. |
Compiles into plain PHP code and caches for high performance. |
| Artisan CLI |
A built-in command-line interface. |
Automates repetitive programming tasks and manages migrations. |
| Migrations |
Version control for your database schema. |
Allows teams to modify and share the application's database structure. |
| Routing |
A simple and intuitive way to define application endpoints. |
Supports RESTful controllers and route grouping with ease. |
The Service Container
The Laravel Service Container is a powerful tool for managing class dependencies and
performing dependency injection. Dependency injection is a fancy phrase that essentially
means class dependencies are "injected" into the class via the constructor or, in some
cases, "setter" methods.
<?php
namespace App\Http\Controllers;
import App\Services\TranslatonService;
import App\Http\Controllers\Controller;
class UserController extends Controller
{
/**
* The translation service instance.
*/
protected $translations;
/**
* Create a new controller instance.
*
* @param \App\Services\TranslatonService $translations
* @return void
*/
public function __construct(TranslatonService $translations)
{
// The Service Container automatically resolves the dependency
// and injects it into the constructor.
???? $this->translations = $translations;
}
}
Note
Modern PHP Requirements: Laravel is built to take advantage of the latest PHP
features. Ensure your environment meets the minimum version requirements (typically
PHP 8.2 or higher for current releases) to utilize features like union types,
readonly properties, and attributes.
Server Requirements
While Laravel provides a local development environment via Laravel Sail (a Docker-based
solution), the framework has specific system requirements when deploying to a production
server.
| Requirement |
Minimum Specification |
Reason |
| PHP Version |
PHP >= 8.2.0 |
Core framework dependencies and type-safety. |
| BCMath Extension |
Enabled |
Required for high-precision arithmetic. |
| Ctype Extension |
Enabled |
Character type checking. |
| JSON Extension |
Enabled |
Handling API responses and configuration. |
| OpenSSL Extension |
Enabled |
Secure communication and encryption. |
| PDO Extension |
Enabled |
Database abstraction and security. |
Warning: Security Configuration
After installing Laravel, you must set your application key using the
php artisan key:generate command. Typically, this key is stored in your
.env file. If the application key is not set, your user sessions and other
encrypted data will not be secure!
Installation (Composer & Laravel Sail)
Installing Laravel involves setting up a modern PHP environment that can manage dependencies
and provide a consistent runtime. The framework offers two primary paths for installation:
Composer, the industry-standard PHP dependency manager, and Laravel Sail, a light-weight
command-line interface for interacting with Laravel's default Docker development
environment. While Composer is suitable for developers who prefer managing their own local
server stack (such as Homebrew, Valet, or Herd), Sail is the recommended starting point for
developers who want a pre-configured environment that mirrors production settings without
manual configuration.
Installation via Composer
Before utilizing Composer to create a new project, you must ensure your local machine has PHP
and Composer installed. The create-project command instructs Composer to
download the Laravel framework skeleton and all necessary vendor dependencies. This method
is ideal if you are using a local PHP installation and a dedicated database server like
MySQL or PostgreSQL already running on your host machine.
# Install the Laravel installer globally
composer global require laravel/installer
# Create a new application using the installer
laravel new my-app
# OR: Create a new application directly via Composer
composer create-project laravel/laravel my-app
Once the installation is complete, you should navigate to the project directory. If you are
not using a specialized local development environment like Laravel Herd, you may start
Laravel's local development server using the Artisan CLI. This server will run by default at
http://localhost:8000.
cd my-app
php artisan serve
Installation via Laravel Sail (Docker)
Laravel Sail provides a robust Docker-based development environment that requires no prior
experience with Docker. It automatically configures a container stack including PHP, MySQL,
Redis, and Selenium. This is the preferred method for ensuring that every member of a
development team is working within an identical environment, regardless of their operating
system (macOS, Linux, or Windows via WSL2).
To initiate a new Sail project, you can use a simple terminal command that pulls the
installation script from laravel.build. This script allows you to choose which
"services" (databases or tools) you want to include in your stack.
| Service |
Description |
Purpose |
| mysql |
Relational Database |
Primary data storage. |
| redis |
In-memory Data Store |
Caching and session management. |
| meilisearch |
Search Engine |
Lightning-fast full-text search. |
| mailpit |
Email Testing |
Catches outgoing emails for local preview. |
| selenium |
Browser Automation |
Running end-to-end browser tests. |
The following command demonstrates how to create a new project with specific services:
# Create a new project named 'blog' with mysql and redis
curl -s "https://laravel.build/blog?with=mysql,redis" | bash
# Navigate to the directory
cd blog
# Start the Docker containers in the background
./vendor/bin/sail up -d
Initial Configuration & Environment
Every Laravel application contains a .env file in the root directory. This file
defines the environment-specific variables that vary between your local machine and your
production server. When installing via Composer, you must manually copy the
.env.example file and generate an application key. If you use Laravel Sail or
the Laravel Installer, these steps are typically handled automatically.
# Copy the example environment file
cp .env.example .env
# Generate the unique application encryption key
php artisan key:generate
Note
After installation, you may need to configure permissions for the
storage and bootstrap/cache directories. These directories
must be writable by the web server (or the user running the Docker containers), or
Laravel will throw a 500 error when attempting to write logs or compiled views.
Configuring the Sail Alias
By default, Sail commands are executed using the ./vendor/bin/sail binary
included with your application. To streamline your workflow, it is a best practice to define
a shell alias. This allows you to type
sail instead of the full path to the binary.
# Add this to your ~/.zshrc or ~/.bashrc file
alias sail='[ -f debug.log ] && bash vendor/bin/sail || bash vendor/bin/sail'
# Now you can use sail directly
sail up
sail artisan migrate
Warning: Docker Desktop on Windows
If you are developing on Windows, you must install and enable Windows Subsystem for Linux 2
(WSL2). For optimal performance, ensure your Laravel project files are stored within the
Linux file system (e.g., ~/code/my-app) rather than the Windows file system
(/mnt/c/Users/...), as file system performance across the 9P protocol is
significantly slower.
Configuration & Environment Variables
Laravel provides a robust and centralized system for managing application configuration. All
of the configuration files for the Laravel framework are stored in the config
directory. Rather than hard-coding values into your application logic, Laravel encourages
the use of Environment Variables, which allow you to change configuration values based on
the environment where the application is running (e.g., local development, staging, or
production). This separation of configuration from code is a core tenet of the
"Twelve-Factor App" methodology, ensuring security and portability across different hosting
platforms.
The Environment Configuration
When Laravel is installed, the root directory will contain a .env.example file.
When you create a new project via Composer or Sail, this file is automatically copied to
.env. This file is not intended to be committed to your version control system
(like Git), as it often contains sensitive credentials such as database passwords, API keys,
and application secrets.
The .env file uses a simple KEY=VALUE syntax. Laravel utilizes the
DotEnv PHP library to load these variables into the $_ENV and
$_SERVER PHP superglobals whenever your application receives a request.
APP_NAME=Laravel
APP_ENV=local
APP_KEY=base64:uS6X...
APP_DEBUG=true
APP_URL=http://localhost
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_db
DB_USERNAME=root
DB_PASSWORD=secret
Retrieving Configuration Values
While you can access environment variables directly, it is a best practice to access them
through Laravel's configuration files using the config() helper function. This
provides a layer of abstraction and allows you to define default values if an environment
variable is missing.
In a configuration file (e.g., config/database.php), you use the
env() helper to pull data from the .env file. In your application
code, you then use the config() helper to retrieve that value using "dot"
syntax, which corresponds to the file name and the key within the returned array.
| Helper |
Usage Context |
Purpose |
env('KEY', 'default')
|
Only within config/*.php files.
|
Pulls a raw value from the .env file.
|
config('file.key')
|
Anywhere in the application (Controllers, Views, etc.).
|
Accesses processed configuration values.
|
Example of Configuration Mapping:
// Within config/services.php
return [
'stripe' => [
'model' => App\Models\User::class,
'key' => env('STRIPE_KEY'),
'secret' => env('STRIPE_SECRET'),
],
];
// Within a Controller or Service class
$stripeSecret = config('services.stripe.secret');
Configuration Caching
To give your application a significant speed boost in production, you should cache your
configuration files. When you run the config:cache Artisan command, Laravel
combines all of your configuration files into a single, fast-loading PHP file. This reduces
the filesystem overhead of loading multiple files and parsing the .env file on
every request.
# Combine all config files into a single cached file
php artisan config:cache
# Clear the configuration cache
php artisan config:clear
Warning: The env() Helper Limitation
Once your configuration is cached using php artisan config:cache, the
.env file is no longer loaded. Consequently, the env() helper will
return null for all environment variables. You must ensure that you only call
the env() function within your configuration files, and use the
config() helper throughout the rest of your application.
Determining the Current Environment
The current application environment is determined via the APP_ENV variable in
your .env file. You may access this value via the environment
method on the App facade. This is particularly useful for conditionally
executing code only in specific environments, such as enabling debug tools only on "local"
or "staging" setups.
use Illuminate\Support\Facades\App;
// Check if the environment is specifically 'local'
if (App::environment('local')) {
// The environment is local
}
// Check if the environment is 'local' OR 'staging'
if (App::environment(['local', 'staging'])) {
// The environment is local OR staging
}
Maintenance Mode
When your application is in maintenance mode, a custom view will be displayed for all
requests into your application. This makes it easy to "disable" your application while you
are updating it or performing database migrations.
| Command |
Action |
Key Feature |
php artisan down |
Activates Maintenance Mode. |
Can specify a "secret" bypass cookie. |
php artisan up |
Deactivates Maintenance Mode. |
Restores normal application flow immediately. |
php artisan down --refresh=15 |
Sets a Refresh header. |
Instructs browsers to refresh the page every 15s. |
Note
You can provide a "secret" phrase when putting the site down:
php artisan down --secret="my-password". You can then visit
your-site.com/my-password to receive a bypass cookie, allowing you to
view the site normally while it remains "down" for the rest of the world.
Directory Structure
The default Laravel directory structure is designed to provide a standardized starting point
for both small and large applications. Laravel follows a convention-over-configuration
approach, meaning that while you are free to organize your application however you like,
following the intended structure allows you to leverage the framework's automated features,
such as service provider discovery and auto-loading, with minimal effort.
The Root Directory
The root of a Laravel project contains several folders and hidden files that manage
dependencies, environment variables, and testing suites. Understanding the purpose of these
top-level directories is essential for navigating the framework's lifecycle.
| Directory |
Purpose |
app/ |
Contains the core code of your application (Models, Controllers, Providers).
|
bootstrap/ |
Contains files that bootstrap the framework and configure autoloading. |
config/ |
Houses all of your application's configuration files. |
database/ |
Contains database migrations, model factories, and seeds. |
public/ |
The document root for your web server; contains the
index.php entry point.
|
resources/ |
Contains your views (Blade templates) and uncompiled assets (CSS, JS). |
routes/ |
Defines all of the routes for your application. |
storage/ |
Contains logs, compiled Blade templates, file uploads, and cache. |
tests/ |
Contains your automated tests (Unit and Feature). |
vendor/ |
Contains your Composer dependencies (do not manually edit). |
The app Directory
The app directory is the "heart" of your application. By default, this directory
is namespaced under App and is autoloaded by Composer using the PSR-4
autoloading standard. While many of the subdirectories (like Jobs or
Events) do not exist by default, they are generated automatically when you use
Artisan make commands.
Http/: This folder contains your Controllers, Middleware, and Form
Requests. Almost all logic for handling incoming web requests will reside here.
Models/: This is the default location for all your Eloquent model classes.
These classes allow you to query and store data in your database tables.
Providers/: Contains all of the Service Providers for your application.
Service providers bootstrap your application by binding services into the service
container and registering events.
// Example of a typical Controller location: app/Http/Controllers/UserController.php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\View\View;
class UserController extends Controller
{
public function show(string $id): View
{
return view('user.profile', [
'user' => User::findOrFail($id)
]);
}
}
The routes Directory
Laravel separates routes based on their entry point and intended behavior. This ensures that
middleware—like CSRF protection or session state—is only applied where necessary.
| File |
Description |
web.php |
Routes for your web interface. These include session state and CSRF
protection. |
api.php |
Routes for your API. These are stateless and usually prefixed with
/api.
|
console.php |
Where you define your closure-based console commands. |
channels.php |
Where you register the event broadcasting channels your app supports. |
The storage Directory
The storage directory is divided into three subdirectories. The app
folder is used to store any files generated by your application. The framework
folder stores framework-generated files and caches. Finally, the logs folder
contains your application's log files.
Note
The storage/app/public directory may be used to store user-generated
files, such as profile avatars, that should be publicly accessible. You should
create a symbolic link at public/storage which points to this directory
using the php artisan storage:link command.
The bootstrap Directory
The bootstrap directory contains the app.php file which bootstraps
the framework. This directory also houses a cache directory which contains
framework-generated files for performance optimization such as the route and config cache
files. You should typically not need to modify any files in this directory unless you are
performing advanced framework customizations.
Warning: Permissions
As mentioned in the installation guide, the storage and
bootstrap/cache directories must be writable by your web server. In a
production environment, failing to set the correct chmod or chown
permissions will result in a fatal error during the application boot process.
The public Directory
The public directory contains the index.php file, which is the
entry point for all requests entering your application and configures autoloading. This
directory also houses your assets such as images, JavaScript, and CSS.
# Typical public directory structure
public/
??? css/
??? js/
??? images/
??? .htaccess
??? favicon.ico
??? index.php
Deployment Strategies
Deploying a Laravel application requires more than simply uploading files to a server via
FTP. Because Laravel utilizes modern PHP features, a dependency manager (Composer), and a
build pipeline for frontend assets (Vite), a robust deployment strategy must account for
environment synchronization, database migrations, and performance optimization. A
"zero-downtime" deployment is the gold standard, ensuring that users do not experience
errors or maintenance screens while new code is being propagated.
The Deployment Lifecycle
A standard Laravel deployment follows a specific sequence of operations. This lifecycle
ensures that the application environment is prepared, dependencies are isolated, and the
framework is tuned for production speeds.
| Phase |
Action |
Purpose |
| Preparation |
git pull |
Retrieve the latest stable code from the repository. |
| Dependency Management |
composer install --optimize-autoloader --no-dev |
Install production-ready PHP packages and optimize the class map. |
| Asset Compilation |
npm install && npm run build |
Compile CSS and JavaScript assets using Vite. |
| Database Synchronization |
php artisan migrate --force |
Apply schema changes to the production database. |
| Optimization |
php artisan config:cache,
php artisan route:cache
|
Flatten configuration and routes into single files for speed. |
Automated Deployment Tools
While manual deployments are possible, they are prone to human error. Laravel developers
typically utilize specialized tools that automate the symlinking of directories to achieve
zero-downtime transitions.
- Laravel Forge: A server management tool that handles the provisioning of PHP, Nginx, and
MySQL, while providing a "Push to Deploy" feature triggered by Git webhooks.
- Laravel Vapor: A serverless deployment platform for Laravel, powered by AWS Lambda. It
scales automatically based on demand and removes the need to manage traditional server
software.
- Envoy: A clean, minimal syntax for defining common tasks you run on your remote servers.
It uses Blade-style syntax to define SSH tasks.
Example Envoy Task (Envoy.blade.php):
@servers(['web' => 'user@192.168.1.1'])
@task('deploy', ['on' => 'web'])
cd /var/www/html
git pull origin main
composer install --no-interaction --quiet --optimize-autoloader --no-dev
php artisan migrate --force
php artisan config:cache
php artisan route:cache
php artisan view:cache
@endtask
Zero-Downtime Deployment Structure
To prevent users from seeing a broken site during a file transfer, modern strategies use a
"releases" folder structure. A symlink (symbolic link) points the web server's document root
to the "current" release. When a new deployment finishes, the symlink is updated to point to
the new folder instantaneously.
/var/www/app
??? releases
? ??? 20260101120000
? ??? 20260226200000 (Newest)
??? shared
? ??? .env
? ??? storage
??? current -> /var/www/app/releases/20260226200000
Optimizing for Production
In a development environment, Laravel is configured to be helpful and descriptive. In
production, these same features become performance bottlenecks or security risks.
- Disable Debug Mode: Ensure
APP_DEBUG is set to false in your
production .env file to
prevent sensitive stack traces from being exposed to users.
- Route & Config Caching: Accessing the filesystem to parse multiple files is slow.
Caching these into a single PHP array significantly reduces overhead.
- Autoloader Optimization: Composer's default autoloader is designed for development
flexibility. Using the
--optimize-autoloader flag creates a "class map" for
faster file
discovery.
Warning: The migrate --force Flag
When running migrations in production via a deployment script, you must use the
--force flag. Laravel will natively prompt for confirmation when running
destructive database operations in a production environment; this flag bypasses the prompt
to allow the automated script to finish.
Queue Workers in Production
If your application uses Laravel Queues to handle background tasks (like sending emails), you
must ensure that your queue workers are restarted during deployment. Since queue workers are
long-lived processes, they store the application state in memory. If you update your code
but do not restart the workers, they will continue to execute the old version of your logic.
# Restart the queue workers to load the new code
php artisan queue:restart
Note
You should use a process monitor like Supervisor on your production server.
Supervisor will monitor your php artisan queue:work commands and
automatically restart them if they fail or if the server reboots.
Frontend Tooling (Vite, Livewire, Inertia)
Laravel does not dictate a single way to build your frontend; instead, it provides a
versatile ecosystem that caters to different architectural preferences. Whether you prefer
traditional server-side rendering, modern reactive components, or a hybrid approach,
Laravel’s tooling—centered around Vite, Livewire, and Inertia.js—streamlines the transition
from backend logic to user interface. These tools are designed to bridge the gap between PHP
and JavaScript, ensuring high performance and a seamless developer experience.
Vite: The Modern Asset Bundler
Vite is the default build tool for Laravel applications, replacing the older Webpack-based
Laravel Mix. It serves as an extremely fast development server that provides Hot Module
Replacement (HMR). During development, Vite does not bundle your assets; instead, it serves
your JavaScript modules via native ES modules, making the feedback loop nearly
instantaneous. When you are ready to deploy, Vite bundles your assets into highly optimized
static files for production.
To use Vite within your Blade templates, you use the @vite directive, which
automatically handles the inclusion of the necessary scripts and styles based on whether you
are in development or production mode.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body>
<div id="app"></div>
</body>
</html>
Choosing Your Frontend Stack
Laravel officially supports two primary "stacks" for building interactive applications. Your
choice depends on whether you prefer to stay within the PHP ecosystem or if you want to
leverage a full JavaScript framework like Vue or React.
| Tool |
Architectural Style |
Primary Language |
Use Case |
| Livewire |
Full-stack Framework |
PHP |
High interactivity without leaving Laravel/Blade. |
| Inertia.js |
Classic Monolith with modern SPA |
JS (Vue/React) |
Using JS frameworks without the complexity of a separate API. |
| Blade |
Server-side Rendering |
PHP/HTML |
Traditional multi-page applications with minimal JS. |
Livewire: Reactive PHP
Livewire is a full-stack framework for Laravel that makes building dynamic interfaces as
simple as writing standard Laravel classes. It allows you to create components that "live"
on the server but feel like a Single Page Application (SPA) to the user. When a user
interacts with a Livewire component (e.g., clicks a button), an AJAX request is sent to the
server, the PHP component re-renders, and Livewire intelligently updates only the changed
portion of the DOM.
// app/Livewire/Counter.php
namespace App\Livewire;
use Livewire\Component;
class Counter extends Component
{
public $count = 0;
public function increment()
{
$this->count++;
}
public function render()
{
return view('livewire.counter');
}
}
<div>
<h1>{{ $count }}</h1>
<button wire:click="increment">+</button>
</div>
Inertia.js: The Modern Monolith
Inertia allows you to build single-page apps using Vue, React, or Svelte, but without the
complexity of building a separate REST or GraphQL API. It acts as the "glue" between your
Laravel backend and your JavaScript frontend. When using Inertia, you define your routes and
controllers in Laravel as usual, but instead of returning a Blade view, you return an
Inertia component.
// app/Http/Controllers/UserController.php
use Inertia\Inertia;
public function show(User $user)
{
// Pass data directly to a Vue/React component
return Inertia::render('User/Profile', [
'user' => $user
]);
}
Note
For rapid scaffolding, Laravel offers Breeze and Jetstream. Breeze provides a
minimal, simple implementation of all Laravel's authentication features using either
Blade/Livewire or Vue/React via Inertia. Jetstream is a more robust application
scaffolding that includes team management, API support, and two-factor
authentication.
Warning: Production Asset Building
In your development environment, you typically run npm run dev to start the Vite
development server. However, before deploying to production, you must run
npm run build. This command generates the versioned manifest and minified
assets required for the @vite directive to function correctly on a live server.
# Development (with HMR)
npm run dev
# Production Build
npm run build
The Request Lifecycle
Understanding the entry point and exit of a request is fundamental to mastering Laravel. When
a user interacts with your application, the request travels through a meticulously
orchestrated series of layers. This lifecycle ensures that the environment is prepared,
security is enforced, and dependencies are resolved before your specific application logic
is ever executed. By understanding this flow, you can pinpoint exactly where to hook into
the framework to modify behavior, handle global exceptions, or inject custom functionality.
The Entry Point: public/index.php
All requests to a Laravel application are directed by your web server (Apache or Nginx) to
the public/index.php file. This file is the "front controller" for the entire
application. It serves a humble but critical purpose: it loads the Composer-generated
autoloader definitions and then retrieves an instance of the Laravel application from
bootstrap/app.php. This initial step transitions the process from a raw PHP
script into the managed environment of the Laravel framework.
HTTP and Console Kernels
Once the application instance is created, the request is handed off to either the HTTP Kernel
or the Console Kernel, depending on the nature of the entry. For web requests, the HTTP
Kernel (Illuminate\Foundation\Http\Kernel) is responsible for the heavy
lifting. The Kernel defines a list of Bootstrappers that are run before the request is
executed. These bootstrappers perform high-level tasks such as:
- Configuring error handling and logging.
- Detecting the application environment.
- Loading and parsing configuration files.
- Initializing the Service Container.
The HTTP Kernel also defines the Middleware stack. All requests must pass through these
global middleware layers (such as session handling and CSRF protection) before reaching your
routes.
Service Providers: The Heart of Bootstrapping
One of the most important actions the Kernel performs is starting the Service Providers.
Service providers are the central place to configure and bootstrap all of Laravel's core
components, such as the database, queue, and validation. The lifecycle of a service provider
occurs in two distinct phases, detailed in the table below:
| Phase |
Method |
Description |
| Registration |
register() |
Binds things into the Service Container. No other services should be used
here, as they may not be loaded yet. |
| Bootstrapping |
boot() |
Called after all providers are registered. You can safely access any service
or dependency here. |
Once all providers have been registered and booted, the request is finally handed to the
Router for dispatching.
Routing and Controller Execution
The Router matches the incoming HTTP request to a specific route defined in your
routes/web.php or routes/api.php files. If the route points to a
Controller, the Service Container automatically resolves the controller class and its
dependencies, injecting them via the constructor.
// Example of a route dispatching to a controller
use App\Http\Controllers\UserController;
use Illuminate\Support\Facades\Route;
// The Router directs the request here
Route::get('/profile/{id}', [UserController::class, 'show']);
// Inside the Controller
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\View\View;
class UserController extends Controller
{
/**
* The Request has passed through the Kernel and Middleware
* and is now being handled by the specific logic below.
*/
public function show(string $id): View
{
$user = User::findOrFail($id);
return view('user.profile', ['user' => $user]);
}
}
The Final Response
After your controller or route closure returns a value (like a View, a String, or a JSON
object), it travels back out through the middleware stack. This "outgoing" trip allows
middleware to modify the response—for example, by adding HTTP headers or minifying the HTML
output. Finally, the response is sent back to the user's browser, and the HTTP Kernel
executes any "terminable" middleware (tasks that happen after the response is sent, like
logging or clean-up).
Note
The Singleton Pattern
Many of the core objects in the request lifecycle, such as the
Application and Kernel instances, are registered as
Singletons. This means the same instance is used throughout the entire lifecycle of
a single request, ensuring consistent state across different parts of your code.
Warning: Statelessness in Octane
If you are using Laravel Octane (running on Swoole or RoadRunner), the request lifecycle
changes slightly. The application is "booted" once and stays in memory across multiple
requests. You must be careful not to store request-specific data in static variables or
singletons, as that data will persist into subsequent users' requests.
Service Container (Dependency Injection)
The Laravel Service Container is the most critical component of the framework’s architecture.
It is a powerful tool for managing class dependencies and performing Dependency Injection.
At its core, the container is a registry that stores "blueprints" for how to create objects.
When you request an object from the container, it automatically inspects the class's
constructor, identifies its dependencies, and recursively resolves them until the final
object is fully instantiated and ready for use.
By centralizing how objects are created, the Service Container promotes a decoupled,
testable, and maintainable architecture. It allows you to swap implementations of a specific
interface without modifying the high-level code that consumes it.
Understanding Dependency Injection
Dependency Injection (DI) is a design pattern where an object's dependencies are "injected"
into it rather than the object creating them itself. This is primarily achieved through
Constructor Injection. Instead of using the new keyword inside a class, you
type-hint the required dependency in the constructor. Laravel's container uses PHP's
Reflection API to automatically provide the correct instance.
| Concept |
Manual Instantiation |
Dependency Injection (Laravel) |
| Control |
Class creates its own dependencies. |
Dependencies are provided externally. |
| Coupling |
High (Hard-coded to a specific class). |
Low (Code against an Interface). |
| Testability |
Difficult; cannot easily mock objects. |
Easy; can inject mock versions of dependencies. |
Example of Constructor Injection:
<?php
namespace App\Http\Controllers;
use App\Services\PaymentGateway;
use Illuminate\Http\Request;
class OrderController extends Controller
{
/**
* The payment gateway instance.
*/
protected $paymentGateway;
/**
* Create a new controller instance.
* The Service Container automatically injects the PaymentGateway.
*/
public function __construct(PaymentGateway $paymentGateway)
{
$this->paymentGateway = $paymentGateway;
}
public function store(Request $request)
{
// Use the injected service
$this->paymentGateway->charge($request->amount);
}
}
Binding into the Container
While Laravel can automatically resolve simple classes that do not require configuration, you
often need to tell the container how to build more complex objects. This is done via
Bindings within a Service Provider.
There are three primary ways to bind a service into the container:
- Simple Bindings: A new instance is created every time the service is resolved.
- Singletons: The instance is created once and shared throughout the entire request
lifecycle.
- Scoped: Similar to singletons, but specific to a lifecycle (useful in Laravel Octane).
// app/Providers/AppServiceProvider.php
public function register(): void
{
// Simple Binding
$this->app->bind(TranslatonService::class, function ($app) {
return new TranslatonService(config('app.locale'));
});
// Singleton Binding
$this->app->singleton(DatabaseConnection::class, function ($app) {
return new DatabaseConnection(config('db.host'));
});
}
Binding Interfaces to Implementations
One of the most powerful features of the Service Container is the ability to bind an
interface to a concrete implementation. This allows your application to remain agnostic of
the specific library or service being used.
| Interface |
Implementation A |
Implementation B |
Filesystem
|
LocalFilesystem
|
S3Filesystem
|
SmsGateway
|
TwilioService
|
VonageService
|
Example of Interface Binding:
// In a Service Provider
$this->app->bind(
\App\Contracts\SmsGateway::class,
\App\Services\TwilioSmsGateway::class
);
// In your Controller
// Even if you change the binding to Vonage, this code remains the same.
public function __construct(\App\Contracts\SmsGateway $sms)
{
$this->sms = $sms;
}
Contextual Binding
Occasionally, you may have two classes that use the same interface, but you wish to inject
different implementations into each class. This is handled via Contextual Binding.
use App\Http\Controllers\PhotoController;
use App\Http\Controllers\VideoController;
use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Support\Facades\Storage;
$this->app->when(PhotoController::class)
->needs(Filesystem::class)
->give(function () {
return Storage::disk('local');
});
$this->app->when(VideoController::class)
->needs(Filesystem::class)
->give(function () {
return Storage::disk('s3');
});
Note
Zero-Configuration Resolution
If a class has no dependencies or only depends on other concrete classes (not
interfaces), the container does not need to be told how to resolve that class. You
can simply type-hint it, and Laravel will instantiate it for you automatically.
Warning: Avoiding the "Service Locator" Pitfall
While you can manually resolve classes using app()->make() or
resolve(), this is generally considered an anti-pattern known as "Service
Location." It hides your class's dependencies and makes testing harder. Always prefer
Constructor Injection whenever possible.
Service Providers
Service providers are the central assembly point for all Laravel applications. While the
Service Container is the "toolbox" that holds your objects, Service Providers are the
"instruction manuals" that tell Laravel how to put those tools together. They are
responsible for bootstrapping the core framework services as well as your own application’s
custom services, such as database connections, queue workers, and event listeners.
Virtually every major component in Laravel—from the Router to the Validation engine—is
initialized via a service provider. Without these providers, the framework would be a
collection of disconnected classes with no knowledge of how to interact with one another.
The Provider Lifecycle
A service provider contains two primary methods: register and boot.
Understanding the distinction between these two is critical for avoiding "class not found"
errors or trying to use services before they have been initialized.
| Method |
Timing |
Purpose |
register |
First Phase |
Only used to bind things into the Service Container.
You should never execute logic or use other services here.
|
boot |
Second Phase |
Called after all other service providers have been
registered.
You can safely use any service (database, routes, etc.) here.
|
The register Method
Within the register method, you should only ever bind implementation classes to
the service container. Do not attempt to register any event listeners, routes, or any other
piece of functionality within this method. If you do, you may accidentally use a service
that is provided by another service provider which has not loaded yet.
<?php
namespace App\Providers;
use App\Services\RiotApiService;
use Illuminate\Support\ServiceProvider;
class RiotServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
// Binding a singleton into the container
$this->app->singleton(RiotApiService::class, function ($app) {
return new RiotApiService(config('services.riot.key'));
});
}
}
The boot Method
The boot method is called after all other service providers have been
registered. This means you have access to all other services that have been registered by
the framework. This is the ideal place to register view composers, listen for events, or
define custom validation rules.
<?php
namespace App\Providers;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*/
public function boot(): void
{
// Sharing a variable across all Blade views
View::share('site_name', config('app.name'));
// Registering a custom validation rule
\Illuminate\Support\Facades\Validator::extend('uppercase', function ($attribute, $value) {
return strtoupper($value) === $value;
});
}
}
Deferred Providers
If your provider is only registering bindings in the service container, you may choose to
defer its registration until one of those bindings is actually needed. Deferring the loading
of a provider improves the performance of your application because it is not loaded from the
filesystem on every request.
To defer a provider, implement the
\Illuminate\Contracts\Support\DeferrableProvider interface and define a
provides method. The provides method should return the service
container bindings registered by the provider.
<?php
namespace App\Providers;
use App\Services\PaymentGateway;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\ServiceProvider;
class PaymentServiceProvider extends ServiceProvider implements DeferrableProvider
{
public function register(): void
{
$this->app->singleton(PaymentGateway::class, function ($app) {
return new PaymentGateway();
});
}
/**
* Get the services provided by the provider.
*/
public function provides(): array
{
return [PaymentGateway::class];
}
}
Note
Registering Your Providers
In modern Laravel versions (Laravel 11+), service providers are typically registered
in the bootstrap/providers.php configuration file. In older versions,
they were listed in the providers array within
config/app.php. When you use the php artisan make:provider
command, Laravel will often offer to add the provider to this file for you
automatically.
Warning: Logic in the register Method
Placing logic that relies on other services (like calling Auth::user() or
Route::current()) inside the register method is a common source of
"Target class [X] does not exist" errors. Always move such logic to the boot
method to ensure the entire container is ready.
Core Service Providers
Every Laravel application comes with several built-in providers that handle essential
framework logic:
| Provider |
Description |
AppServiceProvider |
The general-purpose provider for your application logic. |
AuthServiceProvider |
Used to define authorization policies and gates. |
EventServiceProvider |
Registers your application's event-to-listener mappings. |
RouteServiceProvider |
Configures route loading, rate limiting, and route-model binding. |
Facades
Facades provide a "static" interface to classes that are available in the application's
Service Container. Laravel ships with many facades which provide access to almost all of
Laravel's features. Unlike traditional static methods, Laravel facades serve as dynamic
proxies to underlying objects in the service container, providing the benefit of a terse,
expressive syntax while maintaining more testability and flexibility than traditional static
methods.
Essentially, a facade is a class that uses the __callStatic() magic method to
redirect static calls to an object resolved from the container. This allows you to use
Cache::get('key') instead of manually resolving the cache repository from the
container and calling the method on the instance.
How Facades Work
In a Laravel application, a facade is a class that extends the base
Illuminate\Support\Facades\Facade class. The only requirement for a facade is
to define the getFacadeAccessor method. This method’s job is to return the name
of a service container binding (a string or an interface).
When you call a static method on a Facade, Laravel resolves the underlying instance from the
service container and executes the method against that instance. This "indirection" is what
allows facades to be easily mocked during testing.
| Component |
Role |
| The Facade Class |
The static "proxy" you interact with (e.g., Route,
DB).
|
getFacadeAccessor() |
Returns the string key used to look up the object in the container. |
| The Underlying Class |
The actual instance (service) performing the work. |
| Service Container |
The registry where the underlying class is stored as a singleton. |
Facade vs. Dependency Injection
One of the primary advantages of facades is their brevity. They allow you to use Laravel
features without needing to inject long lists of dependencies into your controller
constructors. However, some developers prefer Dependency Injection because it makes a
class’s dependencies explicit.
| Feature |
Facades |
Dependency Injection |
| Syntax |
Terse and "Static-like" (Cache::put(...)). |
Verbose ($this->cache->put(...)). |
| Discovery |
Dependencies are hidden inside methods. |
Dependencies are visible in the constructor. |
| Scope |
Available anywhere in the application. |
Requires the class to be resolved by the container. |
| Testing |
Easy to mock via built-in methods. |
Easy to mock via standard testing libraries. |
Example of Facade Usage:
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Cache;
use App\Http\Controllers\Controller;
class UserController extends Controller
{
/**
* Show the profile for the given user.
*/
public function showProfile(string $id)
{
// Using the Cache facade to proxy a call to the Cache Manager
$user = Cache::remember("user.{$id}", 60, function () use ($id) {
return \App\Models\User::find($id);
});
return view('user.profile', ['user' => $user]);
}
}
Creating Custom Facades
You can create your own facades for your application's services. This involves three steps:
creating the service class, binding that class in a Service Provider, and creating the
Facade class itself.
// 1. The Underlying Service
namespace App\Services;
class Analytics
{
public function track(string $event) { /* Logic */ }
}
// 2. The Facade Class
namespace App\Support\Facades;
use Illuminate\Support\Facades\Facade;
class Analytics extends Facade
{
protected static function getFacadeAccessor() { return 'analytics'; }
}
// 3. The Binding (in AppServiceProvider)
$this->app->bind('analytics', function ($app) {
return new \App\Services\Analytics();
});
Now you can call Analytics::track('page_view') from anywhere in your
application.
Testing with Facades
Because facades are proxies to objects resolved from the container, we can easily swap the
real object with a "mock" or a "fake" during a test. Laravel’s built-in facades provide
testing helpers that make this process incredibly simple, ensuring you don't actually hit
the database or send real emails during a unit test.
use Illuminate\Support\Facades\Cache;
public function test_cache_is_called()
{
// Instruct the facade to behave as a mock
Cache::shouldReceive('get')
->once()
->with('key')
->andReturn('value');
// This call will hit the mock, not the real cache driver
$value = Cache::get('key');
$this->assertEquals('value', $value);
}
Note
Real-Time Facades
Laravel allows you to treat any class in your app directory as a facade
on the fly. By prefixing the namespace with Facades\, Laravel will
automatically generate a facade for that class. For example:
use Facades\App\Services\PaymentGateway;.
Warning: Facade "Scope Creep"
It is easy to let your controllers become bloated when using Facades, as there is no
"constructor pain" to alert you that a class has too many responsibilities. If you find
yourself using 10+ different facades in a single controller, consider refactoring that logic
into smaller, dedicated service classes.
Starter Kits (Breeze & Jetstream)
Laravel provides two primary "starter kits" to jumpstart the development of new applications:
Laravel Breeze and Laravel Jetstream. These kits are not merely templates; they are fully
functional application scaffolds that include pre-configured authentication, registration,
password reset, email verification, and password confirmation. By using a starter kit,
developers can skip the repetitive task of building secure authentication systems and move
directly to implementing their application’s unique core logic.
Laravel Breeze: The Minimalist Choice
Laravel Breeze is a minimal, simple implementation of all of Laravel's authentication
features. It is built using Blade templates and Tailwind CSS, providing a "classic"
development experience that is easy to customize. For developers who prefer a modern
JavaScript frontend, Breeze also offers "stacks" for Vue and React via Inertia.js.
Breeze is designed to be the "starting point" for most projects. It publishes all of its
controllers, routes, and views directly into your application's directory structure,
allowing you to modify every line of code as if you had written it yourself from scratch.
| Feature |
Description |
Implementation |
| Styling |
Utility-first CSS. |
Tailwind CSS |
| Stack Options |
Blade, Livewire, Vue, or React. |
Multi-stack support |
| Authentication |
Login, Registration, Password Reset. |
Standard Controllers |
| Complexity |
Low. |
Ideal for beginners or simple apps. |
Installing Breeze:
# Install the package via Composer
composer require laravel/breeze --dev
# Run the installation command and choose your stack
php artisan breeze:install
# Migrate the database and build assets
php artisan migrate
npm install && npm run dev
Laravel Jetstream: The Advanced Ecosystem
Laravel Jetstream is a robust application starter kit that provides a more sophisticated
starting point. It includes everything found in Breeze but adds advanced features required
for enterprise-level SaaS (Software as a Service) applications, such as team management, API
support via Laravel Sanctum, and two-factor authentication.
Jetstream offers two choices for its frontend implementation: Livewire or Inertia.js. Unlike
Breeze, Jetstream utilizes "Actions"—simple classes that perform a single task—to handle
application logic. This keeps your controllers thin and ensures that logic like "Create
Team" can be reused across your web UI and your API.
| Feature |
Description |
Implementation |
| Teams |
Create and manage user groups. |
Built-in Team Model & Logic |
| Security |
2FA and Browser Session management. |
Integrated UI/Logic |
| API Support |
Issue API tokens to users. |
Laravel Sanctum |
| Profile |
Integrated profile photos and info. |
Pre-built UI components |
Installing Jetstream:
# Install Jetstream via Composer
composer require laravel/jetstream
# Install Jetstream with the Livewire stack and Teams support
php artisan jetstream:install livewire --teams
# Finalize the installation
php artisan migrate
npm install && npm run build
Choosing the Right Kit
The decision between Breeze and Jetstream depends on the complexity of your project and your
comfort level with advanced Laravel concepts.
| Criteria |
Choose Breeze If... |
Choose Jetstream If... |
| Project Size |
Building a personal blog or simple MVP. |
Building a SaaS or complex portal. |
| Control |
You want simple, readable controllers. |
You prefer a structured "Action" pattern. |
| Features |
You only need basic login/register. |
You need Teams, 2FA, or API tokens. |
| Experience |
You are new to Laravel. |
You are an experienced Laravel developer. |
Note
The Role of Laravel Fortify
Under the hood, both Breeze and Jetstream (specifically the Jetstream backend) rely
on Laravel Fortify. Fortify is a frontend-agnostic authentication backend for
Laravel. While Breeze provides its own controllers, Jetstream uses Fortify to handle
the "invisible" logic of logging in and registering, allowing you to focus purely on
the UI.
Warning: Modifying Starter Kit Code
Once a starter kit is installed, the code is yours. There is no "update" command to pull in
new features from Breeze or Jetstream into an existing project. Because these kits publish
code directly into your app/ and resources/ directories, any
future framework updates must be applied manually to your customized versions of these
files.
Routing
In Laravel, routing is the process of accepting an incoming HTTP request and redirecting it
to the appropriate piece of logic, typically a controller or an anonymous function
(closure). All Laravel routes are defined in your route files, which are located in the
routes directory. These files are automatically loaded by the framework's
App\Providers\RouteServiceProvider. This centralized approach ensures that your
application's entire URL structure is visible, manageable, and highly decoupled from the
underlying business logic.
Basic Routing
The most basic Laravel routes accept a URI and a closure, providing a very simple and
expressive method of defining routes and behavior without complicated configuration files.
use Illuminate\Support\Facades\Route;
Route::get('/greeting', function () {
return 'Hello World';
});
Laravel supports all standard HTTP verbs, allowing you to build RESTful interfaces
efficiently. The following table illustrates the primary routing methods available:
| Method |
HTTP Verb |
Purpose |
Route::get($uri, $callback) |
GET |
Retrieve a resource or display a page. |
Route::post($uri, $callback) |
POST |
Create a new resource or submit a form. |
Route::put($uri, $callback) |
PUT |
Replace an existing resource entirely. |
Route::patch($uri, $callback) |
PATCH |
Partially update an existing resource. |
Route::delete($uri, $callback) |
DELETE |
Remove a resource from the system. |
Route::options($uri, $callback) |
OPTIONS |
Inspect communication options for a resource. |
Route Parameters
Often you will need to capture segments of the URI within your route. For example, you may
need to capture a user's ID from the URL. You may do so by defining route parameters, which
are always encased within curly braces {} and should consist of alphabetic characters.
Required Parameters
Route::get('/user/{id}', function (string $id) {
return 'User '.$id;
});
Optional Parameters
Occasionally you may need to specify a route parameter, but make the presence of that
parameter optional. You may do so by placing a ? mark after the parameter name. Ensure you
give the corresponding variable a default value.
Route::get('/user/{name?}', function (?string $name = 'John') {
return $name;
});
Named Routes
Named routes allow the convenient generation of URLs or redirects for specific routes. You
may specify a name for a route by chaining the name method onto the route
definition. This is a best practice because it allows you to change the URI of a route in
one place without having to update every link throughout your application.
// Defining the named route
Route::get('/user/profile', function () {
// ...
})->name('profile');
// Generating a URL to the route
$url = route('profile');
// Generating a Redirect
return redirect()->route('profile');
Route Groups and Middleware
Route groups allow you to share route attributes, such as middleware, across a large number
of routes without needing to define those attributes on every individual route.
- Middleware: Assign middleware to all routes within a group.
- Controllers: Assign a common controller to a group of routes.
- Prefixes: Prefix every URI in the group with a specific string (e.g.,
/admin).
- Name Prefixes: Prefix every route name in the group.
Route::middleware(['auth'])->group(function () {
Route::get('/dashboard', function () {
// Only authenticated users may access this...
});
Route::get('/account', function () {
// ...
});
});
Route::prefix('admin')->group(function () {
Route::get('/users', function () {
// Matches the "/admin/users" URL
});
});
Note
CSRF Protection
Any HTML forms pointing to POST, PUT, PATCH,
or DELETE routes that are defined in the web routes file
should include a CSRF token field. Otherwise, the request will be rejected. You can
use the @csrf Blade directive to generate the token field:
<form method="POST" action="/profile">
@csrf
...
</form>
Warning: Route Caching
For a significant performance boost in production, you should utilize Laravel's route cache.
Use the php artisan route:cache command. However, route caching does not work
with Closure-based routes. To cache your routes, you must convert all Closure routes to
Controller classes.
Route-Model Binding
Laravel automatically resolves Eloquent models defined in routes or controller actions whose
type-hinted variable names match a route segment name.
use App\Models\User;
// Laravel automatically finds the User instance with the ID provided in the URL
Route::get('/users/{user}', function (User $user) {
return $user->email;
});
Middleware
Middleware provides a convenient mechanism for inspecting and filtering HTTP requests
entering your application. Conceptually, you can visualize middleware as a series of
"layers" that a request must pass through before it reaches your route closure or
controller. Each layer can examine the request and even reject it entirely. For example,
Laravel includes middleware that verifies the user of your application is authenticated; if
the user is not authenticated, the middleware will redirect the user to the login screen.
Conversely, if the user is authenticated, the middleware will allow the request to proceed
further into the application.
How Middleware Works
All middleware in Laravel are classes that contain a handle method. This method
receives the incoming $request and a $next closure. Calling the
$next closure passes the request deeper into the application, eventually
reaching the intended route. If a middleware performs a task before calling
$next, it is considered "Before Middleware." If it performs a task after
calling $next (on the response object), it is "After Middleware."
| Type |
Timing |
Use Case |
| Before Middleware |
Before the request hits the controller. |
Authentication, CSRF check, Request logging. |
| After Middleware |
After the controller logic executes. |
Adding security headers, Minifying HTML, Response logging. |
Example of a Basic Middleware:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureTokenIsValid
{
/**
* Handle an incoming request.
*/
public function handle(Request $request, Closure $next): Response
{
// Before logic: Check if the request contains a specific token
if ($request->input('token') !== 'my-secret-token') {
return redirect('home');
}
// Pass the request to the next layer
return $next($request);
}
}
Registering Middleware
In modern Laravel (version 11 and above), middleware is primarily configured in the
bootstrap/app.php file. You can define global middleware that runs on every
request, or assign middleware to specific route groups.
Global Middleware
Global middleware runs on every single HTTP request to your application, including API and
Web routes.
// bootstrap/app.php
return Application::configure(basePath: dirname(__DIR__))
->withMiddleware(function (Middleware $middleware) {
$middleware->append(EnsureTokenIsValid::class);
})
->create();
Assigning Middleware to Routes
If you want to assign middleware to specific routes, you may use the middleware
method when defining the route.
use App\Http\Middleware\EnsureTokenIsValid;
Route::get('/profile', function () {
// ...
})->middleware(EnsureTokenIsValid::class);
// You can also assign multiple middleware
Route::get('/', function () {
// ...
})->middleware([First::class, Second::class]);
Middleware Groups
To make route definitions cleaner, Laravel groups several middleware under a single key. By
default, Laravel provides web and api groups. The web
group includes middleware for sessions, CSRF protection, and cookie encryption, while the
api group is stripped down for stateless communication.
| Group |
Included Middleware (Partial List) |
Purpose |
web |
EncryptCookies,
StartSession,
VerifyCsrfToken
|
Standard browser-based web pages. |
api |
ThrottleRequests,
SubstituteBindings
|
Stateless API endpoints. |
Middleware Parameters
Middleware can also receive additional custom parameters. For example, if your application
needs to verify that the authenticated user has a specific "role" before performing an
action, you can pass that role name to the middleware.
// Middleware Definition
public function handle(Request $request, Closure $next, string $role): Response
{
if (! $request->user()->hasRole($role)) {
abort(403);
}
return $next($request);
}
// Route Definition
Route::put('/post/{id}', function (string $id) {
// ...
})->middleware('role:editor');
Note
Terminable Middleware
Sometimes a middleware may need to do some work after the HTTP response has already
been sent to the browser. If you define a terminate method on your
middleware, it will automatically be called after the response is sent. This is
useful for heavy logging or cleaning up resources that shouldn't delay the user's
page load.
Warning: CSRF Middleware
The VerifyCsrfToken middleware is included in the web middleware
group by default. If you are building an API that will be consumed by mobile
apps or third-party services (stateless), you should place those routes in the api group or
the api.php file to avoid 419 "Page Expired" errors caused by missing CSRF
tokens.
CSRF Protection
Cross-Site Request Forgery (CSRF) is a type of malicious exploit where unauthorized commands
are transmitted from a user that the web application trusts. Unlike Cross-Site Scripting
(XSS), which aims to steal data, CSRF targets state-changing requests, such as changing a
user's password, deleting data, or transferring funds. Laravel protects your application
from these attacks by automatically generating a CSRF "token" for each active user session
managed by the application. This token is used to verify that the authenticated user is the
one actually making the requests to the application.
How CSRF Protection Works
Laravel’s VerifyCsrfToken middleware, which is included in the web
middleware group by default, automatically verifies that the token in the request input
matches the token stored in the user's session. When a user session is initialized, Laravel
generates a random string as the CSRF token. This token is then compared against the value
submitted in subsequent POST, PUT, PATCH, or DELETE requests. If the tokens do not match,
the request is rejected with a 419 HTTP status code (Page Expired).
| Component |
Responsibility |
| User Session |
Stores the "known good" token on the server side. |
| Blade Directive |
Injects a hidden input field with the token into HTML forms. |
| X-CSRF-TOKEN |
An HTTP header used by JavaScript frameworks to pass the token. |
| VerifyCsrfToken |
Middleware that intercepts requests to compare the submitted token with the
session. |
Protecting HTML Forms
Any time you define an HTML form in your application that points to a POST,
PUT, PATCH, or DELETE route, you must include a
hidden CSRF token field. This allows the CSRF protection middleware to validate the request.
For convenience, Laravel provides the @csrf Blade directive to generate the
hidden input field automatically:
<form method="POST" action="/profile/update">
@csrf
<label for="email">New Email Address:</label>
<input type="email" name="email" id="email">
<button type="submit">Update Email</button>
</form>
CSRF and JavaScript (AJAX)
When building JavaScript-driven applications (like those using Axios or Fetch), it is often
impractical to manually pass a CSRF token as a request parameter. To solve this, Laravel
stores the current CSRF token in an encrypted XSRF-TOKEN cookie. Most modern
JavaScript libraries, including Axios, will automatically read this cookie and send it along
with every outgoing request in the X-XSRF-TOKEN header.
If you are not using a library that handles this automatically, you can manually include a
meta tag in your application's <head> and then configure your JS to read
from it:
<meta name="csrf-token" content="{{ csrf_token() }}">
<script>
// Example of manually setting the header in a fetch request
fetch('/api/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
},
body: JSON.stringify({ data: 'example' })
});
</script>
Excluding URIs from CSRF Protection
Occasionally, you may wish to exclude a set of URIs from CSRF protection. This is common when
you are expecting incoming webhooks from external services (like Stripe or GitHub) that do
not have access to your user's session tokens.
In Laravel 11, you can exclude these routes within the bootstrap/app.php file
using the validateCsrfTokens method:
// bootstrap/app.php
return Application::configure(basePath: dirname(__DIR__))
->withMiddleware(function (Middleware $middleware) {
$middleware->validateCsrfTokens(except: [
'stripe/*',
'http://example.com/foo/bar',
'http://example.com/foo/*',
]);
})
->create();
Warning: Security Risk of Exclusion
You should be extremely cautious when excluding routes from CSRF protection. Only exclude
routes that are strictly used for machine-to-machine communication (APIs/Webhooks). Never
exclude a route that performs a state change (like updating a user profile) if that route is
accessed via a standard web browser.
Note
CSRF and Sessions
CSRF protection is tied directly to the Laravel Session. If your session driver is
set to null (common in stateless APIs), CSRF protection will not
function correctly because there is no server-side token to compare against. For
stateless APIs, you should use the api middleware group, which does not
include the CSRF verification layer.
Controllers
Instead of defining all of your request handling logic as closures in your route files, you
may wish to organize this behavior using controller classes. Controllers can group related
request handling logic into a single class. For example, a UserController might
handle all incoming requests related to users, including showing, creating, updating, and
deleting users. Controllers are typically stored in the app/Http/Controllers
directory.
Writing Controllers
A basic controller should extend the base controller class included with Laravel:
App\Http\Controllers\Controller. This base class provides several convenience
methods, such as the middleware method, which may be used to attach middleware
to controller actions.
When a route points to a controller, Laravel's Service Container automatically resolves the
controller and injects any type-hinted dependencies into the constructor or the specific
method being called.
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\View\View;
use App\Http\Controllers\Controller;
class UserController extends Controller
{
/**
* Show the profile for a given user.
*/
public function show(string $id): View
{
return view('user.profile', [
'user' => User::findOrFail($id)
]);
}
}
Once you have defined a controller, you can register a route to the controller action like
so:
use App\Http\Controllers\UserController;
use Illuminate\Support\Facades\Route;
Route::get('/user/{id}', [UserController::class, 'show']);
Single Action Controllers
If a controller action is particularly complex, you might find it convenient to dedicate an
entire controller class to that single action. To accomplish this, you may define a single
__invoke method within the controller. When registering routes for
single-action controllers, you do not need to specify a controller method.
<?php
namespace App\Http\Controllers;
use App\Models\User;
use App\Http\Controllers\Controller;
class ProvisionServer extends Controller
{
/**
* Provision a new web server.
*/
public function __invoke()
{
// ... Logic for provisioning
}
}
// Routing for the invokable controller
Route::post('/server', ProvisionServer::class);
Resource Controllers
Laravel resource routing assigns the typical "CRUD" (Create, Read, Update, Delete) routes to
a controller with a single line of code. You can quickly create a resource controller using
the Artisan CLI command:
php artisan make:controller PhotoController --resource.
The following table demonstrates the actions handled by a resource controller and their
corresponding HTTP verbs/URIs:
| Verb |
URI |
Action |
Route Name |
Purpose |
| GET |
/photos |
index |
photos.index |
Display a list of resources. |
| GET |
/photos/create |
create |
photos.create |
Show the form to create a resource. |
| POST |
/photos |
store |
photos.store |
Store a new resource in the database. |
| GET |
/photos/{photo} |
show |
photos.show |
Display a specific resource. |
| GET |
/photos/{photo}/edit |
edit |
photos.edit |
Show the form to edit a resource. |
| PUT/PATCH |
/photos/{photo} |
update |
photos.update |
Update a specific resource. |
| DELETE |
/photos/{photo} |
destroy |
photos.destroy |
Delete a specific resource. |
To register all these routes at once, use the resource method:
use App\Http\Controllers\PhotoController;
Route::resource('photos', PhotoController::class);
Dependency Injection & Controllers
Laravel's service container is used to resolve all Laravel controllers. As a result, you may
type-hint any dependencies your controller may need in its constructor. The declared
dependencies will automatically be resolved and injected into the controller instance.
<?php
namespace App\Http\Controllers;
use App\Repositories\UserRepository;
class UserController extends Controller
{
/**
* The user repository instance.
*/
protected $users;
/**
* Create a new controller instance.
*/
public function __construct(UserRepository $users)
{
$this->users = $users;
}
}
Note
Controller Middleware
While middleware can be assigned to routes in your route files, you may also specify
middleware within your controller's constructor. This allows you to restrict certain
actions within a single controller to specific middleware. However, in modern
Laravel versions, it is generally preferred to define middleware in the route files
to keep the controller clean.
Warning: Bloated Controllers
A common anti-pattern in MVC development is the "Fat Controller." Controllers should ideally
only be responsible for receiving a request and returning a response. Complex business
logic, external API calls, or complicated database queries should be abstracted into Service
Classes or Action Classes to keep your controllers slim and testable.
HTTP Requests
Laravel’s Illuminate\Http\Request class provides an object-oriented way to
interact with the current HTTP request being handled by your application. Instead of using
PHP superglobals like $_GET, $_POST, and $_FILES,
Laravel injects a Request instance into your controller methods, allowing you to retrieve
input, cookies, files, and even the request's structural metadata (like headers or the URI)
through a consistent and testable API.
Accessing the Request
To obtain an instance of the current HTTP request via dependency injection, you should
type-hint the Illuminate\Http\Request class on your controller method. The
incoming request instance will automatically be injected by the Laravel Service Container.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class UserController extends Controller
{
/**
* Store a new user.
*/
public function store(Request $request)
{
// Retrieve the 'name' input field
$name = $request->input('name');
// Retrieve the 'email' input field with a default value
$email = $request->input('email', 'guest@example.com');
return response()->json(['name' => $name, 'email' => $email]);
}
}
Retrieving Input
Laravel provides several methods to access user input regardless of the HTTP verb used (GET,
POST, PUT, etc.). Whether the data is sent via a query string or as form-encoded data, the
input method will retrieve it.
| Method |
Description |
Use Case |
all() |
Retrieves an array of all input data. |
Bulk operations or debugging. |
input('key') |
Retrieves a specific input value. |
Standard form field access. |
query('key') |
Retrieves values specifically from the query string (?id=1).
|
Filtering or sorting logic. |
only(['a', 'b']) |
Retrieves a subset of the input data. |
Preventing mass-assignment vulnerabilities. |
except(['a']) |
Retrieves all data except specified keys. |
Excluding sensitive fields like passwords. |
has('key') |
Checks if a value is present in the request. |
Conditional logic. |
Input Trimming & Normalization
By default, Laravel includes the TrimStrings and
ConvertEmptyStringsToNull middleware in the global middleware stack. This means
that any string input will automatically have whitespace trimmed from the beginning and end,
and empty strings will be converted to null. This ensures data consistency
before it even reaches your controller logic.
// If a user submits " hello ", $request->input('message') returns:
"hello"
// If a user submits an empty field, $request->input('message') returns:
null
Retrieving Files
You may retrieve uploaded files from a Illuminate\Http\Request instance using
the file method or by using dynamic properties. The file method
returns an instance of the Illuminate\Http\UploadedFile class, which extends
the PHP SplFileInfo class and provides a variety of methods for interacting
with the file.
// Check if a file was uploaded
if ($request->hasFile('photo')) {
$file = $request->file('photo');
// Get the original file name
$name = $file->getClientOriginalName();
// Store the file on the 's3' disk
$path = $file->store('images', 's3');
}
Request Path & Method Inspection
The request instance provides a variety of methods for examining the HTTP request. This is
particularly useful for conditionally executing logic based on the URI or the request
method.
| Method |
Return Example |
Description |
path() |
user/profile |
Returns the request's path info. |
is('admin/*') |
true |
Checks if the path matches a pattern. |
url() |
https://site.com/user/profile |
Returns the URL without the query string. |
fullUrl() |
https://site.com/user/profile?id=1 |
Returns the URL including the query string. |
method() |
POST |
Returns the HTTP verb of the request. |
isMethod('post') |
true |
Validates the HTTP verb. |
Note
PSR-7 Requests
The PSR-7 standard specifies HTTP message interfaces for requests and responses. If
you would like to use a PSR-7 request instead of a Laravel request, you will first
need to install a few libraries. Laravel uses the Symfony HTTP Bridge component to
convert typical Laravel requests and responses into PSR-7 compatible
implementations.
Warning: Input Security
While the all() method is convenient, you should avoid passing its output
directly into Eloquent models (e.g., User::create($request->all())). This
creates a security risk called Mass Assignment, where a malicious user could inject fields
like is_admin=1 into the request. Always use $request->only() or
Form Request Validation to whitelist allowed fields.
HTTP Responses
All routes and controllers in Laravel should return a response to be sent back to the user's
browser. Laravel provides several different ways to return responses. The most basic
response is returning a string from a route or controller; however, most developer actions
will involve returning a full Illuminate\Http\Response instance or a Redirect.
Returning a full Response instance allows you to customize the response's HTTP status code
and headers.
Creating Responses
While strings and arrays are automatically converted into full HTTP responses by
the framework, you may often need to initiate a response manually to define custom status
codes or headers. The response helper can be used to generate these instances. When you
return an array, Laravel automatically converts it to a JSON response and sets the
Content-Type header to application/json.
use Illuminate\Http\Response;
Route::get('/custom-response', function () {
return response('Hello World', 200)
->header('Content-Type', 'text/plain')
->header('X-Header-One', 'Header Value');
});
Response Types
Laravel's response helper provides a variety of methods for generating different
types of response instances, such as JSON, file downloads, and views.
| Response Type |
Method |
Description |
| View |
view('name', $data) |
Returns a rendered Blade template. |
| JSON |
json(['key' => 'value']) |
Automatically sets the JSON content type. |
| Download |
download($pathToFile) |
Forces the browser to download a file. |
| File |
file($pathToFile) |
Displays a file (like an image/PDF) in the browser. |
| Stream |
stream($callback) |
Streams a response for large data sets. |
Example of a JSON Response:
return response()->json([
'name' => 'Abigail',
'state' => 'CA',
], 201);
Redirects
Redirect responses are instances of the Illuminate\Http\RedirectResponse class
and contain the proper headers needed to redirect the user to another URL. You
may use the redirect helper to generate these. Common practices include redirecting to the
previous location, a named route, or a specific controller action.
// Redirecting to a specific URL
return redirect('/home/dashboard');
// Redirecting to a named route
return redirect()->route('login');
// Redirecting back to the previous location (useful for form validation)
return back()->withInput();
Attaching Headers to Responses
Most response methods are chainable, allowing for the fluent construction of response
instances. You may use the header method to add a series of headers to the
response before sending it back to the user. You may also use the withCookie
method to easily attach cookies to your outgoing response.
return response($content)
->header('Content-Type', $type)
->header('X-Header-One', 'Header Value')
->withCookie(cookie('name', 'value', $minutes));
Note
Response Macros
If you would like to define a custom response that you can re-use in a variety of
your routes and controllers, you may use the macro method on the
Response facade. This is typically done within the boot
method of a Service Provider. Once defined, you can call your macro using
response()->yourMacroName().
Warning: Sensitive Data in JSON
When returning Eloquent models or collections directly as JSON, Laravel automatically
serializes them. Ensure you have defined the $hidden property in your Eloquent
models to protect sensitive data like passwords or API tokens from being accidentally
included in the response.
// In your User model
protected $hidden = [
'password',
'remember_token',
];
Views
Views in Laravel serve as the presentation layer of your application, separating your
controller and business logic from your user interface. While controllers handle the logic
of processing a request, views are responsible for the actual HTML output sent to the user's
browser. Laravel views are typically written using Blade, a powerful templating engine that
allows you to write plain HTML enhanced with PHP-like control structures and data binding.
All views are stored in the resources/views directory. When a view is requested,
Laravel compiles the template into plain PHP code and caches it for performance, ensuring
that there is zero overhead when rendering the page for subsequent users.
Creating and Rendering Views
A view file is simply a file with a .blade.php extension. To return a view from
a route or controller, you use the global view helper function. This function
accepts the name of the view file (relative to the resources/views directory)
and an optional array of data to pass to the template.
// resources/views/greeting.blade.php
<html>
<body>
<h1>Hello, {{ $name }}</h1>
</body>
</html>
// In your routes/web.php or Controller
Route::get('/welcome', function () {
return view('greeting', ['name' => 'James']);
});
Nested view directories use "dot" notation to indicate the path. For example, if your view is
stored at resources/views/admin/profile.blade.php, you should return it using
view('admin.profile').
Passing Data to Views
You may pass an array of data to views using the second argument of the view
helper. Once the data is passed, each key in the array becomes a variable inside the view.
Alternatively, you may use the with method to chain data onto the view
instance.
| Method |
Example Syntax |
Best Use Case |
| Array Argument |
view('profile', ['user' => $user])
|
Passing multiple variables at once. |
with() Method |
view('profile')->with('user', $user)
|
Fluent syntax for single variables. |
compact() |
view('profile', compact('user', 'posts'))
|
Passing existing local variables quickly. |
View Composers
View composers are callbacks or class methods that are called when a view is rendered. If you
have data that you want to be bound to a view every time that view is rendered (such as a
list of categories in a sidebar or the authenticated user's profile), a view composer can
help you organize that logic into a single location rather than duplicating it in multiple
controllers.
// app/Providers/ViewServiceProvider.php
use App\View\Composers\ProfileComposer;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
public function boot(): void
{
// Using a class-based composer...
View::composer('profile', ProfileComposer::class);
// Using a closure-based composer...
View::composer('dashboard', function ($view) {
$view->with('count', \App\Models\User::count());
});
}
The Blade Templating Engine
Blade is the powerful templating engine provided by Laravel. Unlike other PHP templating
engines, Blade does not restrict you from using plain PHP code in your templates. In fact,
all Blade templates are compiled into plain PHP code and cached until they are modified.
Displaying Data
You may display data passed to your Blade views by wrapping the variable in curly braces.
Blade {{ }} statements are automatically sent through PHP's
htmlspecialchars function to prevent XSS attacks.
Hello, {{ $name }}.
The current UNIX timestamp is {{ time() }}.
Control Structures
Blade provides convenient shortcuts for common PHP control structures, such as conditional
statements and loops. These shortcuts provide a very clean, terse way of working with PHP
control structures while remaining familiar to PHP developers.
@if (count($records) === 1)
I have one record!
@elseif (count($records) > 1)
I have multiple records!
@else
I don't have any records!
@endif
@foreach ($users as $user)
<p>This is user {{ $user->id }}</p>
@endforeach
Note
Unescaped Data
By default, Blade {{ }} statements are automatically sanitized. If you
need to display a variable without escaping it (for example, if it contains HTML
that you trust), you may use the {!! !!} syntax:
Hello, {!! $name !!}. Be extremely careful when using this with
user-provided content.
Warning: View Caching in Production
In a production environment, you should run the php artisan view:cache command.
This will pre-compile all your Blade templates so that they do not need to be compiled on
demand, significantly improving the speed of every request that returns a view.
Blade Templates (Directives, Components)
Blade is the powerful, feature-rich templating engine provided by Laravel. Unlike other PHP
templating engines, Blade does not restrict you from using plain PHP code in your templates;
in fact, all Blade templates are compiled into plain PHP code and cached until they are
modified. Blade adds zero overhead to your application while providing convenient shortcuts
for common PHP tasks, such as displaying data, conditional logic, and looping. Its two most
powerful features—Directives and Components—allow for a highly modular and readable frontend
architecture.
Blade Directives
Directives are the "syntactic sugar" of Blade. They are prefixed with the @ symbol and
provide a clean, terse way of working with PHP control structures. Because these directives
are compiled into raw PHP, they are extremely performant.
Conditionals and Loops
Blade provides directives that map directly to PHP's native control structures but with a
much cleaner syntax.
| Directive |
PHP Equivalent |
Description |
@if, @else, @endif |
if ($val) { ... } |
Standard conditional logic. |
@unless, @endunless |
if (! $val) { ... } |
Executes if the condition is false. |
@foreach, @endforeach |
foreach ($items as $item) |
The most common loop for collections. |
@forelse, @empty |
foreach + if (empty) |
Loop with a fallback if the collection is empty. |
@auth, @guest |
Auth::check() |
Quick checks for user authentication status. |
Example of @forelse Usage:
<ul>
@forelse ($users as $user)
<li>{{ $user->name }}</li>
@empty
<p>No users found in the system.</p>
@endforelse
</ul>
Blade Components
Components are the modern approach to building reusable UI elements in Laravel. They provide
a similar experience to frontend frameworks like Vue or React, allowing you to wrap HTML and
logic into a single tag. Components can be either class-based (for complex logic) or
anonymous (for simple templates).
Defining and Using Components
You can create a component using the Artisan command:
php artisan make:component Alert. This creates a class in
app/View/Components and a view in resources/views/components.
<div class="alert alert-{{ $type }}">
{{ $slot }}
</div>
<x-alert type="danger">
<strong>Error!</strong> Something went wrong.
</x-alert>
Component Attributes and Slots
Components often need to accept dynamic data. You can pass data to a component via
attributes. If an attribute is prefixed with a colon :, it will be treated as a
PHP expression. The {{ $slot }} variable is used to render whatever content is
placed inside the component tags.
| Feature |
Syntax |
Purpose |
| Simple Attribute |
type="info" |
Passes a hard-coded string. |
| PHP Attribute |
:message="$message" |
Passes a PHP variable or expression. |
| Named Slots |
<x-slot:title> |
Allows multiple injection points in one component. |
| Attributes Bag |
{{ $attributes }} |
Forwards unexpected attributes (like class or id)
to the root element.
|
Layouts via Components
Most web applications maintain the same general layout across various pages. Instead of
repeating the entire HTML structure in every view, you can define a "layout" component.
<html>
<head>
<title>{{ $title ?? 'Default Title' }}</title>
</head>
<body>
<nav>...</nav>
{{ $slot }}
</body>
</html>
<x-layout>
<x-slot:title>Home Page</x-slot:title>
<h1>Welcome to our website!</h1>
</x-layout>
Custom Directives
Laravel allows you to define your own custom Blade directives to simplify repetitive patterns
in your code. This is typically done within the boot method of your
AppServiceProvider.
// app/Providers/AppServiceProvider.php
use Illuminate\Support\Facades\Blade;
public function boot(): void
{
// A directive to format currency
Blade::directive('money', function (string $amount) {
return "<?php echo '$' . number_format($amount, 2); ?>";
});
}
// Usage in Blade:
// @money($product->price)
Note
The $loop Variable
When iterating through a @foreach loop, a $loop variable is
automatically available. This object provides useful information such as
$loop->first, $loop->last, or
$loop->iteration (a 1-based index), allowing you to style the first or
last items of a list differently.
Warning: Directive Compilation
When you change the logic of a custom Blade directive in your Service Provider, the changes
may not appear immediately in your browser. Since Blade views are compiled and cached, you
must clear the view cache using php artisan view:clear to force the framework
to recompile the templates with the new directive logic.
Asset Bundling (Vite)
Asset bundling is the process of taking your application's raw frontend source files—such as
JavaScript, TypeScript, CSS, and images—and processing them into optimized, minified files
suitable for production. In modern Laravel applications, this is handled by Vite, a
next-generation frontend build tool that significantly improves the developer experience.
Vite replaced the older Webpack-based "Laravel Mix" by offering nearly instantaneous Hot
Module Replacement (HMR) and a highly optimized rollup-based build process.
How Vite Works with Laravel
Vite operates differently depending on your environment. During development, Vite serves your
assets via native browser ES modules, meaning it does not need to bundle your code every
time a file changes. It simply serves the modified file directly to the browser. In
production, Vite uses Rollup to bundle your assets into highly optimized, versioned files
that can be cached effectively by the browser.
| Feature |
Development Mode (npm run dev) |
Production Mode (npm run build) |
| Speed |
Instantaneous; no bundling required. |
Optimized for delivery. |
| HMR |
Changes reflect in the browser without a refresh. |
N/A (Static files). |
| Output |
Served from a local dev server (default port 5173). |
Minified files in public/build. |
| Manifest |
N/A. |
A manifest.json maps source files to hashed versions. |
Configuration
The configuration for Vite is stored in a vite.config.js file at the root of
your Laravel project. This file tells Vite which entry points (JavaScript and CSS files) it
should process.
// vite.config.js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
export default defineConfig({
plugins: [
laravel({
input: [
'resources/css/app.css',
'resources/js/app.js'
],
refresh: true, // Enables auto-refresh for Blade templates
}),
],
});
Loading Assets in Blade
To include your Vite-processed assets in your HTML, you use the @vite Blade
directive. This directive is intelligent: in development, it injects the Vite client and
points to the dev server; in production, it reads the manifest.json file to
inject the correct hashed filenames and CSS links.
<head>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
Handling Common Assets
Beyond JavaScript and CSS, Vite can also process images, fonts, and other static assets. When
you reference an asset within your JavaScript or CSS, Vite will automatically include it in
the build process and update the URL.
Images in JavaScript
import logo from '../images/logo.png';
document.getElementById('logo').src = logo;
Images in CSS
.hero {
/* Vite will resolve this path and hash the file */
background-image: url('../images/hero-bg.jpg');
}
Images in Blade
To reference an asset inside a Blade template that has been processed by Vite, you can use
the Vite::asset() method:
<img src="{{ Vite::asset('resources/images/logo.png') }}" alt="Logo">
Note
Hot Module Replacement (HMR)
When running npm run dev, Vite enables HMR. This means when you save a
JavaScript file or a CSS file, only the modified module is updated in the browser.
You don't lose the "state" of your page (like values in an input field), and the
update happens in milliseconds.
Warning: Deployment Requirement
If you deploy your application to a production server without running the build command, your
site will fail to load assets. You must execute npm run build as part of your
CI/CD pipeline. This creates the public/build directory and the
manifest.json file that Laravel's @vite directive requires.
Vite with CSS Frameworks
Laravel is often paired with Tailwind CSS. Because Vite uses PostCSS under the hood,
integrating Tailwind is seamless. You simply need to ensure your
tailwind.config.js is configured and that you import Tailwind in your main CSS
file.
/* resources/css/app.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
URL Generation
Laravel provides several helpers to assist you in generating URLs for your application. While
you could hard-code URLs in your templates and API responses, using Laravel's built-in
generation tools ensures that your links remain valid even if you change your application's
domain or restructure your route URIs. These helpers automatically account for the protocol
(HTTP or HTTPS) and the base URL defined in your .env file.
Basic URL Generation
The most fundamental way to generate a URL is via the url helper. This function
simply appends the path you provide to the application's base URL.
// If APP_URL is http://example.com
$url = url('/posts/1');
// Output: http://example.com/posts/1
If the current request is secure (HTTPS), the generated URL will also use HTTPS. You may also
access the current URL or the previous URL using the following methods:
| Method |
Output Example |
Purpose |
url()->current() |
https://site.com/user |
The URL without the query string. |
url()->full() |
https://site.com/user?id=1 |
The URL including the query string. |
url()->previous() |
https://site.com/dashboard |
The URL of the previous request. |
URLs for Named Routes
The route helper generates URLs for named routes. This is the preferred method
for generating links because it decouples your UI from your URI structure. If you change a
route's path from /user/profile to /account/settings, you only
need to update the route definition; all calls to route('profile') will update
automatically.
// Route Definition
Route::get('/post/{post}', function (Post $post) {
// ...
})->name('post.show');
// URL Generation
$url = route('post.show', ['post' => 1]);
// Output: http://example.com/post/1
If the route requires multiple parameters, you can pass them as an associative array. Any
extra array keys that do not match route parameters will be appended to the URL as a query
string.
URLs for Controller Actions
The action helper generates a URL for a given controller action. You do not need
to provide the full namespace of the controller if it is registered within the standard
App\Http\Controllers namespace.
use App\Http\Controllers\UserController;
$url = action([UserController::class, 'index']);
// With parameters
$url = action([UserController::class, 'show'], ['id' => 1]);
Signed URLs
Laravel allows you to easily create "signed" URLs to named routes. These URLs have a
"signature" hash appended to the query string which allows Laravel to verify that the URL
has not been modified since it was created. This is exceptionally useful for "Unsubscribe"
links or "Email Verification" links that should not be guessable or tamperable.
use Illuminate\Support\Facades\URL;
// Create a standard signed URL
$url = URL::signedRoute('unsubscribe', ['user' => 1]);
// Create a temporary signed URL (expires in 30 minutes)
$url = URL::temporarySignedRoute(
'unsubscribe', now()->addMinutes(30), ['user' => 1]
);
To verify the signature on the incoming request, you should use the signed middleware
provided by Laravel:
Route::get('/unsubscribe/{user}', function (Request $request) {
// ...
})->name('unsubscribe')->middleware('signed');
Note
Asset URL Generation
To generate a URL for an asset (like a static image or a plain CSS file not processed
by Vite), use the asset helper. This will point to the public
directory:
echo asset('img/logo.png'); // http://example.com/img/logo.png
Warning: The APP_URL Constant
When running Laravel from the CLI (e.g., in a scheduled task or a queued job), the
framework cannot "guess" the base URL because there is no incoming web request. In
these cases, Laravel defaults to the APP_URL value defined in your
.env file. Ensure this is set correctly to avoid broken links in sent
emails.
Session Storage
Since HTTP-driven applications are stateless, sessions provide a way to store information
about the user across multiple requests. That user information is typically placed in a
persistent store that can be accessed from subsequent requests. Laravel ships with a variety
of session backends that are accessed through an expressive, unified API. Support for
popular backends such as Memcached, Redis, and databases is included out of the box.
Configuration
Your application's session configuration file is stored at config/session.php.
By default, Laravel is configured to use the file session driver, which works
well for many applications. In production, you may consider using the database
or redis drivers for even faster performance.
| Driver |
Description |
Best Use Case |
file |
Sessions are stored in storage/framework/sessions. |
Local development and small sites. |
cookie |
Sessions are stored in encrypted, signed cookies. |
Small payloads; avoids server-side storage. |
database |
Sessions are stored in a relational database table. |
Scalable apps with existing DB infrastructure. |
memcached /
redis
|
Sessions are stored in fast, cache-based stores. |
High-traffic, multi-server applications. |
array |
Sessions are stored in a PHP array and will not be persisted. |
Testing environments. |
Interacting with the Session
There are two primary ways to work with session data in Laravel: the HTTP Request instance
and the global session helper.
Retrieving Data
You can access session data from a Request instance, which can be type-hinted on
a controller method.
public function show(Request $request, string $id)
{
// Retrieve a piece of data from the session...
$value = $request->session()->get('key');
// Retrieve with a default value...
$value = $request->session()->get('key', 'default');
// Retrieve all data in the session...
$data = $request->session()->all();
}
Storing Data
To store data in the session, you will typically use the request instance's put
method or the global session helper.
// Via the Request instance...
$request->session()->put('key', 'value');
// Via the global helper...
session(['key' => 'value']);
Flash Data
Sometimes you may wish to store items in the session only for the next request. You may do so
using the flash method. Data stored in the session using this method will only
be available during the subsequent HTTP request, and then will be deleted. Flash data is
primarily useful for short-lived status messages.
$request->session()->flash('status', 'Task was successful!');
In your Blade view, you can then check for this status:
@if (session('status'))
<div class="alert alert-success">
{{ session('status') }}
</div>
@endif
Deleting Data
The forget method will remove a piece of data from the session. If you would
like to remove all data from the session, you may use the flush method.
| Method |
Action |
forget('key') |
Removes a specific key. |
pull('key') |
Retrieves and then deletes the key (one-step). |
flush() |
Removes all data from the session. |
invalidate() |
Flushes the session and regenerates the ID (Security best practice). |
Note
Session Blocking
By default, Laravel allows requests using the same session to execute concurrently.
If you have a long-running request that modifies session data, it might "clobber"
data from a faster request. You can use the block method in your route
definition to prevent concurrent session access for specific routes.
Warning: Database Session Setup
When using the database session driver, you must create a table to contain the
session items. Laravel provides an Artisan command to generate the migration for this table:
php artisan session:table. Without this table, the application will throw an
error as soon as it tries to write session data.
Validation (Form Requests, Rules)
Validation is a critical aspect of any web application, ensuring that the data provided by
users meets your business requirements and security standards before it is processed or
stored. Laravel provides several approaches to validate incoming data, ranging from simple
inline validation in controllers to dedicated "Form Request" classes that keep your
controllers lean and readable.
Validation Approaches
Laravel offers three primary ways to handle validation. Choosing the right one depends on the
complexity of the data and the design of your application.
| Method |
Best Use Case |
Pros |
| Inline Validation |
Small, quick projects or single-field updates. |
Quick to implement; logic is right in the controller. |
| Form Requests |
Complex forms, reusable logic, or large applications. |
Extremely clean controllers; reusable across methods. |
| Manual Validator |
AJAX requests or non-HTTP data (e.g., CLI, Queues). |
Full control over the validator instance and redirects. |
Inline Validation
The simplest way to validate a request is to use the validate method available
on the Illuminate\Http\Request object. If the validation rules pass, your code
will continue executing normally; if they fail, an exception is thrown, and the user is
automatically redirected back to their previous location with the error messages and input
data.
public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required|unique:posts|max:255',
'body' => 'required',
'publish_at' => 'nullable|date',
]);
// The current request is valid...
Post::create($validated);
}
Form Request Validation
For more complex validation scenarios, you may wish to create a "form request." Form requests
are custom request classes that encapsulate their own validation and authorization logic.
You can generate one using the Artisan command:
php artisan make:request StorePostRequest.
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StorePostRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return $this->user()->can('create-posts');
}
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'title' => ['required', 'unique:posts', 'max:255'],
'body' => ['required'],
];
}
}
In your controller, you simply type-hint the request:
public function store(StorePostRequest $request)
{
// The request is already validated and authorized here!
$validated = $request->validated();
}
Available Validation Rules
Laravel includes over 90 built-in validation rules. Here are some of the most commonly used:
required: The field must be present and not empty.
email: The field must be formatted as an e-mail address.
unique:table,column: The value must not exist in the specified database
table.
confirmed: The field must have a matching field of
foo_confirmation (common for passwords).
exists:table,column The value must exist in the specified database table.
mimes:jpg,png: The file must match one of the given extensions.
Displaying Validation Errors
When validation fails, Laravel automatically flashes the errors to the session. In your Blade
templates, you can use the @error directive or the $errors
variable to display these messages to the user.
<label for="title">Post Title</label>
<input id="title" type="text" name="title" class="@error('title') is-invalid @enderror">
@error('title')
<div class="alert alert-danger">{{ $message }}</div>
@enderror
Note
Custom Validation Rules
If the built-in rules aren't enough, you can create custom rule objects using
php artisan make:rule Uppercase. This allows you to define complex,
reusable logic in a dedicated class's validate method.
Warning: The validated() Method
When using $request->validate() or Form Requests, always use the
$request->validated() method to retrieve your data instead of
$request->all(). This ensures you are only dealing with data that has actually
been checked against your rules, preventing malicious users from injecting unvalidated
fields into your database.
Error Handling & Logging
In any production application, things will eventually go wrong—a database connection might
time out, an external API might be down, or a user might trigger an edge case. Laravel
provides a robust, built-in error handling and logging system that makes it easy to catch,
report, and debug these issues. By default, Laravel uses the Monolog library, which provides
support for a variety of powerful log handlers.
Error Handling Configuration
In Laravel 11, the primary configuration for exception handling is found in
bootstrap/app.php. This file allows you to customize how the application
handles specific exceptions, ignores certain errors, or renders custom error pages.
| Feature |
Description |
APP_DEBUG |
In .env, set to true for detailed stack traces
(Dev) or
false for generic error pages (Prod).
|
| Custom Rendering |
Allows you to return a specific response (like JSON) when a specific
exception occurs.
|
| Ignoring Exceptions |
Prevents specific exceptions from being logged to the disk to save space.
|
Example of Custom Exception Handling:
// bootstrap/app.php
->withExceptions(function (Exceptions $exceptions) {
$exceptions->render(function (InvalidOrderException $e, Request $request) {
return response()->view('errors.invalid-order', [], 500);
});
})
The Logging System
Laravel’s logging system is "channel" based. A channel defines where your log messages are
sent. You might want to log simple errors to a single file, while critical system failures
are sent to Slack or an external service like Sentry or Bugsnag.
Available Log Channels
Configuration is located in config/logging.php.
| Channel |
Description |
single |
A single laravel.log file in storage/logs. |
daily |
A new log file is created for each day; old logs are pruned automatically.
|
slack |
Sends log messages to a Slack webhook. |
stack |
A "wrapper" that allows you to send a single log message to multiple
channels at once. |
syslog |
Writes to the system's logging facility. |
Writing Log Messages
You may write information to your logs using the Log facade. The logger provides
the eight logging levels defined in the RFC 5424 specification: emergency, alert, critical,
error, warning, notice, info, and debug.
use Illuminate\Support\Facades\Log;
Log::info('User failed to login.', ['id' => $user->id]);
try {
// Dangerous operation...
} catch (Exception $e) {
Log::error('Critical System Failure', [
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
}
HTTP Exceptions (Error Pages)
Some exceptions describe HTTP error codes from the server. For example, this may be a "page
not found" error (404), an "unauthorized error" (401), or even a developer-generated 500
error. To return such a response from anywhere in your application, use the
abort helper:
// Triggers a 404 Not Found
abort(404);
// Triggers a 403 Forbidden with a custom message
abort(403, 'You do not have permission to access this resource.');
Customizing Error Pages
If you want to create a custom error page for a specific HTTP status code, create a view file
in resources/views/errors/. For example, 404.blade.php will be
used for all 404 errors.
Note
Contextual Information
You can define "global" context that is added to every log entry for the duration of
a request. This is useful for tracking a specific user's journey through the logs.
Log::withContext(['request_id' => $id]);
Warning: Sensitive Data in Logs
Never log sensitive information such as user passwords, credit card numbers, or API secrets.
Laravel's default exception handler automatically masks some of these fields, but when
writing manual Log::info() calls, always sanitize the data array you pass.
Getting Started (Connections & Configuration)
Laravel makes interacting with databases extremely simple across a variety of database
backends using either raw SQL, the Fluent Query Builder, or the Eloquent ORM. By default,
Laravel provides first-class support for five primary database systems. Configuration for
your database services is located in the config/database.php file, where you
may define all of your database connections and specify which connection should be used by
default.
Supported Database Systems
Laravel's database layer is built on top of PHP Data Objects (PDO), ensuring compatibility
and security across different environments.
| Database |
PHP Extension Required |
Best Use Case |
| SQLite |
pdo_sqlite |
Local development, testing, and small, low-traffic apps. |
| MySQL |
pdo_mysql |
The industry standard for web applications. |
| MariaDB |
pdo_mysql |
A popular, open-source community drop-in for MySQL. |
| PostgreSQL |
pdo_pgsql |
Advanced features, complex queries, and data integrity. |
| SQL Server |
pdo_sqlsrv |
Enterprise environments using the Microsoft stack. |
Configuration via Environment Variables
While the config/database.php file contains the structural settings, the actual
credentials (host, database name, username, password) should always be stored in your .env
file. This keeps sensitive information out of your version control system (Git).
# .env Example
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=my_app
DB_USERNAME=root
DB_PASSWORD=secret
For SQLite, Laravel 11 simplifies things even further. If you don't provide a database name,
Laravel will look for a database/database.sqlite file. If it doesn't exist, the
framework can even create it for you automatically.
Read & Write Connections
For high-traffic applications, you may wish to use one database server for
SELECT statements and another for INSERT, UPDATE, and
DELETE statements. Laravel makes this easy to configure within your
mysql or pgsql connection array.
// config/database.php
'mysql' => [
'read' => [
'host' => [
'192.168.1.1',
],
],
'write' => [
'host' => [
'192.168.1.2',
],
],
'sticky' => true,
'driver' => 'mysql',
// ...
],
The sticky option is an optional value that can be used to allow the immediate reading of
records that have been written to the database during the current request cycle.
Running Raw SQL Queries
Once you have configured your database connection, you may run queries using the
DB facade. The DB facade provides methods for each type of query:
select, update, insert, delete, and
statement.
| Method |
Returns |
Example |
DB::select() |
An array of results. |
DB::select('select * from users where id = ?', [1]) |
DB::insert() |
bool (success/fail). |
DB::insert('insert into users (name) values (?)', ['Alice'])
|
DB::update() |
int (affected rows). |
DB::update('update users set votes = 100 where name = ?', ['Alice'])
|
DB::delete() |
int (affected rows). |
DB::delete('delete from users') |
Note
Using Multiple Connections
If your application uses multiple database connections, you can access them via the
connection method on the DB facade:
$users = DB::connection('sqlite')->select(...);
Warning: SQL Injection Protection
When running raw queries, never manually concatenate user input into your SQL strings. Always
use "parameter binding" (the ? or :name syntax) as shown in the
examples above. Laravel uses PDO parameter binding to sanitize all input, protecting your
application from SQL injection attacks.
Query Builder
Laravel's database query builder provides a fluent, convenient interface for creating and
running database queries. It uses PDO parameter binding to protect your application against
SQL injection attacks, so there is no need to manually clean strings passed to the query
builder. It can be used to perform most database operations in your application and works
across all supported database systems.
Retrieving Results
The DB facade's table method begins a query. From there, you can
chain constraints onto the query and finally use a "termination" method to retrieve the
results.
| Method |
Result Type |
Description |
get() |
Collection |
Retrieves all rows matching the query. |
first() |
stdClass |
Retrieves a single row (the first match). |
value('col') |
mixed |
Retrieves a single value from the first row. |
find(id) |
stdClass |
Retrieves a single row by its id column. |
pluck('col') |
Collection |
Retrieves a list of values from a single column. |
Example of basic retrieval:
use Illuminate\Support\Facades\DB;
$users = DB::table('users')->where('active', true)->get();
foreach ($users as $user) {
echo $user->name; // Results are objects
}
Where Clauses
The where method is the most basic way to filter your data. It requires three
arguments: the column name, the operator, and the value. If you only provide two arguments,
Laravel assumes the operator is =.
// Standard comparison
$users = DB::table('users')->where('votes', '>', 100)->get();
// Logical "OR"
$users = DB::table('users')
->where('votes', '>', 100)
->orWhere('name', 'John')
->get();
// Sub-queries (Where Between, Where In, Where Null)
$users = DB::table('users')->whereBetween('votes', [1, 100])->get();
$users = DB::table('users')->whereIn('id', [1, 2, 3])->get();
Joins
The query builder may also be used to add join clauses to your queries. For a basic "inner
join," you can use the join method.
$users = DB::table('users')
->join('contacts', 'users.id', '=', 'contacts.user_id')
->join('orders', 'users.id', '=', 'orders.user_id')
->select('users.*', 'contacts.phone', 'orders.price')
->get();
Inserts, Updates, and Deletes
The query builder also provides methods for modifying data.
| Operation |
Method |
Example |
| Insert |
insert() |
DB::table('users')->insert(['email' => 'kayla@example.com', 'votes' => 0]);
|
| Update |
update() |
DB::table('users')->where('id', 1)->update(['votes' => 1]);
|
| Increment |
increment() |
DB::table('users')->increment('votes', 5);
|
| Delete |
delete() |
DB::table('users')->where('votes', '<', 50)->delete();
|
| Truncate |
truncate() |
DB::table('users')->truncate(); // Clears all rows and resets IDs
|
Aggregates and Pagination
The query builder also provides a variety of methods for retrieving aggregate values like
count, max, min, avg, and
sum. Additionally, one of the most loved features is the built-in pagination.
// Aggregates
$price = DB::table('orders')->max('price');
// Pagination
$users = DB::table('users')->paginate(15);
// In Blade: {{ $users->links() }}
Note
Database Transactions
You may use the transaction method on the DB facade to run a set of
operations within a database transaction. If an exception is thrown within the
closure, the transaction will automatically be rolled back.
DB::transaction(function () {
DB::table('users')->update(['votes' => 1]);
DB::table('posts')->delete();
});
Warning: Object vs. Model
Unlike Eloquent ORM, the Query Builder returns results as generic PHP stdClass
objects. This means you cannot call model methods (like relationships or custom attributes)
on the results. If you need that functionality, use Eloquent models instead.
Pagination
In many web applications, showing every database record on a single page is impractical due
to performance and user experience concerns. Laravel's paginator is integrated with the
Query Builder and Eloquent ORM out of the box, providing a convenient, automated way to
split large datasets into manageable chunks.
Basic Usage
The most common way to paginate data is using the paginate method. This method
automatically handles setting the proper limit and offset based on the page
query string parameter in the user's request.
With Query Builder
use Illuminate\Support\Facades\DB;
$users = DB::table('users')->paginate(15);
With Eloquent
use App\Models\User;
$users = User::where('active', 1)->paginate(15);
| Method |
Description |
Best Use Case |
paginate(n) |
Shows links for all pages; performs a COUNT query.
|
Standard dashboards or search results. |
simplePaginate(n) |
Shows only "Next" and "Previous" links; faster performance. |
Infinite scrolls or very large datasets. |
cursorPaginate(n) |
Uses "pointers" instead of offsets; most performant. |
High-frequency data (social media feeds). |
Displaying Pagination Results
When you call paginate, you receive an instance of
Illuminate\Pagination\LengthAwarePaginator. When you call
simplePaginate, you
receive Illuminate\Pagination\Paginator. To render the navigation links in your
Blade view,
simply call the links method:
<div class="container">
@foreach ($users as $user)
{{ $user->name }}
@endforeach
</div>
{{ $users->links() }}
Customizing Pagination
Laravel allows you to customize the URI, query string, and even the "fragment" (anchor) used
by the paginator.
- Appending to Links: If you have search filters, you must append them to the pagination
links so they persist when the user changes pages.
- On Each Side: Control how many links are displayed on each side of the current page
link.
// Appending search filters
$users = User::paginate(15)->withQueryString();
// Manually appending specific parameters
$users = User::paginate(15)->appends(['sort' => 'votes']);
// Adjusting link count
$users = User::paginate(15)->onEachSide(1);
Paginator Instance Methods
The paginator instance provides additional information about the state of the data through
various methods:
| Method |
Description |
$users->count() |
Number of items on the current page. |
$users->currentPage() |
The current page number. |
$users->hasMorePages() |
Boolean: Are there more pages after this? |
$users->lastPage() |
The total number of pages
(Not available in
simplePaginate).
|
$users->total() |
Total number of items in the database
(Not available in
simplePaginate).
|
Note
Tailwind & Bootstrap
By default, Laravel uses Tailwind CSS for its pagination links. If you are using
Bootstrap, you should inform Laravel in the boot method of your
App\Providers\AppServiceProvider:
use Illuminate\Pagination\Paginator;
public function boot(): void {
Paginator::useBootstrapFive();
}
Warning: Performance and COUNT(*)
The standard paginate() method executes a COUNT(*) query to
determine how many total pages exist. On tables with millions of rows, this can be slow. In
these cases, prefer simplePaginate() or cursorPaginate(), which do
not need to count the entire table.
Migrations
Migrations are like version control for your database, allowing your team to modify and share
the application's database schema definition. Instead of manually creating tables and
columns in a database management tool, you define your schema in PHP files. This ensures
that every developer on your team and your production environment are running the exact same
database structure.
Migrations also allow you to "roll back" changes to a previous state, making it much safer to
experiment with schema changes during development.
Creating and Running Migrations
You can generate a migration using the make:migration Artisan command. Laravel
uses the name you provide to guess if you are creating a new table or modifying an existing
one.
| Action |
Command |
Result |
| Create Table |
php artisan make:migration create_flights_table |
Creates a new migration with a Schema::create block.
|
| Update Table |
php artisan make:migration add_votes_to_users_table |
Creates a migration with a Schema::table block.
|
| Run Migrations |
php artisan migrate |
Executes all outstanding migrations. |
| Rollback |
php artisan migrate:rollback |
Reverts the last "batch" of migrations. |
Migration Structure
A migration class contains two methods: up and down. The up method is used to add new tables,
columns, or indexes to your database, while the down method should reverse the operations
performed by the up method.
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('flights', function (Blueprint $blueprint) {
$blueprint->id(); // Alias for bigIncremental ID
$blueprint->string('name');
$blueprint->string('airline')->nullable();
$blueprint->integer('passengers')->default(0);
$blueprint->timestamps(); // Adds created_at and updated_at
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('flights');
}
};
Available Column Types
The schema builder contains a variety of column types that you may use when building your
tables. Laravel maps these PHP methods to the appropriate SQL data types for your specific
database driver (MySQL, PostgreSQL, etc.).
| Method |
Database Type |
Use Case |
$table->string('name') |
VARCHAR |
Short text (names, titles). |
$table->text('bio') |
TEXT |
Long-form content. |
$table->integer('votes') |
INTEGER |
Standard numbers. |
$table->boolean('is_active') |
BOOLEAN/TINYINT |
True/False flags. |
$table->decimal('price', 8, 2) |
DECIMAL |
Precise currency values. |
$table->foreignId('user_id') |
BIGINT |
Foreign keys (unsigned). |
Modifying and Deleting Columns
To modify an existing column (e.g., changing its length or making it nullable), you should
use the change method. To drop a column, use the dropColumn
method.
Note
Before modifying columns, you may need to install the doctrine/dbal
library via Composer, though modern Laravel versions (10+) support most
modifications natively in MySQL, PostgreSQL, and SQL Server.
// Modifying a column
Schema::table('users', function (Blueprint $table) {
$table->string('name', 50)->nullable()->change();
});
// Dropping a column
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('votes');
});
Foreign Key Constraints
Laravel also provides support for creating foreign key constraints, which are used to force
data integrity at the database level.
Schema::table('posts', function (Blueprint $table) {
// Shorthand for an unsigned big integer user_id
// referencing the id on the users table
$table->foreignId('user_id')
->constrained()
->onUpdate('cascade')
->onDelete('cascade');
});
Note
Squash Your Migrations
As your application grows, you may accumulate hundreds of migration files. You can
"squash" these into a single SQL file using php artisan schema:dump.
This keeps your migrations directory clean and speeds up your testing
suite.
Warning: Data Loss on Refresh
The php artisan migrate:refresh and
php artisan migrate:fresh commands will drop all tables and re-run all
migrations. Use these with extreme caution; they should essentially never be run on
a production server, as they will delete all of your data.
Seeding
Database seeding is the process of populating your database with test data. While Migrations
define the structure of your database, Seeders provide the initial data needed for
development, testing, or production defaults (like administrative accounts or country
lists). Laravel includes a simple method of seeding your database with test data using seed
classes, which are stored in the database/seeders directory.
Creating and Running Seeders
By default, Laravel provides a DatabaseSeeder class. You can create additional
seeders using the make:seeder Artisan command. All seeder classes are executed
using the php artisan db:seed command.
| Action |
Command |
Result |
| Create Seeder |
php artisan make:seeder UserSeeder
|
Creates a new class in database/seeders.
|
| Run All Seeders |
php artisan db:seed
|
Executes the run method in DatabaseSeeder.
|
| Run Specific Seeder |
php artisan db:seed --class=UserSeeder
|
Executes only the specified seeder class.
|
| Fresh Seed |
php artisan migrate:fresh --seed
|
Drops all tables, re-migrates, and then seeds.
|
Writing Seeders
A seeder class contains one default method: run. This method is called when the
db:seed Artisan command is executed. Within the run method, you may insert data
into your database using the Query Builder or Eloquent models.
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
class UserSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
DB::table('users')->insert([
'name' => 'Administrator',
'email' => 'admin@example.com',
'password' => Hash::make('password'),
]);
}
}
Using Model Factories
Manually specifying the attributes for each database seed is cumbersome. Instead, you can use
Model Factories to generate large amounts of database records. Factories allow you to define
a "blueprint" for your models and then generate as many instances as you need with fake
data.
// database/seeders/DatabaseSeeder.php
public function run(): void
{
// Generate 10 random users using the User Factory
\App\Models\User::factory(10)->create();
// Generate a user with a specific relationship
\App\Models\User::factory(5)
->hasPosts(3) // Creates 3 posts for each of the 5 users
->create();
}
Calling Additional Seeders
Within the DatabaseSeeder class, you may use the call method to
execute additional seed classes. Using the call method allows you to break up
your database seeding into multiple files so that no single seeder class becomes too large.
public function run(): void
{
$this->call([
UserSeeder::class,
PostSeeder::class,
CommentSeeder::class,
]);
}
Database Seeding Summary
| Feature |
Description |
Best Practice |
| Faker Library |
Generates fake names, emails, and text. |
Use inside Factories for realistic test data. |
| Environment Check |
Prevent seeding in production. |
Use if (app()->environment('local')) in your seeder. |
| Silent Seeding |
Run seeds without output. |
Use the --silent flag in the CLI. |
Note
The run Method
You are not limited to database operations inside the run method. You
can use it to create filesystem directories, fetch data from external APIs to
populate your database, or even run other Artisan commands.
Warning: Mass Assignment
When using Eloquent models in your seeders, remember that Mass Assignment protections still
apply. Ensure that the fields you are seeding are either in the $fillable array
of your model or use the unguard method to temporarily disable protection
during the seed process.
Redis Integration
Redis is an open-source, advanced key-value store. It is often referred to as a data
structure server since keys can contain strings, hashes, lists, sets, and sorted sets. In
Laravel, Redis is primarily used for caching, session storage, and as a high-performance
queue worker.
Laravel interacts with Redis through the phpredis PHP extension or the
predis/predis package.
Configuration
The Redis configuration for your application is located in the
config/database.php file. Within this file, you will see a redis
array containing the connections used by your application.
| Setting |
Default Value |
Description |
client |
phpredis |
The driver used to communicate with Redis. |
host |
127.0.0.1 |
The IP address of your Redis server. |
port |
6379 |
The port Redis is listening on. |
database |
0 |
The specific Redis logical database index. |
To configure these values, update your .env file:
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
Interacting with Redis
You may interact with Redis by calling various methods on the Redis facade. The
Redis facade supports dynamic methods, meaning you may call any Redis command
directly, and it will be passed to the server.
use Illuminate\Support\Facades\Redis;
// Simple Key-Value storage
Redis::set('name', 'Taylor');
$name = Redis::get('name');
// Working with Lists
Redis::lpush('frameworks', 'Laravel');
Redis::lpush('frameworks', 'Symfony');
// Executing complex commands
$values = Redis::lrange('frameworks', 0, -1);
Primary Use Cases in Laravel
While you can use Redis for general data storage, Laravel integrates it deeply into three
core systems for maximum performance.
| System |
Benefit of Redis |
| Cache |
Extremely fast retrieval for frequently accessed data compared to the
file driver.
|
| Session |
Ideal for "sticky" sessions in load-balanced environments with multiple web
servers. |
| Queue |
Low latency and high throughput for processing background jobs. |
| Pub/Sub |
Allows for real-time communication, such as broadcasting events to
Laravel Echo.
|
Pipelining and Transactions
To reduce network latency, you can "pipeline" multiple commands to be sent to the server in
one go. Alternatively, you can use transactions to ensure a block of commands is executed
atomically.
Pipelining
Redis::pipeline(function ($pipe) {
for ($i = 0; $i < 1000; $i++) {
$pipe->set("key:$i", $i);
}
});
Transactions (Multi/Exec)
Redis::transaction(function ($tx) {
$tx->set('user:1:votes', 1);
$tx->incr('user:1:votes');
});
Note
Connection Clusters
If your application uses a Redis cluster, you should define these connections within
the clusters option in your Redis configuration. This allows Laravel to
shard data across multiple Redis nodes automatically.
Warning: Redis Memory Limits
Redis stores all data in RAM. If your Redis server reaches its maximum memory limit, it may
start evicting keys (deleting data) based on its maxmemory-policy. Always
monitor your Redis memory usage, especially when using it for both Cache and Sessions
simultaneously.
Eloquent Getting Started
The Eloquent ORM (Object-Relational Mapper) is Laravel's powerful implementation of the
"Active Record" pattern. It allows you to interact with your database using PHP syntax
instead of writing raw SQL. Each database table has a corresponding "Model" that is used to
interact with that table.
When using Eloquent, every row in your database table is represented as an instance of your
Model class, making data manipulation intuitive and object-oriented.
Defining Models
By default, Eloquent models live in the app/Models directory. You can create a
new model using the make:model Artisan command. Typically, you will want to
create a migration at the same time to define the table structure.
| Command |
Result |
php artisan make:model Post |
Creates only the Model file. |
php artisan make:model Post -m |
Creates the Model and a database migration. |
php artisan make:model Post -a |
Creates Model, Migration, Factory, Seeder, and Controller
(Recommended).
|
Basic Model Example:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
// By default, this model maps to the "flights" table
}
Model Conventions
Eloquent makes several assumptions about your database structure to reduce configuration.
However, if your database does not follow these conventions, you can manually override them
within the model.
| Feature |
Convention |
Manual Override Example |
| Table Name |
Lowercase, plural (e.g., flights). |
protected $table = 'my_flights'; |
| Primary Key |
An integer named id. |
protected $primaryKey = 'flight_id'; |
| Timestamps |
Requires created_at and updated_at. |
public $timestamps = false; |
| Connection |
The default database connection. |
protected $connection = 'sqlite'; |
Retrieving Models
Once you have a model and its associated table, you can start retrieving data. Eloquent
models are also Query Builders, so you can use all the methods available in the Fluent Query
Builder.
use App\Models\Flight;
// Retrieve all records
$flights = Flight::all();
// Filtered retrieval
$flights = Flight::where('active', 1)
->orderBy('name')
->take(10)
->get();
// Retrieve by primary key
$flight = Flight::find(1);
// Throw an exception if not found (useful for APIs/Controllers)
$flight = Flight::findOrFail(1);
Inserts & Updates
To create a new record, simply create a new model instance, set its attributes, and call the
save method. To update, retrieve the model first, change the attributes, and save again.
Basic Insert
$flight = new Flight;
$flight->name = 'Tokyo 747';
$flight->save();
Basic Update
$flight = Flight::find(1);
$flight->name = 'London 808';
$flight->save();
Mass Assignment
Mass assignment occurs when you pass an array of data to a model method, such as
create or update. For security reasons, Eloquent requires you to
define which attributes are "fillable" to prevent users from injecting unexpected data (like
is_admin = true).
class Flight extends Model
{
/**
* The attributes that are mass assignable.
*/
protected $fillable = ['name', 'airline'];
}
// Now you can use the create method:
Flight::create(['name' => 'Paris 101', 'airline' => 'Air France']);
Note
When you retrieve multiple records via Eloquent (using all() or
get()), they are returned as an Eloquent Collection. This is a powerful
wrapper around PHP arrays that allows you to map, filter, or reduce your data easily
without hitting the database again.
Warning: Database Logic in Models
While it's tempting to put all your logic in the Model, try to keep them focused on data
access. For complex business logic involving multiple models, consider using Service Classes
to keep your Eloquent models from becoming "God Objects."
Relationships (One-to-One, One-to-Many, Many-to-Ma
Database tables are often related to one another. For example, a blog post may have many
comments, or an order could be linked to the user who placed it. Eloquent makes managing and
working with these relationships easy, supporting a variety of common relationship types.
Relationships are defined as methods on your Eloquent model classes. Since relationships also
serve as powerful query builders, defining them as methods provides powerful method chaining
and querying capabilities.
One-to-One
A one-to-one relationship is a very basic type of database relationship. For example, a
User model might be associated with one Phone model.
| Direction |
Method |
Description |
| Parent to Child |
hasOne() |
Defined on the model that does not hold the foreign key. |
| Child to Parent |
belongsTo() |
Defined on the model that holds the foreign key
(user_id). |
// User.php
public function phone() {
return $this->hasOne(Phone::class);
}
// Accessing it:
$phone = User::find(1)->phone;
One-to-Many
A one-to-many relationship is used to define relationships where a single model is the parent
to one or more child models. For example, a blog Post may have an infinite
number of Comments.
| Direction |
Method |
Example |
| One (Parent) |
hasMany() |
return $this->hasMany(Comment::class); |
| Many (Child) |
belongsTo() |
return $this->belongsTo(Post::class); |
// Post.php
public function comments() {
return $this->hasMany(Comment::class);
}
// Accessing it (returns a Collection):
$comments = Post::find(1)->comments;
Many-to-Many
Many-to-many relations are slightly more complicated than hasOne and
hasMany. An example is a User with many Roles, where the roles are also shared
by other users. This requires a pivot table (e.g., role_user).
// User.php
public function roles() {
return $this->belongsToMany(Role::class);
}
// Attaching a role to a user:
$user->roles()->attach($roleId);
// Accessing pivot data:
foreach ($user->roles as $role) {
echo $role->pivot->created_at;
}
Note
Touching Timestamps
When a child model is updated (like a Comment), you may want to update
the updated_at timestamp of the parent (Post). You can do this by
adding the relationship name to a $touches property on the child model.
Warning: Relationship Method vs Property
If you call $user->posts, you get a Collection of posts. If you call
$user->posts(), you get a Query Builder instance, allowing you to filter further:
$user->posts()->where('active', 1)->get();.
Collections
All multi-result sets returned by Eloquent are instances of the
Illuminate\Database\Eloquent\Collection object. Eloquent collections extend the
base Laravel Support Collection, which means they inherit dozens of powerful methods used to
fluently wrap and manipulate arrays of data.
Unlike standard PHP arrays, collections allow you to chain operations like filtering,
mapping, and reducing in a readable, functional style without needing manual
foreach loops.
Common Collection Methods
Laravel collections offer over 100 methods. Below are the most frequently used operations for
handling database results.
| Method |
Description |
Example |
filter() |
Filters the collection using a callback. |
$users->filter(fn($u) => $u->active); |
map() |
Iterates and modifies each item. |
$users->map(fn($u) => strtoupper($u->name)); |
pluck() |
Retrieves all values for a given key. |
$users->pluck('email'); // Array of emails |
contains() |
Checks if a specific item exists. |
$users->contains('role', 'admin'); |
first() |
Returns the first item passing a truth test. |
$users->first(fn($u) => $u->points > 100); |
each() |
Iterates over items (useful for actions). |
$users->each(fn($u) => $u->sendEmail()); |
sortBy() |
Sorts the collection by a given key. |
$users->sortByDesc('created_at'); |
Eloquent vs. Support Collections
While Eloquent collections inherit from the base Support collection, they include specialized
methods specifically for database models.
load(): Eager loads relationships for all models in the collection after
they have already been retrieved.
find($id): Returns the model that has a primary key matching the given ID.
modelKeys(): Returns an array of all primary keys for the models in the
collection.
toQuery(): Converts the collection back into a Query Builder instance,
allowing you to perform database-level updates on the specific set of models.
Higher Order Messaging
Collections provide "shortcuts" for common actions. Instead of writing a full closure, you
can call methods directly on the collection instance as if they were properties.
// Instead of this:
$users->each(function ($user) {
$user->archive();
});
// Use Higher Order Messaging:
$users->each->archive();
This also works with filtering:
// Returns only the users where $user->is_active is true
$activeUsers = $users->filter->is_active;
Lazy Collections
When dealing with tens of thousands of Eloquent records, a standard collection might exhaust
your PHP memory limit because it loads all records into RAM at once. Lazy Collections use
PHP generators to keep memory usage low by loading only one model at a time as you iterate
through them.
use App\Models\User;
// Only one user is kept in memory at a time
foreach (User::cursor() as $user) {
// Process user...
}
Note
Most collection methods return a new collection instance. They do not modify the
original collection. This allows you to "chain" multiple transformations together
while keeping the original data intact if needed.
Warning: Collection vs. Database Query
Be careful not to perform "Collection Filtering" when you should be doing "Database
Filtering."
- Bad:
User::all()->where('active', 1) (Loads all users into memory, then
filters).
- Good:
User::where('active', 1)->get() (Filters at the database level,
loading only what is needed).
Mutators & Casting
Mutators, accessors, and attribute casting allow you to transform Eloquent attribute values
when you retrieve or set them on model instances. This is essential for ensuring data
consistency—such as hashing passwords automatically, formatting dates, or converting JSON
strings into PHP arrays.
Accessors & Mutators
In modern Laravel, accessors and mutators are defined by creating a single method on your
model that returns an Illuminate\Database\Eloquent\Casts\Attribute instance.
| Feature |
Method |
Purpose |
| Accessor |
get: |
Formats a value when it is retrieved from the database.
|
| Mutator |
set: |
Formats a value before it is stored in the database. |
Example: Formatting a User's Name
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Get the user's first name.
*/
protected function firstName(): Attribute
{
return Attribute::make(
get: fn (string $value) => ucfirst($value), // Accessor
set: fn (string $value) => strtolower($value), // Mutator
);
}
}
Attribute Casting
Attribute casting provides functionality similar to accessors and mutators without requiring
you to define extra methods. Instead, you define a casts method (or property)
that maps a column to a specific data type.
| Cast Type |
Description |
integer / boolean |
Converts the database value to a primitive PHP type. |
decimal:<digits> |
Converts to a string with fixed decimal places (safe for money). |
datetime / date |
Converts the value to a Carbon instance. |
json / array |
Automatically json_encode on save and json_decode
on retrieval. |
encrypted |
Encrypts the value in the database using Laravel's encrypter. |
hashed |
Automatically hashes the value (perfect for passwords). |
Example Usage:
protected function casts(): array
{
return [
'is_admin' => 'boolean',
'options' => 'array',
'published_at' => 'datetime',
'password' => 'hashed',
];
}
Custom Casts
If Laravel’s built-in casts don't meet your needs, you can define your own cast class. This
is useful for complex value objects, such as a Address object or a specific
Currency class.
// Creating a custom cast
php artisan make:cast AddressCast
// Applying it in the Model
protected function casts(): array
{
return [
'address' => AddressCast::class,
];
}
Date Casting & Serialization
By default, Eloquent will cast the created_at and updated_at
columns to Carbon instances. You can customize the format of these dates globally or
per-attribute. When a model is converted to JSON (e.g., in an API response), the dates will
be serialized to an ISO-8601 string.
protected function casts(): array
{
return [
'birthday' => 'date:Y-m-d', // Serialize specifically as 1990-01-01
];
}
Note
Appending Values
Sometimes you may want to add attributes that do not have a corresponding column in
your database. You can do this by defining an Accessor and then adding the attribute
name to the $appends property on your model. This ensures the value is
included when the model is converted to an array or JSON.
Warning: Hashing Passwords
Never use a simple accessor to "check" a password. Always use the hashed cast
for password fields. This ensures that even if you forget to manually call
Hash::make() in your controller, the password will be safely encrypted before
hitting the database.
API Resources
When building an API, you often need a transformation layer that sits between your Eloquent
models and the JSON responses served to your application's users. Laravel's API Resources
allow you to expressively and easily transform your models and model collections into JSON,
ensuring your API output remains consistent even if your database schema changes.
Creating Resources
You can generate a resource class using the make:resource Artisan command.
Typically, resources are placed in the app/Http/Resources directory.
| Command |
Purpose |
php artisan make:resource UserResource
|
Creates a resource for a single model.
|
php artisan make:resource UserCollection
|
Creates a resource for a collection of models (wraps
pagination/metadata).
|
Data Transformation
Each resource class contains a toArray method which returns the array of
attributes that should be converted to JSON when the resource is returned from a route or
controller.
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'created_at' => $this->created_at->format('Y-m-d'),
// Conditional attributes
'is_admin' => $this->when($request->user()?->isAdmin(), true),
];
}
}
Handling Relationships
One of the most powerful features of API Resources is the ability to include relationships
only if they have already been loaded by the model. This prevents the "N+1" query problem
and ensures you don't accidentally leak data that wasn't requested.
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'title' => $this->title,
// Only include posts if they are eager-loaded
'posts' => PostResource::collection($this->whenLoaded('posts')),
];
}
Resource Collections & Pagination
When returning a collection of resources or a paginated response, Laravel automatically wraps
the data in a data key and includes relevant pagination information (like meta
and links).
| Method |
Usage |
Result |
new UserResource($user) |
Single Model |
{ "data": { ... } } |
UserResource::collection($users) |
List/Collection |
{ "data": [ {...}, {...} ] } |
$users->paginate(10) |
Paginated List |
Adds links and meta keys to the JSON. |
Top-Level Metadata
Sometimes you need to include extra information in your API response that isn't directly tied
to the model, such as an API version or a success message.
// In the Controller
return (new UserResource($user))
->additional(['meta' => [
'version' => '1.0.0',
]]);
Note
The $this Variable
Inside the toArray method of a resource, you can access the model's
properties directly using $this. This is because a Resource class
proxies property and method access down to the underlying model.
Warning: Avoiding toArray() on Models
While Eloquent models have a built-in toArray() method, using API Resources is
highly recommended for production APIs. This prevents "Internal Schema Leakage"—where
changing a database column name accidentally breaks your public API for third-party
consumers.
Serialization (JSON & Arrays)
When building APIs or JavaScript-heavy frontends, you often need to convert your Eloquent
models and collections into arrays or JSON. Serialization is the process of transforming a
complex data structure (like an Eloquent Model object) into a format that can be easily
stored or transmitted (like a JSON string).
Converting Models to Arrays & JSON
Eloquent provides two primary methods for basic serialization. These methods are recursive,
meaning all nested relationships (that have been eager loaded) will also be serialized.
| Method |
Output |
Use Case |
toArray() |
PHP array |
Passing data to a Blade view or internal processing. |
toJson() |
JSON string |
Manual API responses or storing data in a text field. |
Example:
$user = User::with('posts')->first();
// Convert to array
$array = $user->toArray();
// Convert to JSON
$json = $user->toJson();
// Or simply cast to string
$jsonString = (string) $user;
Hiding Attributes from JSON
You often have sensitive data (like passwords, API tokens, or internal IDs) that should never
be included in your serialized output. You can define these using the $hidden
property on your model. Conversely, you can use $visible to act as an
"allow-list."
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Attributes that should be hidden for serialization.
*/
protected $hidden = ['password', 'remember_token'];
/**
* Attributes that should be visible (ignores everything else).
* protected $visible = ['first_name', 'last_name'];
*/
}
Appending Values to JSON
Sometimes you need to include an attribute that doesn't exist as a column in your database
but is calculated by an Accessor. To include these in your JSON output, add the attribute
name to the $appends property.
class User extends Model
{
protected $appends = ['is_admin'];
protected function isAdmin(): Attribute
{
return Attribute::make(
get: fn () => $this->type === 'admin',
);
}
}
Date Serialization
Laravel serializes dates into an ISO-8601 string format
(2026-02-26T20:19:38.000000Z) by default. You can customize this format per
model by overriding the serializeDate method.
/**
* Prepare a date for array / JSON serialization.
*/
protected function serializeDate(\DateTimeInterface $date): string
{
return $date->format('Y-m-d H:i:s');
}
Dynamic Hiding/Showing
On specific occasions, you might want to hide or show attributes "on the fly" for a single
request without changing the global model configuration.
| Method |
Effect |
makeVisible('attr') |
Temporarily shows a hidden attribute. |
makeHidden('attr') |
Temporarily hides a visible attribute. |
setVisible(['a', 'b']) |
Forces only these specific attributes to show. |
return $user->makeVisible('email_verified_at')->toArray();
Note
Automatic Serialization
When you return an Eloquent model or collection directly from a Route or Controller,
Laravel automatically calls toJson() and sets the
Content-Type header to application/json. You don't need to
manually encode your response.
Warning: Circular References
Be careful when eager loading reciprocal relationships (e.g., a User has many Posts, and a
Post belongs to a User). If you serialize both directions simultaneously without hiding one
of the foreign keys, you may create an infinite loop that crashes your PHP process.
Eloquent Factories
When testing your application or seeding your database, you often need to generate fake data
that looks and behaves like real records. Eloquent Factories provide a convenient way to
define a "blueprint" for your models. Instead of manually specifying every column, you can
use factories to generate hundreds of realistic records with a single line of code.Laravel
uses the Faker PHP library to generate random names, emails, addresses, and more.
Creating and Defining Factories
Factories are stored in the database/factories directory. You can generate a
factory for a specific model using the make:factory Artisan command.
| Command |
Purpose |
php artisan make:factory PostFactory
|
Creates a factory for the Post model. |
php artisan make:model Post -f
|
Creates a Model and its Factory simultaneously. |
php artisan make:model Post -a
|
Creates Model, Migration, Factory, Seeder, and Controller. |
Example Factory Definition:
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
class UserFactory extends Factory
{
public function definition(): array
{
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => static::$password ??= Hash::make('password'),
'remember_token' => Str::random(10),
];
}
}
Using Factories
Once a factory is defined, you can use the static factory() method provided by
the HasFactory trait on your Eloquent models.
Creation Methods
| Method |
Action |
Result |
make() |
Creates instances in memory. |
Returns a Model object (not saved to DB). |
create() |
Creates and persists to DB. |
Returns a Model object (saved to DB). |
count(n) |
Specifies the number of items. |
Returns a Collection of models. |
Example Usage:
// Create a single user in the database
$user = User::factory()->create();
// Create 50 users with a specific attribute override
$users = User::factory()->count(50)->create([
'admin' => true,
]);
Factory States
States allow you to define discrete variations of your model. For example, a User might be an
"admin" or "suspended." Instead of manually passing these attributes every time, you can
define a state method.
// UserFactory.php
public function suspended(): static
{
return $this->state(fn (array $attributes) => [
'account_status' => 'suspended',
]);
}
// Usage
$suspendedUser = User::factory()->suspended()->create();
Factory Relationships
Factories make it easy to generate related models. You can define relationships using methods
like has() or for() .
// Create a user with 3 related posts
$user = User::factory()
->has(Post::factory()->count(3))
->create();
// Create a post that "belongs to" a specific user
$post = Post::factory()
->for(User::factory()->state(['name' => 'John Doe']))
->create();
Sequences
If you want to toggle a value for each created model (e.g., alternating between "Male" and
"Female" or "Active" and "Inactive"), you can use Sequences.
use Illuminate\Database\Eloquent\Factories\Sequence;
$users = User::factory()
->count(10)
->state(new Sequence(
['role' => 'admin'],
['role' => 'editor'],
))
->create();
Note
The fake() Helper
In Laravel 11, the fake() helper is the preferred way to access the
Faker library. It is localized based on your app.faker_locale setting
in config/app.php (e.g., en_US, fr_FR).
Warning: Database Speed in Tests
While create() is convenient, it hits the database. In unit tests where you only
need to test model logic (and not database persistence), use make(). It is
significantly faster because it avoids the overhead of SQL INSERT statements.
Authentication (Guards & Providers)
Laravel’s authentication system is designed to be "pluggable." At its core, the system is
comprised of two main components: Guards and Providers. These components work together to
define how users are authenticated and where their data is retrieved from for every request.
The Authentication Architecture
Understanding the relationship between Guards and Providers is essential for customizing how
users log into your application (e.g., using a traditional session vs. an API token).
| Component |
Responsibility |
Description |
| Guard |
"How" |
Defines how users are authenticated for each request (e.g.,
session guard for web,
token guard for APIs).
|
| Provider |
"Where" |
Defines how users are retrieved from your persistent storage (e.g.,
eloquent provider for DB models,
database provider for raw
tables).
|
Configuration
Authentication configuration is found in config/auth.php. By default, Laravel is
configured with a web guard and an eloquent user provider.
// config/auth.php
'defaults' => [
'guard' => 'web',
'passwords' => 'users',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
'hash' => false,
],
],
Authenticating Users
While Laravel offers starter kits (like Breeze or Jetstream) to handle this automatically,
you can interact with the authentication system manually using the Auth facade.
Attempting Authentication
The attempt method is typically used to handle authentication attempts from your
application's "login" form.
use Illuminate\Support\Facades\Auth;
if (Auth::attempt(['email' => $email, 'password' => $password], $remember)) {
// Authentication was successful...
return redirect()->intended('dashboard');
}
Checking if a User is Authenticated
To determine if the user is already logged into your application, you may use the check
method:
if (Auth::check()) {
// The user is logged in...
}
Accessing the Authenticated User
Once a user is authenticated, you can access the user model instance via the
Auth facade or the Request instance.
| Method |
Syntax |
| Via Facade |
Auth::user() |
| Via Request |
$request->user() |
| Get User ID |
Auth::id() |
Protecting Routes
The auth middleware is used to allow only authenticated users to access a given
route. If the user is not authenticated, they will be redirected to the login
route.
// Protecting a single route
Route::get('/profile', function () {
// Only authenticated users may access...
})->middleware('auth');
// Specifying a Guard
Route::get('/api/user', function () {
// Use the 'api' guard instead of the default 'web'
})->middleware('auth:api');
Note
If you would like to provide "remember me" functionality in your application, you may
pass a boolean value as the second argument to the attempt method. This will keep
the user authenticated indefinitely, or until they manually logout.
Warning: Password Hashing
Laravel's Auth::attempt() method automatically hashes the password provided in
the array and compares it against the hashed password in the database. You should never hash
the password yourself before passing it to attempt(), or the comparison will
fail.
Authorization (Gates & Policies)
While Authentication identifies who a user is, Authorization determines what they are allowed
to do. Laravel provides two primary ways of authorizing actions: Gates and Policies.
Think of Gates as simple "closure-based" checks (like a bouncer at a door), while Policies
are organized around specific Eloquent models (like a legal document defining ownership and
permissions).
Gates
Gates are most useful for actions that are not related to a specific model, such as opening
an administrative dashboard or viewing system logs. They are typically defined in the
boot method of the App\ServiceProvider.
Defining a Gate:
use Illuminate\Support\Facades\Gate;
use App\Models\User;
public function boot(): void
{
Gate::define('access-admin-panel', function (User $user) {
return $user->is_admin;
});
}
Using a Gate:
if (Gate::allows('access-admin-panel')) {
// The user can access the admin panel...
}
if (Gate::denies('access-admin-panel')) {
abort(403);
}
Policies
Policies are classes that organize authorization logic around a particular model or resource.
For example, if your application has a Post model, you might have a
PostPolicy to authorize actions such as creating or updating posts.
| Action |
Policy Method |
Purpose |
| View |
viewAny,
view
|
Can the user see the list or a specific record? |
| Create |
create |
Can the user add a new record? |
| Update |
update |
Can the user edit an existing record? |
| Delete |
delete |
Can the user remove a record? |
Creating a Policy:
php artisan make:policy PostPolicy --model=Post
Example Policy Logic:
public function update(User $user, Post $post): bool
{
// Only the author can update the post
return $user->id === $post->user_id;
}
Authorizing Actions
Laravel provides several ways to check permissions using policies.
Via the User Model
The User model includes two helpful methods for checking permissions: can and cannot.
if ($user->can('update', $post)) {
// ...
}
Via Controller Helpers
In your controllers, you can use the authorize method. If the action is not
authorized, a 403 Forbidden response is automatically sent.
public function update(Request $request, Post $post)
{
$this->authorize('update', $post);
// Continue update logic...
}
Via Blade Directives
To conditionally show parts of your UI, use the @can and @cannot
directives.
@can('update', $post)
<button>Edit Post</button>
@endcan
The "Super Admin" Bypass
For administrative users, you might want to grant all permissions regardless of what the
Gates or Policies say. You can use the Gate::before method to intercept all
authorization checks.
Gate::before(function ($user, $ability) {
if ($user->is_super_admin) {
return true;
}
});
Note
Policy Auto-Discovery
Laravel automatically discovers policies as long as they are in the
app/Policies directory and the name matches the model (e.g.,
Post -> PostPolicy). You don't need to manually register them in most
cases.
Warning
Middleware Placement
When using authorization in middleware (e.g.,
->middleware('can:update,post')), ensure it comes after the
auth middleware. You cannot authorize a user if the system hasn't
identified who they are yet.
Email Verification
In many web applications, it is necessary to verify that a user actually owns the email
address they used to register. Laravel provides a built-in, painless way to send and verify
email verification requests. This system ensures that users have a valid email before they
can access specific routes or features.
Model Preparation
To get started, your User model must implement the MustVerifyEmail
contract. This interface tells Laravel that this model requires verification.
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable implements MustVerifyEmail
{
use Notifiable;
// ...
}
By default, the users table migration already includes an
email_verified_at column. If yours does not, ensure it is added:
$table->timestamp('email_verified_at')->nullable();
Routing & Controllers
To handle verification, you need to define three specific routes. While starter kits like
Breeze handle this for you, the manual logic involves:
| Route |
Purpose |
Description |
| Notice |
Displaying a "Verify Your Email" page. |
Shown to users who haven't verified yet. |
| Handler |
Processing the clicked link in the email. |
Marks the user as verified in the database. |
| Resend |
Allowing the user to request a new link. |
Throttled to prevent spam. |
Protecting Routes
To ensure only verified users can access a specific route, Laravel provides the
verified middleware. This middleware is typically applied to dashboard or
profile routes.
Route::get('/profile', function () {
// Only verified users may access this...
})->middleware(['auth', 'verified']);
If an unverified user attempts to access these routes, they will automatically be redirected
to the email verification notice page.
Event Handling
When a user is successfully verified, Laravel dispatches an
Illuminate\Auth\Events\Verified event. You can listen for this event to perform
actions like:
- Sending a "Welcome" email.
- Assigning a default role.
- Updating a CRM.
Customizing the Verification Email
The verification email is sent via a Notification. If you want to change the text, the
"Verify" button color, or the overall layout, you can override the toMail
method by calling VerifyEmail::toMailUsing() in your
AppServiceProvider.
use Illuminate\Auth\Notifications\VerifyEmail;
use Illuminate\Notifications\Messages\MailMessage;
public function boot(): void
{
VerifyEmail::toMailUsing(function (object $notifiable, string $url) {
return (new MailMessage)
->subject('Verify Your Account')
->line('Click the button below to join our community.')
->action('Verify Email Address', $url);
});
}
Note
Manual Verification
If you need to verify a user manually (e.g., via an admin panel), you can call the
markEmailAsVerified() method on the user model instance. This will
update the email_verified_at timestamp automatically.
Warning
Security of Links
Laravel verification links are "signed" URLs. They contain a cryptographic hash that
prevents users from tampering with the user ID or expiration time in the URL. Never
modify the signature logic unless you are an advanced user, as it protects your app
from unauthorized account activations.
Encryption
Laravel provides a simple, convenient interface for encrypting and decrypting data via the
OpenSSL library using AES-256 and AES-128 encryption. All of Laravel's encrypted values are
signed using a Message Authentication Code (MAC) so that their underlying value cannot be
modified or tampered with once encrypted.
The Application Key
Before you can use Laravel's encrypter, you must set the APP_KEY in your
.env file. This key is used to "salt" your encrypted data. If this key is not
set, your encrypted data will be insecure.
| Component |
Setting in .env |
Requirement |
| App Key |
APP_KEY |
Must be 32 characters (random string). |
| Cipher |
APP_CIPHER |
Default is AES-256-CBC. |
Warning
If you change your APP_KEY, all data currently encrypted by Laravel
will become unreadable. Never change this key in a production environment if you
have encrypted data in your database.
Encrypting & Decrypting Values
You may encrypt values using the Crypt facade. These methods are designed to handle strings,
but they can also handle serialized objects and arrays.
Basic Usage
use Illuminate\Support\Facades\Crypt;
// Encrypting a value
$encrypted = Crypt::encryptString('Hello world.');
// Decrypting a value
try {
$decrypted = Crypt::decryptString($encrypted);
} catch (DecryptException $e) {
// The payload was invalid or the MAC was tampered with
}
Comparison: Encryption vs. Hashing
It is common to confuse encryption with hashing (which we covered in previous sections). They
serve very different security purposes.
| Feature |
Encryption ( Crypt ) |
Hashing ( Hash ) |
| Type |
Two-way (Reversible). |
One-way (Irreversible). |
| Purpose |
To store data you need to read later (e.g., API keys). |
To verify data (e.g., passwords). |
| Recovery |
Can be decrypted with the APP_KEY. |
Cannot be "decrypted." |
Eloquent Attribute Encryption
As discussed in the Mutators & Casting section, Laravel makes it incredibly easy to encrypt
specific database columns automatically. This ensures that even if your database is
compromised, sensitive fields remain unreadable without the APP_KEY.
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* The attributes that should be cast.
*/
protected function casts(): array
{
return [
'secret_token' => 'encrypted',
'bank_account_data' => 'encrypted:json', // Encrypts an array as JSON
];
}
}
Encryption for Files and Streams
While Crypt::encryptString is for short strings, you should not use it for
massive files, as it loads the entire content into memory. For large files, it is better to
use dedicated streaming encryption libraries or handle the encryption in chunks.
Note
Handling Decryption Failures
If the decryptString method fails (due to a changed APP_KEY
or a tampered payload), it throws an
Illuminate\Contracts\Encryption\DecryptException. Always wrap your
decryption logic in a try-catch block when dealing with user-provided
or external data.
Hashing
Laravel provides a secure, built-in interface for hashing data, primarily used for storing
user passwords. Unlike Encryption, hashing is a one-way cryptographic operation. Once a
value is hashed, it cannot be "decrypted" back to its original form. Instead, you verify the
original value by hashing it again and comparing the results.
Laravel uses the Bcrypt and Argon2 hashing algorithms by default, ensuring your application
stays compliant with modern security standards.
Configuration
The hashing configuration is managed in config/hashing.php. Here, you can
specify which driver to use and customize the "work factor" (the amount of processing power
required to generate a hash).
| Algorithm |
Driver |
Recommendation |
| Bcrypt |
bcrypt |
The default and most common choice for web apps. |
| Argon2i |
argon |
Secure, but requires the PHP zodium extension. |
| Argon2id |
argon2id |
Most secure; resistant to GPU-based side-channel attacks. |
Basic Usage
You may hash a string by calling the make method on the Hash facade.
Hashing a Password
use Illuminate\Support\Facades\Hash;
$hashedPassword = Hash::make($request->newPassword);
Verifying a Password
The check method allows you to verify that a given plain-text string matches a given hash.
This is exactly what Laravel does internally during the authentication process.
if (Hash::check('plain-text-password', $hashedPassword)) {
// The passwords match...
}
Checking If a Password Needs Re-hashing
As hardware improves, you may want to increase the "work factor" of your hashes. The
needsRehash method allows you to determine if the hash was created using a work
factor that is no longer considered secure.
if (Hash::needsRehash($hashedPassword)) {
$hashedPassword = Hash::make('plain-text-password');
}
Automatic Hashing (Eloquent)
As of Laravel 10+, you can use the hashed cast in your Eloquent models. This
ensures that any value assigned to that attribute is automatically hashed before being saved
to the database.
protected function casts(): array
{
return [
'password' => 'hashed',
];
}
// In your controller:
User::create([
'name' => 'John',
'email' => 'john@example.com',
'password' => 'secret123', // Automatically hashed by Eloquent
]);
Encryption vs. Hashing Summary
| Feature |
Encryption ( Crypt ) |
Hashing ( Hash ) |
| Nature |
Two-way (Reversible). |
One-way (Irreversible). |
| Best For |
API Keys, sensitive PII. |
Passwords, PINs. |
| Database Storage |
Ciphertext (readable with key). |
Hash digest (never readable). |
Note
Secure by Default
Laravel uses a "salt" for every hash automatically. This means that if two users have
the exact same password, their hashes stored in the database will still be
completely different, protecting your app against "Rainbow Table" attacks.
Warning
Never Store Plain Text
Storing passwords in plain text is a critical security failure. Even if you use a
"custom" reversible encryption, a database breach would expose all user passwords.
Always use the Hash facade or the hashed model cast.
Password Reset
Laravel provides a robust, built-in system for handling user password resets. This includes
generating secure tokens, sending reset links via email, and updating the password in the
database. Like authentication, this system is designed to work out of the box with minimal
configuration, especially when using Laravel starter kits.
Core Components
The password reset process relies on several moving parts to ensure security and a smooth
user experience.
| Component |
Responsibility |
Password
Facade
|
The primary interface for sending reset links and resetting passwords. |
| Broker |
Manages the logic of creating tokens and retrieving users
(configured in config/auth.php).
|
| Tokens Table |
A database table (password_reset_tokens)
that stores hashes of reset tokens.
|
| Notification |
The email sent to the user containing the unique reset link. |
Database Configuration
Before using the password reset system, ensure the password_reset_tokens table
exists. This is included in a default Laravel installation.
Schema::create('password_reset_tokens', function (Blueprint $table) {
$table->string('email')->primary();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
Sending the Reset Link
To send a password reset link, use the sendResetLink method on the
Password facade. This method expects an array of data that includes the user's
email address.
use Illuminate\Support\Facades\Password;
$status = Password::sendResetLink(
$request->only('email')
);
return $status === Password::RESET_LINK_SENT
? back()->with(['status' => __($status)])
: back()->withErrors(['email' => __($status)]);
Resetting the Password
When the user clicks the link in their email, they are directed to a form where they enter
their new password. You then use the reset method to validate the token and update the user.
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Str;
$status = Password::reset(
$request->only('email', 'password', 'password_confirmation', 'token'),
function ($user, $password) {
$user->forceFill([
'password' => Hash::make($password)
])->setRememberToken(Str::random(60));
$user->save();
}
);
Password Reset Security
Laravel implements several security layers to prevent abuse of the reset system:
- Token Expiration: By default, tokens expire after 60 minutes (configurable in
config/auth.php).
- Token Hashing: Tokens are never stored in plain text in your database; only their hashes
are stored.
- Throttling: Sending reset emails is automatically throttled to prevent spamming a user's
inbox.
- Single-Use: Once a password is successfully reset, the token is immediately deleted from
the database.
Note
Customizing the Email
To customize the reset email, you can override the
sendPasswordResetNotification method on your User model.
This allows you to use a custom Notification class with your own branding and text.
Warning
The Token Payload
The token sent in the email is a plain-text string, while the version in
the password_reset_tokens table is hashed. If you try to manually query
the database for a token using the plain-text string from the URL, the match will
fail.
Artisan Console Custom Commands
Artisan is the command-line interface included with Laravel. While you use it daily to
migrate databases and make controllers, its true power lies in the ability to create custom
commands. These are perfect for automating repetitive tasks, such as clearing old logs,
syncing external APIs, or generating complex reports.
Creating a Command
To create a new command, use the make:command Artisan command. This will
generate a new class in the app/Console/Commands directory.
| Command |
Action |
php artisan make:command SendEmails
|
Generates a basic command class. |
php artisan list
|
Shows all available commands, including your new one. |
php artisan help [command]
|
Displays the description and arguments for a specific command. |
Command Structure
A command class consists of two main parts: the signature (how you call the command) and the
handle method (what the command actually does).
namespace App\Console\Commands;
use Illuminate\Console\Command;
class SendEmails extends Command
{
// The name and signature of the console command
protected $signature = 'mail:send {user} {--queue}';
// The console command description
protected $description = 'Send an email to a specific user';
// Execute the console command
public function handle()
{
$userId = $this->argument('user');
$useQueue = $this->option('queue');
$this->info("Sending mail to user: {$userId}...");
// Logic here...
$this->success("Mail sent successfully!");
}
}
Arguments & Options
Signatures allow you to define what data your command needs.
| Type |
Syntax |
Description |
Example |
| Argument |
{user} |
Required input data. |
php artisan mail:send 1 |
| Optional Arg |
{user?} |
Input that can be omitted. |
php artisan mail:send |
| Option |
{--queue} |
A flag (true/false). |
php artisan mail:send 1 --queue |
| Value Option |
{--id=} |
An option that requires a value. |
php artisan mail:send --id=5 |
I/O: Interacting with the User
Artisan provides several methods to make your command-line output look professional and
interactive.
- Outputting Text: Use
$this->info(), $this->error(), $this->warn(), or $this->line().
- Progress Bars: Perfect for long-running loops.
$bar = $this->output->createProgressBar(count($users));
foreach ($users as $user) {
// process...
$bar->advance();
}
$bar->finish();
User Input: Ask for confirmation or secrets.
if ($this->confirm('Do you wish to continue?')) {
$password = $this->secret('Enter the API password');
}
Running Commands Programmatically
You don't always have to run Artisan commands from the terminal. You can trigger them from
within your controllers or jobs using the Artisan facade.
use Illuminate\Support\Facades\Artisan;
Route::post('/clear-cache', function () {
Artisan::call('cache:clear');
return "Cache Cleared!";
});
Note
Automatic Registration
In modern Laravel versions, any command stored in app/Console/Commands
is automatically registered by the framework. You do not need to manually add them
to a list.
Warning
Long-Running Processes
If a command runs for a very long time, it may hit the PHP
max_execution_time. However, when running via CLI, this is typically
set to 0 (infinite). Be mindful of memory usage; use chunk() when
processing thousands of database records to keep the footprint small.
Broadcasting (WebSockets & Reverb)
Laravel Broadcasting allows you to share the same event names between your server-side
Laravel application and your client-side JavaScript application. This enables real-time
features like live notifications, chat messages, or updating dashboards without the user
needing to refresh the page.
The Broadcasting Flow
When an action occurs on the server, an Event is dispatched. If that event implements the
ShouldBroadcast interface, Laravel sends it to a "Broadcasting Driver" (like
Reverb, Pusher, or Ably), which then pushes the data to the client via WebSockets.
| Driver |
Description |
| Laravel Reverb |
A first-party, high-performance WebSocket server built specifically for
Laravel. |
| Pusher |
A hosted third-party service; easy to set up but has usage limits. |
| Ably |
A hosted alternative to Pusher with advanced scaling features. |
| Redis / Socket.io |
A self-hosted option using a Node.js companion server. |
Channel Types
Not all data should be public. Laravel uses Channels to control who receives which
broadcasts.
| Channel Type |
Access Level |
Use Case |
| Public |
Anyone can listen. |
Stock tickers, public news feeds. |
| Private |
Requires authentication. |
User notifications, order status updates. |
| Presence |
Private + User data. |
"Who is online" lists, "User is typing..." indicators. |
Defining Broadcast Events
To broadcast an event, add the ShouldBroadcast interface to your event class.
You must define a broadcastOn method to specify which channel(s) the event
should be sent to.
namespace App\Events;
use App\Models\Order;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Broadcasting\PrivateChannel;
class OrderShipmentStatusUpdated implements ShouldBroadcast
{
public function __construct(public Order $order) {}
public function broadcastOn(): array
{
return [
new PrivateChannel('orders.' . $this->order->user_id),
];
}
}
Authorizing Channels
For Private and Presence channels, you must define authorization logic in
routes/channels.php. This acts like a "Gate" for your WebSockets.
// routes/channels.php
Broadcast::channel('orders.{userId}', function ($user, $userId) {
return (int) $user->id === (int) $userId;
});
Listening on the Frontend (Laravel Echo)
Laravel Echo is a JavaScript library that makes it painless to subscribe to channels and
listen for events broadcast by Laravel.
import Echo from 'laravel-echo';
window.Echo.private(`orders.${userId}`)
.listen('OrderShipmentStatusUpdated', (e) => {
console.log('Order status changed to:', e.order.status);
});
Laravel Reverb
Introduced in 2024, Reverb is the modern standard for Laravel WebSockets. It is written in
PHP using the Revolt event loop, making it incredibly fast and capable of handling thousands
of simultaneous connections without needing Node.js or a third-party service.
Why use Reverb?
- First-party: Built by the Laravel team specifically for the framework.
- Horizontal Scaling: Supports scaling across multiple servers using Redis.
- No Extra Cost: Self-hosted on your own server, so there are no monthly fees for high
usage.
Note
Data Payloads
By default, Laravel will include all public properties of your Event
class in the broadcast payload. If you want more control over the data sent to the
client, you can define a broadcastWith method in your event class.
Warning
CSRF and WebSockets
Private channels require an AJAX request to authorize the user. Ensure your frontend
is correctly sending the X-CSRF-TOKEN or using
withCredentials (for Sanctum) so that Laravel can verify the user's
session.
Cache
In high-traffic applications, querying a database or performing complex calculations
repeatedly can slow down your server.
Caching allows you to store the results of these expensive operations in a
fast storage system (like RAM)
so that subsequent requests can retrieve the data almost instantaneously.
Cache Drivers
Laravel provides a unified API for various caching backends. You can configure your default
driver in the
config/cache.php file or via the CACHE_STORE environment variable.
| Driver |
Description |
Best Use Case |
file |
Stores cached items in the filesystem
(storage/framework/cache). |
Local development; simple apps with single servers. |
database |
Stores items in a database table. |
When you don't want to manage a separate cache server. |
redis |
An in-memory key-value store. |
Production standard; extremely fast and supports tagging.
|
memcached |
A high-performance memory object caching system. |
Legacy systems or specific high-concurrency needs. |
array |
Stores items in a simple PHP array. |
Testing; data persists only for the current request. |
Basic Usage
You interact with the cache using the Cache facade. It follows a simple
key-value store logic.
Storing & Retrieving
use Illuminate\Support\Facades\Cache;
// Store for 60 seconds
Cache::put('key', 'value', $seconds = 60);
// Retrieve (returns null if not found)
$value = Cache::get('key');
// Retrieve with a default value
$value = Cache::get('key', 'default');
The "Remember" Pattern
This is the most common use case: "If it's in the cache, give it to me; otherwise, get it
from the DB and save it for next time."
$users = Cache::remember('users.active', $seconds = 3600, function () {
return User::where('active', 1)->get();
});
Atomic Locks
Atomic locks allow for the manipulation of distributed locks without worrying about "race
conditions."
For example, if you want to ensure only one server processes a specific task at a time.
$lock = Cache::lock('processing-invoice-1', 10);
if ($lock->get()) {
// Critical section logic...
$lock->release();
}
Cache Tags
Note
Cache tags are not supported when using the file, dynamodb,
or database cache drivers. They require redis or memcached.
Tags allow you to group related cached items. This makes it easy to clear an entire category
of data
without affecting the rest of the cache.
// Storing tagged data
Cache::tags(['people', 'artists'])->put('John', $john, $seconds);
// Clearing all "people" tags
Cache::tags('people')->flush();
Cache Best Practices
| Practice |
Description |
| Granular Keys |
Use specific keys like user:1:profile rather than one giant
users key.
|
| Avoid Over-Caching |
Don't cache data that changes every few seconds; the overhead of writing to
cache might outweigh the benefit. |
| Fallbacks |
Always provide a default or a closure in Cache::get to handle
cache misses gracefully. |
| Production Driver |
Always use Redis or Memcached in
production for true performance gains. |
Warning
Cache Invalidation
The hardest part of caching is knowing when to delete it. If you cache a user's
profile, ensure you use an Eloquent Observer or Listener to clear that specific
cache key whenever the user updates their information.
Collections (Laravel Base Collections)
While we previously covered Eloquent Collections, Laravel also provides
Base Collections for handling standard PHP arrays. A Collection is a
sophisticated wrapper around arrays, providing a fluent, chainable interface for data
manipulation. Instead of using messy nested foreach loops and
array_ functions, you can transform data using a functional approach.
Creating Collections
You can create a collection using the collect helper or by instantiating the
Illuminate\Support\Collection class.
$collection = collect([1, 2, 3, 4, 5]);
// Or from an associative array
$prices = collect(['apple' => 10, 'orange' => 20]);
Essential Transformation Methods
Base collections offer over 100 methods to filter, sort, and transform data. Here are the
"Power Players" used in daily development:
| Method |
Action |
Example |
map() |
Iterates and modifies every item. |
collect([1, 2])->map(fn($n) => $n * 10); // [10, 20] |
filter() |
Removes items that don't pass a truth test. |
$col->filter(fn($n) => $n > 5); |
reduce() |
Reduces collection to a single value. |
$col->reduce(fn($carry, $item) => $carry + $item); |
pluck() |
Retrieves values from a specific key. |
$users->pluck('email'); |
flatten() |
Flattens a multi-dimensional array. |
collect([[1, 2], [3, 4]])->flatten(); // [1, 2, 3, 4] |
chunk() |
Breaks collection into smaller sizes. |
$col->chunk(3); // Useful for grid layouts |
Chainability & Readability
The true power of collections is method chaining. Each method returns a new
collection instance, allowing you to perform complex data surgery in a single, readable
block.
$names = collect([' taylor', 'abigail', null, ' jeffrey'])
->map(fn($name) => trim($name)) // Remove whitespace
->filter() // Remove nulls
->map(fn($name) => ucfirst($name)) // Capitalize
->sort(); // Alphabetize
// Result: ['Abigail', 'Jeffrey', 'Taylor']
Advanced Comparison & Mathematical Methods
Collections aren't just for loops; they include powerful logic for comparing sets of data and
performing math.
diff(): Returns items in the original collection
that are not present in the given collection.
intersect(): Returns items present in both
collections.
sum(), avg(), min(),
max(): Instantly calculate totals or averages.
unique(): Removes duplicate items from the
collection.
Lazy Collections
Standard collections load the entire array into memory. If you are reading a 500MB log file
or thousands of records, your script might crash.
Lazy Collections use PHP generators to process one item at
a time, keeping memory usage near zero.
use Illuminate\Support\LazyCollection;
LazyCollection::make(function () {
$handle = fopen('huge_file.csv', 'r');
while (($line = fgetcsv($handle)) !== false) {
yield $line;
}
})->map(fn($row) => $row[0])->each(function ($data) {
// Process one row at a time
});
Note
Immutability
Collections are immutable. Calling a method like map()
or filter() does not change the original collection; it returns a
new one. If you need to keep the results, make sure to assign the result to
a variable.
Warning
Collection vs. Array
While collections are powerful, they carry a slight performance overhead compared to
raw PHP arrays. For small arrays or extremely performance-critical loops inside a
high-frequency function, a standard foreach on a native array will be
faster.
Context (Request Context)
In a typical web application, "Context" refers to the specific environment and data
associated with a single lifecycle of a request. Laravel provides several mechanisms to
access and store this data, ensuring that information—like the currently authenticated user,
the language preference, or a unique tracking ID—is available throughout the entire
execution of your code without having to pass variables manually between every function.
The Request Object
The most immediate form of context is the Illuminate\Http\Request instance. It
contains everything about the incoming HTTP call.
| Category |
Method |
Description |
| Input |
$request->input('key') |
Accesses data from GET, POST, or JSON body. |
| Authentication |
$request->user() |
Retrieves the currently logged-in user instance. |
| Environment |
$request->ip() / $request->userAgent() |
Details about the client's machine and browser. |
| Route Info |
$request->route()->getName() |
Identifies which specific route was triggered. |
Context Class (The Context Facade)
Introduced in recent Laravel versions, the Context facade allows you to store
and retrieve data that is local to the current request or
job. This is particularly useful for logging, as any data added to the
Context is automatically appended to every log entry generated during that
request.
Storing Contextual Data:
use Illuminate\Support\Facades\Context;
// In a Middleware
Context::add('request_id', (string) Str::uuid());
Context::add('tenant_id', $request->user()->tenant_id);
Retrieving Contextual Data:
$tenantId = Context::get('tenant_id');
Context vs. Sessions
It is important to distinguish between "Request Context" and "User Sessions".
| Feature |
Request Context (Context) |
Sessions (Session) |
| Lifespan |
Single Request. Disappears after response. |
Persistent. Lasts across many requests. |
| Storage |
Memory (RAM). |
File, Database, or Redis. |
| Best For |
Trace IDs, logging metadata, current tenant. |
Login status, shopping carts, flash messages. |
Sharing Context with Background Jobs
One of the most powerful features of Laravel's Context system is its ability to "follow" the
execution into the queue. If you dispatch a background job while certain data is in the
Context, Laravel can automatically hydrate that context inside the queue worker
when the job starts processing.
// In config/context.php, you can define which keys should be hidden or shared
'stack' => [
'request_id',
'source_ip',
],
The Service Container as Context
The Service Container itself acts as a context manager. When you bind a
class as a "Singleton," it exists for the duration of that request. This is often used for
"Repository" or "Service" classes that need to maintain state while the request is being
processed.
// Binding a singleton to hold "Request-Specific" settings
$this->app->singleton(Settings::class, function ($app) {
return new Settings($app['request'])->user();
});
Note
Global Helpers
While the Context facade is the modern way to handle this, many
developers still use the request() helper to access context globally
without dependency injection. However, using the Context facade is
cleaner for data that isn't strictly part of the HTTP specification (like a
transaction ID).
Warning
Data Leakage
Because Context is stored in memory, be careful not to store massive
objects or sensitive credentials that might accidentally end up in your log files.
Always use Context::addHidden() for data that you want to access
programmatically but do not want to see in your logs.
Contracts
Laravel Contracts are a set of interfaces that define the core services
provided by the framework. For example, the Illuminate\Contracts\Queue\Queue
contract defines the methods needed for queueing jobs, while the
Illuminate\Contracts\Mail\Mailer contract defines the methods needed for
sending emails.
If you type-hint a contract in your class constructor, Laravel's service container will
automatically resolve the concrete implementation for you.
Contracts vs. Facades
While Facades provide a "static" way to access services, Contracts provide an explicit way
to define your class's dependencies.
| Feature |
Facades |
Contracts |
| Syntax |
Cache::get('key') |
$this->cache->get('key') |
| Dependency |
Implicit (hidden) |
Explicit (visible in constructor) |
| Testing |
Uses "Mock" methods on facade |
Standard PHPUnit interface mocking |
| Coupling |
High (tied to Laravel Facades) |
Low (tied to a PHP Interface) |
Why Use Contracts?
The primary benefits of using Contracts are Loose Coupling and
Simplicity.
- Loose Coupling: Your code is not tied to a specific library. If you
want to swap the cache implementation from Redis to Memcached, your application code
remains untouched because it only cares about the
Cache interface.
- Simplicity: When all of Laravel's services are defined within tidy
interfaces, it is very easy to determine what a particular service does.
How to Use Contracts
To use a contract, you simply type-hint the interface in the constructor of your class.
Laravel's Service Container will look at the interface and inject the
correct underlying class.
namespace App\Orders;
use App\Models\Order;
use Illuminate\Contracts\Cache\Repository as Cache;
class OrderProcessor
{
/**
* Create a new processor instance.
*/
public function __construct(
protected Cache $cache, // The Contract
) {}
public function process(Order $order): void
{
if ($this->cache->has('order.'.$order->id)) {
return;
}
// Processing logic...
}
}
Commonly Used Contracts
Laravel provides dozens of contracts. Below are the ones you will encounter most frequently:
| Contract |
Purpose |
Illuminate\Contracts\Auth\Authenticatable |
Defines what a "User" model must look like. |
Illuminate\Contracts\Cache\Repository |
Provides methods for interacting with the cache. |
Illuminate\Contracts\Config\Repository |
Provides methods for accessing configuration values. |
Illuminate\Contracts\Filesystem\Filesystem |
Defines methods for reading/writing files (Local, S3). |
Illuminate\Contracts\Queue\ShouldQueue |
An interface that tells Laravel a job should be backgrounded. |
Note
When to use Contracts
Most developers use Facades for speed and simplicity in
controllers. However, if you are building a Package or a highly
complex enterprise application where you might want to swap out core components,
Contracts are the superior choice.
Warning
Choosing the Right Interface
Always ensure you are importing the interface from the
Illuminate\Contracts namespace. Many services have a "concrete" class
with a similar name in a different namespace; type-hinting the concrete class
instead of the interface defeats the purpose of using Contracts.
Events & Listeners
Laravel's Events and Listeners provide a simple observer
implementation, allowing you to subscribe and listen for various events that occur in your
application. This serves as a great way to decouple various aspects of your
application, since a single event can have multiple listeners that do not depend on each
other.
For example, when an order is shipped, you may want to send a Slack notification, update the
inventory, and email the customer. Instead of bloating your OrderController,
you can simply dispatch an OrderShipped event.
The Workflow
The event system follows a straightforward "Announcement and Reaction" pattern.
| Component |
Responsibility |
| Event |
A "Plain Old PHP Object" (POPO) that contains the data related to the event
(e.g., the Order model). |
| Listener |
A class that performs the actual logic when the event is triggered (e.g.,
SendShipmentNotification).
|
| Dispatcher |
The mechanism that notifies all listeners that an event has occurred. |
Creating Events & Listeners
You can generate these classes quickly using Artisan:
php artisan make:event OrderShipped
php artisan make:listener SendShipmentNotification --event=OrderShipped
Defining the Event
An event class typically just accepts a model instance via its constructor:
namespace App\Events;
use App\Models\Order;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class OrderShipped
{
use Dispatchable, SerializesModels;
public function __construct(public Order $order) {}
}
Registering Events
In modern Laravel versions, you don't always need to manually register events in a Service
Provider; the framework can auto-discover them. However, you can still
manually map them in the App\Providers\AppServiceProvider:
use App\Events\OrderShipped;
use App\Listeners\SendShipmentNotification;
use Illuminate\Support\Facades\Event;
/**
* Register any events for your application.
*/
public function boot(): void
{
Event::listen(
OrderShipped::class,
SendShipmentNotification::class,
);
}
Queued Listeners
If a listener is going to perform a slow task (like sending an email or calling a 3rd-party
API), you can tell Laravel to handle it in the background by implementing the
ShouldQueue interface.
namespace App\Listeners;
use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendShipmentNotification implements ShouldQueue
{
public function handle(OrderShipped $event): void
{
// Access the order via $event->order...
// This runs in the background!
}
}
Dispatching Events
To trigger an event, use the dispatch static method on the event class or the
event() helper.
use App\Events\OrderShipped;
public function shipOrder($orderId)
{
$order = Order::findOrFail($orderId);
// Logic to ship the order...
OrderShipped::dispatch($order);
}
Summary of Benefits
| Benefit |
Description |
| Clean Controllers |
Keeps business logic out of your HTTP layer. |
| Extensibility |
Easy to add new features (e.g., a new "Rewards" listener) without touching
existing code. |
| Asynchronous |
Queued listeners ensure the user doesn't wait for slow background tasks.
|
Note
Event Discovery
If you prefer not to register events manually, ensure your listener methods are
type-hinted with the event they handle. Laravel will scan your
Listeners directory and map them automatically.
Warning
SerializesModels
Always use the SerializesModels trait on your Event classes. If the
event is queued, Laravel will only store the Model's ID in the queue. When the
listener runs, it will automatically re-fetch the fresh model from the database,
preventing "stale data" issues.
File Storage & Filesystem
Laravel provides a powerful filesystem abstraction thanks to the Flysystem PHP package. This
allows you to interact with local files, Amazon S3, and SFTP using the same simple API. You
can switch your storage backend in your configuration without changing a single line of
application code.
Configuration & Disks
The filesystem configuration is located at config/filesystems.php. Inside this
file, you define "disks." A disk represents a particular storage driver and location.
| Disk |
Driver |
Description |
local |
local |
Private files stored in storage/app. Not accessible via URL.
|
public |
local |
Publicly accessible files (after running storage:link). |
s3 |
s3 |
Amazon S3 cloud storage. Requires the S3 SDK package. |
Storing Files
You can use the Storage facade to store files. Laravel automatically handles
generating unique file names if you don't provide one.
Basic Uploads
use Illuminate\Support\Facades\Storage;
// Store a file with an automatically generated name
$path = Storage::putFile('avatars', $request->file('avatar'));
// Store a file with a specific name
Storage::put('photos/1.jpg', $contents);
Automatic Stream Uploads
When working with large files, use putFile or putFileAs. Laravel
will automatically stream the file to your storage (S3 or Local) to keep memory usage low.
Retrieving Files
| Task |
Method |
| Get Content |
$content = Storage::get('file.jpg');
|
| Download |
return Storage::download('file.jpg');
|
| File URL |
$url = Storage::url('file.jpg');
|
| Check Existence |
if (Storage::exists('file.jpg')) { ... }
|
The Public Disk & Symbolic Links
Files stored in storage/app/public are not accessible to the web by default. To
make them accessible, you must create a symbolic link from public/storage to
storage/app/public.
Command:
php artisan storage:link
Once linked, you can generate a URL to the file in your Blade templates:
<img src="{{ asset('storage/avatars/user.jpg') }}">
File Metadata & Deletion
Laravel makes it easy to manage file properties and cleanup.
// Get file size in bytes
$size = Storage::size('file.jpg');
// Get last modified timestamp
$time = Storage::lastModified('file.jpg');
// Delete a single file
Storage::delete('file.jpg');
// Delete multiple files
Storage::delete(['file1.jpg', 'file2.jpg']);
Note
S3 Visibility
When uploading to S3, you can specify if the file should be "public" or "private"
directly in the put method:
Storage::put('file.jpg', $contents, 'public');
Warning
Cloud Storage Latency
While the API is the same for Local and S3, remember that S3 involves a network
request.
If you are checking Storage::exists() or getting file sizes inside a
loop
for 100 files, your application will slow down significantly. In those cases,
consider
caching file metadata in your database.
Helpers (Array, String, Path)
Laravel includes a variety of global PHP "helper" functions. These functions are designed to
make your life easier by providing shortcuts for common tasks like manipulating arrays,
transforming strings, and generating paths to files within your application.
String Helpers ( Str )
While PHP has native string functions, Laravel's Str class (and the
str() helper) provides a more fluent and expressive way to handle complex
string manipulations.
| Method |
Result |
Description |
str()->slug('Laravel Framework') |
laravel-framework |
Ideal for SEO-friendly URLs. |
str()->limit($text, 20) |
The quick brown... |
Truncates a string with a suffix. |
str()->contains($str, 'word') |
true / false |
Checks if a string exists within another. |
str()->snake('FooBar') |
foo_bar |
Converts to snake_case. |
str()->isUuid($string) |
true / false |
Validates if a string is a UUID. |
Fluent Strings:
You can chain multiple string operations together using the str() helper:
$result = str(' Laravel 11 ')->trim()->replace('11', '12')->upper();
// "LARAVEL 12"
Array Helpers ( Arr )
The Arr class (and collect() for more complex logic) provides tools
for working with PHP arrays.
| Method |
Purpose |
Arr::get($array, 'user.name') |
Uses "dot notation" to get a nested value safely. |
Arr::has($array, 'products.0.price') |
Checks if a nested key exists. |
Arr::forget($array, 'key') |
Removes a key/value pair using dot notation. |
Arr::pluck($array, 'email') |
Pulls an array of values from a specific key. |
Arr::wrap($value) |
Ensures the value is an array (wraps it if it's not). |
Path Helpers
Path helpers allow you to generate absolute paths to various directories in your Laravel
application. This is much safer than hardcoding paths, as directory structures can change
between local and production environments.
| Helper |
Path Returned |
base_path() |
The root directory of your project. |
app_path() |
The app directory. |
config_path() |
The config directory. |
public_path() |
The public directory. |
resource_path() |
The resources directory (views, CSS, JS). |
storage_path() |
The storage directory. |
Example:
$path = storage_path('app/photos/user.jpg');
Miscellaneous Global Helpers
Beyond strings and arrays, several "power" helpers are used throughout the framework:
blank() / filled(): Better than empty().
blank() returns true if the value is null, an empty string, or only
whitespace.
retry(): Executes a callback a certain number of times until it succeeds or
hits a limit.
throw_if(): Throws an exception if a given condition is true.
optional(): Allows you to access properties or call methods on an object
that might be null without crashing.
Note
Facade vs. Helper
Most helpers have a corresponding Facade (e.g., str() vs Str::). Using
the str() helper is often preferred for "fluent" chaining, while the
Str:: facade is used for quick static calls.
Warning
Custom Helpers
If you create your own global helper functions, it is best practice to wrap them in a
function_exists check to avoid collisions with future Laravel updates
or third-party packages.
HTTP Client (Guzzle Wrapper)
Laravel provides an expressive, minimal API around the Guzzle HTTP client,
allowing you to quickly make outgoing HTTP requests to communicate with other web
applications or third-party APIs. It focuses on the most common use cases and provides a
wonderful testing experience.
Making Requests
You can use the Http facade to send GET, POST,
PUT, PATCH, and DELETE requests. The client
automatically handles JSON data and returns a fluent response object.
| Method |
Usage |
Description |
get() |
Http::get($url, $params) |
Retrieves data from an endpoint. |
post() |
Http::post($url, $data) |
Sends data (defaults to JSON) to an endpoint. |
withToken() |
Http::withToken($token) |
Quickly adds a Bearer token to the Authorization header. |
timeout() |
Http::timeout(5) |
Specifies the maximum number of seconds to wait. |
Example:
use Illuminate\Support\Facades\Http;
$response = Http::withToken('your-api-key')
->post('https://api.example.com/orders', [
'product_id' => 1,
'quantity' => 5,
]);
Handling Responses
The response object returned by the client provides a variety of methods that can be used to
inspect the results of the request.
| Method |
Result |
$response->body() |
Returns the raw body content as a string. |
$response->json() |
Decodes the JSON response into an array. |
$response->status() |
Returns the HTTP status code (e.g., 200, 404). |
$response->ok() |
Boolean: check if the status code is 200. |
$response->failed() |
Boolean: check if the status code is >= 400. |
if ($response->successful()) {
return $response->json()['data'];
}
Retries and Error Handling
External APIs can be flaky. Laravel makes it easy to retry requests that fail due to
temporary network issues.
// Retry 3 times, waiting 100ms between attempts
$response = Http::retry(3, 100)->get('https://api.example.com');
// Throw an exception if the request fails (4xx or 5xx)
$response = Http::get('https://api.example.com')->throw();
Concurrent Requests
Sometimes you need to make multiple HTTP requests simultaneously to improve performance. You
can use the pool method to dispatch several requests at once.
use Illuminate\Http\Client\Pool;
$responses = Http::pool(fn (Pool $pool) => [
$pool->get('https://api.test/users'),
$pool->get('https://api.test/posts'),
]);
return $responses[0]->ok() && $responses[1]->ok();
Testing & Faking
One of the best features of the Laravel HTTP client is the ability to "fake" responses. This
allows you to test your application logic without actually hitting an external API.
Http::fake([
// Stub a JSON response for GitHub endpoints...
'github.com/*' => Http::response(['foo' => 'bar'], 200),
// Stub a string response for Google endpoints...
'google.com/*' => Http::response('Hello World', 200),
]);
$response = Http::get('https://github.com/laravel');
// Returns ['foo' => 'bar']
Note
Guzzle Options
Since Laravel's client is a wrapper for Guzzle, you can pass any Guzzle
request options to the withOptions method if you need advanced
configuration not covered by the standard facade.
Warning
Sensitive Data in Logs
If you are logging your outgoing HTTP requests, be careful with the
withToken() or withHeaders() methods. Ensure your logging
middleware masks sensitive keys like Authorization or
X-Api-Key to prevent them from appearing in plain text in your log
files.
Localization (i18n)
Laravel's localization features provide a convenient way to retrieve strings in various
languages, allowing you to easily support multiple languages within your application.
Language strings are stored in files within the lang directory.
Language Files & Structure
Laravel supports two ways of managing translation strings: using PHP files
(for short keys) and JSON files (for full-sentence translations).
| Storage Method |
Directory |
Best Use Case |
| PHP Files |
lang/{locale}/{file}.php |
Small, repetitive keys (e.g., auth.failed). |
| JSON Files |
lang/{locale}.json |
Large applications or translating full sentences. |
Example PHP File (lang/es/messages.php):
return [
'welcome' => '¡Bienvenido a nuestra aplicación!',
];
Example JSON File (lang/es.json):
{
"I love Laravel": "Me encanta Laravel"
}
Retrieving Translation Strings
You may retrieve strings from your language files using the __ helper function
or the @lang Blade directive.
Basic Retrieval
// From PHP file (filename.key)
echo __('messages.welcome');
// From JSON file (exact string)
echo __('I love Laravel');
Replacing Parameters
You can define "placeholders" in your translation strings by prefixing them with a
:.
// lang/en/messages.php
'welcome' => 'Welcome, :name',
// Usage
echo __('messages.welcome', ['name' => 'Dayle']);
Pluralization
Laravel uses the ICU Message Format to handle pluralization rules gracefully
using the trans_choice function or the | character.
// lang/en/messages.php
'apples' => '{0} There are none|[1,19] There are some|[20,*] There are many',
// Usage
echo trans_choice('messages.apples', 10);
Configuring & Switching Locales
The default language for your application is stored in config/app.php. You can
change the locale at runtime using the App facade.
| Action |
Code |
| Get Current Locale |
App::currentLocale(); |
| Set Locale |
App::setLocale('es'); |
| Check Locale |
App::isLocale('fr'); |
Middleware for Language Switching:
Usually, you would create a middleware to set the locale based on a session variable or a URL
parameter:
public function handle(Request $request, Closure $next)
{
if (Session::has('locale')) {
App::setLocale(Session::get('locale'));
}
return $next($request);
}
Overriding Package Translations
If you are using a package that comes with its own translations, you can override them by
placing files in lang/vendor/{package}/{locale}. This allows you to customize
third-party text without modifying the vendor files.
Note
The trans() Helper
The trans() helper is an alias for __(). While both work the same way,
__() is more common in modern Laravel applications, especially when using JSON
translation files.
Warning
Fallback Locales
If a translation string does not exist for the current locale, Laravel will try to
find it in the fallback_locale defined in config/app.php
(usually en). If it still can't find it, it will return the key itself. Always
ensure your fallback language files are complete.
Mail
Laravel provides a clean, simple API over the popular Symfony Mailer
library. It includes drivers for SMTP, Mailgun, Postmark, and Amazon SES, allowing you to
send mail through a local or cloud-based service of your choice.
Configuration
The email services are configured in config/mail.php. Each "mailer" defined here
can have its own options and even its own "transport."
| Driver |
Best Use Case |
| SMTP |
Standard email servers (Gmail, Outlook, Mailtrap). |
| SES |
High-volume, low-cost sending via Amazon Web Services. |
| Mailgun / Postmark |
Managed services with high deliverability and API features. |
| Log |
Writes emails to storage/logs/laravel.log (Great for local
dev). |
Generating Mailables
In Laravel, each email sent by your application is represented as a Mailable
class. These classes are stored in the app/Mail directory.
Command:
php artisan make:mail OrderShipped
The Mailable Structure
A Mailable class uses three main methods to define the email's properties:
namespace App\Mail;
use App\Models\Order;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
class OrderShipped extends Mailable
{
public function __construct(protected Order $order) {}
// Define Subject and "From"
public function envelope(): Envelope
{
return new Envelope(subject: 'Order Shipped');
}
// Define the Blade view and data
public function content(): Content
{
return new Content(
view: 'emails.orders.shipped',
with: ['orderName' => $this->order->name]
);
}
}
Sending Mail
To send an email, use the to method on the Mail facade. The
to method accepts an email address, a user instance, or a collection of users.
use App\Mail\OrderShipped;
use Illuminate\Support\Facades\Mail;
Mail::to($request->user())->send(new OrderShipped($order));
Markdown Mailables
Markdown mailables allow you to take advantage of pre-built, beautiful HTML email templates
provided by Laravel. You can write your email using Markdown, and Laravel will convert it
into a responsive HTML email.
Command:
php artisan make:mail OrderShipped --markdown=emails.orders.shipped
Example Blade (emails/orders/shipped.blade.php):
<x-mail::message>
# Order Shipped
Your order has been dispatched!
<x-mail::button :url="$url">
View Order
</x-mail::button>
Thanks,<br>
{{ config('app.name') }}
</x-mail::message>
Queuing Mail
Since sending email can be a slow process that impacts application response time, most
developers queue emails for background sending. To do this, simply have your Mailable
implement the ShouldQueue interface.
class OrderShipped extends Mailable implements ShouldQueue
{
use Queueable, SerializesModels;
// ...
}
When you call Mail::to(...)->send(), Laravel will automatically push the email
onto the queue.
Previewing Mail in the Browser
Instead of sending emails to a real address during development, you can return a Mailable
directly from a route to see how it looks in the browser.
Route::get('/mail-preview', function () {
$order = App\Models\Order::find(1);
return new App\Mail\OrderShipped($order);
});
Note
Mailtrap
For local testing, Mailtrap is a popular service that acts as a
"fake" SMTP server. It catches all outgoing mail and displays it in a web interface,
ensuring you never accidentally send a test email to a real customer.
Warning
Sensitive Data in Views
When passing data to an email view, remember that email content is often stored in
plain text on mail servers. Avoid sending highly sensitive information (like
passwords or full credit card numbers) directly in the body of an email.
Notifications (Email, SMS, Slack)
While Mailables are great for sending emails, Laravel
Notifications are designed to send short, informational messages across a
variety of delivery channels. A single notification can be delivered via Email, SMS,
Slack, Database, or even custom channels like Telegram or Discord.
The Notification Class
Notifications are represented by a single class (typically stored in
app/Notifications). Each class defines which channels the notification should
be sent on and how the message should be formatted for each specific channel.
| Component |
Purpose |
via() |
Determines which channels the notification will be sent on (e.g.,
mail, database, slack).
|
toMail() |
Defines the email representation of the notification. |
toDatabase() |
Defines the data that should be stored in the notifications
table. |
toSlack() |
Defines the message format for a Slack workspace. |
Command:
php artisan make:notification InvoicePaid
Sending Notifications
You can send notifications in two ways: using the Notifiable trait on your
models or using the Notification facade.
Using the Notifiable Trait
Your User model includes the Notifiable trait by default. This
allows you to call a notify method directly on the user.
use App\Notifications\InvoicePaid;
$user->notify(new InvoicePaid($invoice));
Using the Notification Facade
This is useful when you need to send a notification to a collection of users or an
"on-demand" recipient (someone not in your database).
use Illuminate\Support\Facades\Notification;
Notification::send($users, new InvoicePaid($invoice));
// On-demand (Anonymous) Notification
Notification::route('mail', 'guest@example.com')
->notify(new InvoicePaid($invoice));
Delivery Channels
| Channel |
Requirement |
Best For |
mail |
Mailer configured in mail.php. |
Receipts, password resets, official alerts. |
database |
A notifications database table. |
In-app notification centers/bells. |
broadcast |
WebSockets (Reverb/Pusher). |
Real-time browser alerts. |
slack |
A Slack Webhook URL. |
Internal team alerts (e.g., "New Sale!"). |
vonage / twilio |
External SMS API credentials. |
Urgent security alerts, MFA codes. |
Database Notifications
Storing notifications in a database allows you to display them in your application's UI (the
"bell" icon). Laravel provides a migration for the necessary table:
php artisan notifications:table
Retrieving Notifications:
// Get all unread notifications
foreach ($user->unreadNotifications as $notification) {
echo $notification->data['message'];
// Mark as read
$notification->markAsRead();
}
Formatting Slack Notifications
To send Slack notifications, you must install the Slack notification channel package. You can
then define how the message looks using a "block" style.
public function toSlack(object $notifiable): SlackMessage
{
return (new SlackMessage)
->content('A new invoice has been paid!')
->attachment(function ($attachment) {
$attachment->title('Invoice #1234')
->fields(['Amount' => '$99.00']);
});
}
Note
Queuing Notifications
Just like Mailables, Notifications can take time to send. To make them asynchronous,
simply implement the ShouldQueue interface in your Notification class.
Laravel will then push the delivery of each channel onto your queue.
Warning
Channel Overload
Be careful not to overwhelm users by sending the same notification through too many
channels at once. Use the via() method to implement logic (e.g., check
user preferences) to determine if they should receive an SMS and an email,
or just one of the two.
Package Development
Package development is the process of extracting reusable code into a standalone library that
can be installed via Composer. This is the "pro level" of Laravel development, allowing you
to share functionality across multiple projects or contribute to the open-source community.
Package Structure
A Laravel package typically follows a standard directory structure that mirrors a
mini-Laravel application.
| Directory/File |
Purpose |
src/ |
Contains the actual logic (Controllers, Models, Services). |
resources/ |
Contains views, lang files, and assets. |
database/ |
Contains migrations and factories. |
composer.json |
Defines dependencies and autoloading rules. |
tests/ |
Contains the package's test suite (usually using Orchestra Testbench). |
The Service Provider
The Service Provider is the connection point between your package and Laravel. It tells
Laravel how to load your package's resources.
namespace YourName\PackageName;
use Illuminate\Support\ServiceProvider;
class PackageServiceProvider extends ServiceProvider
{
public function boot()
{
// Load routes, views, and migrations
$this->loadRoutesFrom(__DIR__.'/../routes/web.php');
$this->loadViewsFrom(__DIR__.'/../resources/views', 'packagename');
$this->loadMigrationsFrom(__DIR__.'/../database/migrations');
// Publishing files for the user to customize
if ($this->app->runningInConsole()) {
$this->publishes([
__DIR__.'/../config/config.php' => config_path('packagename.php'),
], 'config');
}
}
public function register()
{
// Merge configuration or bind classes to the container
$this->mergeConfigFrom(__DIR__.'/../config/config.php', 'packagename');
}
}
Developing Locally
To develop a package alongside an existing Laravel app without publishing it to GitHub first,
you use the "path" repository feature in your main app's composer.json.
"repositories": [
{
"type": "path",
"url": "../packages/my-package"
}
],
"require": {
"yourname/package-name": "dev-main"
}
Key Considerations
| Feature |
Description |
| Namespacing |
Use a unique vendor name (e.g., Spatie,
TaylorOtwell) to avoid collisions.
|
| Config Publishing |
Always allow users to publish your config file so they can modify settings
without touching your package code. |
| View Namespacing |
Use view('packagename::view-name') to ensure Laravel doesn't
confuse your views with the app's views. |
| Testing |
Use Orchestra Testbench, which provides a "fake" Laravel
environment to run your package tests. |
Publishing to Packagist
Once your package is ready:
- Push to GitHub: Create a public repository.
- Tag a Version: Use Semantic Versioning (e.g.,
v1.0.0).
- Submit to Packagist: Go to Packagist.org and submit your GitHub URL.
- Install: Now anyone can run
composer require yourname/packagename.
Note
Discovery
In your package's composer.json, you can add an extra
section so that Laravel automatically detects and registers your Service Provider
and Facades. This is called Package Discovery.
Warning
Dependency Versioning
Be careful when defining dependencies in your package. If you require
laravel/framework: ^11.0 but a user is on Laravel 10, they won't be
able to install your package. Try to support a range of versions if possible.
Processes (Invoking CLI commands)
While Laravel's Artisan allows you to run internal commands, you often need to execute
external system commands—like optimized image compression (OptiPNG), video processing
(FFmpeg), or custom Python scripts. Laravel provides a beautiful, fluent wrapper around the
Symfony Process component to make this secure and easy.
Invoking a Process
You can use the Process facade to run a command. The result is a process object that allows
you to inspect the output, exit code, and error messages.
use Illuminate\Support\Facades\Process;
$result = Process::run('ls -la');
return $result->output();
Process Results
The result instance returned by the run method provides several helpful methods
for inspecting the output and status of the command.
| Method |
Description |
output() |
Returns the standard output (stdout) of the process. |
errorOutput() |
Returns the error output (stderr) of the process. |
exitCode() |
Returns the integer exit code (typically 0 for success). |
successful() |
Returns a boolean indicating if the exit code was 0. |
failed() |
Returns a boolean indicating if the exit code was not 0. |
Asynchronous Processes
If you need to start a process in the background without waiting for it to finish, you may
use the start method. This is useful for long-running tasks that shouldn't
block the current request.
$process = Process::start('php artisan backup:run');
while ($process->running()) {
// Do something else while the process is working...
}
$result = $process->wait();
Concurrent Processes
Laravel allows you to run multiple external commands at the same time and wait for all of
them to finish. This is significantly faster than running them one after another.
use Illuminate\Process\Pool;
$results = Process::pool(function (Pool $pool) {
$pool->path(public_path())->command('npm run build');
$pool->path(base_path())->command('php artisan migrate');
})->start()->wait();
// Check if all were successful
$results->every(fn ($result) => $result->successful());
Testing & Faking
Just like the HTTP client, you should never run real system commands during your unit tests.
Laravel allows you to "fake" the results of any process.
Process::fake([
'ffmpeg *' => Process::result('Fake Video Content', exitCode: 0),
'ls *' => Process::result('file1.txt', exitCode: 0),
]);
// This will now return "Fake Video Content" without actually running FFmpeg
$result = Process::run('ffmpeg -i input.mp4 output.avi');
Note
Security (Command Injection)
Laravel automatically escapes arguments when you pass them as an array. Always prefer
the array syntax over a single string if you are including user input to prevent
malicious "command injection" attacks.
- Safe:
Process::run(['ls', $userPath])
- Unsafe:
Process::run("ls $userPath")
Warning
TTY and Interactive Commands
Some CLI commands require a "TTY" (Terminal) to run (like those that ask for input).
While Laravel supports tty(), these can be difficult to manage in a web
environment. Whenever possible, use flags like -y or
--no-interaction to ensure commands run non-interactively.
Queues & Background Jobs
Laravel Queues allow you to defer the processing of a time-consuming task, such as sending an
email or generating a PDF, until a later time. By moving these tasks to the background, your
application can respond to user requests much faster, providing a smoother user experience.
The Queue Workflow
When a task is "queued," it is serialized and stored in a database or a specialized "broker."
A background process, known as a worker, constantly polls this storage and executes the jobs
as they arrive.
| Component |
Responsibility |
| Job |
A simple class containing the logic to be executed (e.g.,
ProcessVideo).
|
| Dispatcher |
The part of your code that pushes the job into the queue. |
| Driver/Broker |
Where the jobs are stored (Database, Redis, Amazon SQS, etc.). |
| Worker |
A persistent CLI process that pulls jobs and runs them. |
Configuration
You can configure your queue connections in config/queue.php.
| Driver |
Description |
Best For |
sync |
Executes jobs immediately (no background). |
Local development and testing. |
database |
Stores jobs in a database table. |
Simple apps without external dependencies. |
redis |
Fast, in-memory storage. |
Production standard for high-performance apps. |
sqs |
Amazon Simple Queue Service. |
High-scale, cloud-native applications. |
Creating and Dispatching Jobs
Command:
php artisan make:job ProcessVideo
This creates a class in app/Jobs implementing the ShouldQueue
interface.
Defining a Job
namespace App\Jobs;
use App\Models\Video;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
class ProcessVideo implements ShouldQueue
{
use Queueable;
public function __construct(public Video $video) {}
public function handle(): void
{
// Expensive logic: transcode video, generate thumbnails...
}
}
Dispatching a Job
use App\Jobs\ProcessVideo;
ProcessVideo::dispatch($video);
// Or with a delay
ProcessVideo::dispatch($video)->delay(now()->addMinutes(10));
Managing Workers
To start processing jobs, you must run the queue:work command. In production,
this should be kept running by a process monitor like Supervisor.
php artisan queue:work: Starts a worker process.
php artisan queue:restart: Gracefully kills workers after they finish their
current job (used during deployment).
php artisan queue:failed: Lists all jobs that failed.
php artisan queue:retry {id}: Attempts to run a failed job again.
Advanced Features
- Job Chaining: Run a sequence of jobs in order. If one fails, the rest are not executed.
Bus::chain([new ProcessVideo, new NotifyUser])->dispatch();
- Job Batches: Dispatch a set of jobs and perform an action when the whole batch is
finished.
- Rate Limiting: Ensure a job (like calling an external API) doesn't run too many times
per minute.
Note
Horizon
If you are using Redis for your queue, you should use Laravel Horizon. It provides a
beautiful dashboard and code-driven configuration for your workers, allowing you to
monitor job throughput and runtime in real-time.
Warning
Serializing Models
When a model is passed to a job, only its ID is stored in the queue. When the job is
processed, the worker re-fetches the model from the DB. Ensure the data the worker
needs still exists and hasn't changed in a way that would break the job logic.
Rate Limiting
Rate limiting is a security and performance strategy used to control the amount of incoming
traffic to your application or specific features. It prevents "brute-force" attacks on login
forms, protects your APIs from being overwhelmed by too many requests, and ensures fair
usage for all users.
How it Works
Laravel uses a "Token Bucket" or "Fixed Window" approach to track requests. Every time a user
hits a route, Laravel increments a counter associated with their IP address or User ID. If
the counter exceeds a defined threshold within a specific timeframe, Laravel returns a
429 Too Many Requests response.
Defining Rate Limiters
Rate limiters are typically defined in the boot method of your
App\Providers\AppServiceProvider (or RouteServiceProvider in older
versions). You use the RateLimiter facade to define named configurations.
| Configuration |
Example Use Case |
| Fixed |
Allow 60 requests per minute for everyone. |
| Dynamic |
Allow 100 requests for VIP users and 10 for guests. |
| Segmented |
Limit requests per IP address or per User ID. |
Example Definition:
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
RateLimiter::for('api', function (Request $request) {
return $request->user()?->is_premium
? Limit::perMinute(1000)
: Limit::perMinute(60)->by($request->ip());
});
Applying Rate Limiters to Routes
Once defined, you apply the rate limiter to routes or route groups using the throttle
middleware followed by the name of the limiter.
Route::middleware(['throttle:api'])->group(function () {
Route::get('/user', function () {
// ...
});
});
Manual Rate Limiting
Sometimes you need to rate limit logic that isn't tied to a specific route, such as a custom
command or a button click in an internal dashboard. You can interact with the RateLimiter
facade manually.
use Illuminate\Support\Facades\RateLimiter;
if (RateLimiter::tooManyAttempts('send-message:'.$user->id, $perMinute = 5)) {
return 'Too many messages sent. Please wait.';
}
RateLimiter::hit('send-message:'.$user->id);
// Perform the action...
Rate Limiter Response Headers
When a rate limiter is active, Laravel automatically attaches headers to the outgoing
response to inform the client of their current status:
| Header |
Description |
X-RateLimit-Limit |
The maximum number of requests allowed in the window. |
X-RateLimit-Remaining |
The number of requests left in the current window. |
Retry-After |
(Only on 429) The number of seconds to wait before trying again. |
Note
Cache Dependency
Rate limiting relies on your application's Cache. If your cache driver is set to
array, the rate limit will reset after every request. For production, ensure you are
using redis or database to keep the counters persistent across requests.
Warning
IP Spoofing
If your application is behind a load balancer or a service like Cloudflare,
request()->ip() might return the IP of the balancer rather than the
user. Ensure your "Trusted Proxies" are configured correctly in Laravel so the rate
limiter targets the correct individual.
String Manipulation
While we touched on string helpers in 7.9, Laravel's Fluent String Manipulation deserves its
own deep dive. It allows you to perform a sequence of string operations using a readable,
object-oriented syntax rather than nesting multiple PHP functions.
The Fluent Workflow
To begin a fluent string chain, use the Str::of() method or the
str() helper. This wraps the raw string in an
Illuminate\Support\Stringable object, giving you access to dozens of chainable
methods.
| Approach |
Syntax Example |
| Traditional (Helper) |
Str::slug(Str::limit($title, 20))
|
| Fluent (Object) |
str($title)->limit(20)->slug()
|
Common Fluent Methods
Fluent strings allow you to transform text in a "pipeline" fashion.
| Method |
Description |
Example Output |
after() |
Returns everything after a given value. |
str('user@example.com')->after('@') // "example.com" |
before() |
Returns everything before a given value. |
str('user@example.com')->before('@') // "user" |
append() / prepend() |
Adds text to the end or start. |
str('log')->prepend('sys_')->append('.txt') |
headline() |
Converts string to "Title Case" with spaces. |
str('user_first_name')->headline() // "User First Name" |
markdown() |
Converts GitHub-flavored Markdown to HTML. |
str('# Hello')->markdown() // "<h1>Hello</h1>" |
squish() |
Removes all extra whitespace. |
str(' John Doe ')->squish() // "John Doe" |
Logical & Testing Methods
You can also perform logic checks within the chain. These methods return a boolean or allow
for conditional transformations.
$isSecure = str('https://laravel.com')->startsWith('https://'); // true
$contains = str('The quick brown fox')->contains(['fox', 'dog']); // true
Conditional Transformations (when)
The when method allows you to apply a transformation only if a specific condition is met.
$string = str('laravel')
->when(true, function ($string) {
return $string->upper();
});
// "LARAVEL"
Advanced Manipulation
For complex text processing, Laravel provides methods that integrate with regular expressions
or sub-string logic.
replaceMatches(): Replaces text based on a RegEx pattern.
scan(): Parses a string into a collection based on a format (like
sscanf).
wordCount(): Returns the number of words in the string.
wrap(): Wraps the string with the given strings.
str('Laravel')->wrap('**') becomes **Laravel**.
Converting Back to a String
Once you are finished with your transformations, you can treat the object like a string (it
implements __toString()), or explicitly cast it.
$result = (string) str(' framework ')->trim()->title();
// Or use it directly in a view
<h1>{{ str($page->title)->limit(50) }}</h1>
Note
Immutability
Like Collections, Fluent Strings are immutable. Each method in the chain returns a
new Stringable instance. The original string remains unchanged unless
you re-assign the variable.
Warning
Performance
While fluent strings are much more readable, creating a Stringable
object is slightly more memory-intensive than using native PHP string functions. For
a single explode() or trim(), native PHP is fine. For 3+
transformations, use the Fluent API for better code maintenance.
Task Scheduling (Cron Jobs)
In the past, developers had to generate a Cron configuration entry for every task they wanted
to schedule on their server. This was a headache because your task schedule was no longer in
source control, and you had to SSH into your server to view or edit them.
Laravel's Command Scheduler allows you to fluently and expressively define your command
schedule within Laravel itself. Your schedule is defined in code, and only a single Cron
entry is needed on your server to run everything.
The "Heartbeat" Cron Entry
To start the scheduler, you only need to add one single Cron item to your server. This entry
calls the Laravel schedule runner every minute.
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
Defining Schedules
In modern Laravel versions, you define your scheduled tasks in the withSchedule
closure within your bootstrap/app.php file. In older versions, this was done in
the app/Console/Kernel.php file.
| Task Type |
Method |
Example |
| Artisan Command |
command() |
schedule->command('emails:send')->daily(); |
| Queued Job |
job() |
schedule->job(new Heartbeat)->everyFiveMinutes(); |
| Shell Command |
exec() |
schedule->exec('node script.js')->weekly(); |
| Closure |
call() |
schedule->call(fn() => DB::table('logs')->delete())->monthly();
|
Schedule Frequency Options
Laravel provides dozens of frequency helpers to make your schedule readable.
| Helper |
Frequency |
->everyMinute() |
Run the task every minute. |
->hourly() |
Run the task at the top of every hour. |
->dailyAt('13:00') |
Run the task every day at 1:00 PM. |
->twiceDaily(1, 13) |
Run at 1:00 AM and 1:00 PM. |
->weekdays() |
Limit the task to Monday through Friday. |
->cron('* * * * *') |
Use a custom Cron expression. |
Task Hooks & Constraints
You can further refine when a task should run using truth tests or handle what happens after
a task finishes.
->when(Closure): Run the task only if the closure returns true.
->onOneServer(): If you have multiple web servers, this ensures the task
only runs on one of them (requires Redis/Memcached).
->withoutOverlapping(): Prevents a task from running if the previous
instance is still executing.
->emailOutputTo('admin@example.com'): Automatically emails the console
output if the task produces any.
Running the Scheduler Locally
During development, you don't need to mess with your local machine's Crontab. Laravel
provides a convenient command that simulates the Cron heartbeat:
Command:
php artisan schedule:work
This command will stay running in your terminal and execute your scheduled tasks every minute
as defined in your code.
Note
Maintenance Mode
By default, scheduled tasks will not run when Laravel is in maintenance mode. If you
have a critical cleanup task that must run regardless, use the
->evenInMaintenanceMode() method when defining the schedule.
Warning
Task Timeouts
The scheduler assumes tasks finish quickly. If you have a task that takes 30 minutes
to run, it might block other tasks or cause overlap issues. Always use
->withoutOverlapping() for long-running tasks or consider dispatching
them to the Queue instead.
Getting Started (Pest & PHPUnit)
Testing is the practice of writing code to verify that your application logic works as
expected. In Laravel, testing is a first-class citizen. Every new Laravel installation comes
pre-configured with two primary testing frameworks: PHPUnit (the industry standard) and Pest
(the modern, elegant alternative).
Pest vs. PHPUnit
Laravel officially supports both. While PHPUnit uses a class-based approach, Pest offers a
functional, "domain-specific language" (DSL) that makes tests much easier to read and write.
| Feature |
PHPUnit |
Pest |
| Syntax |
Class-based ( class UserTest extends TestCase )
|
Functional ( it('does something', function() { ... }) )
|
| Readability |
Verbose; requires boilerplate. |
Minimalist; reads like English sentences. |
| Features |
Mature, standard features. |
Includes built-in architecture testing and "Expectations." |
| Compatibility |
The foundation of PHP testing. |
Runs on top of PHPUnit (you can use both in one project). |
Test Types: Unit vs. Feature
Laravel separates tests into two directories within the tests/ folder. Understanding the
difference is crucial for a healthy test suite.
| Test Type |
Location |
Scope |
Speed |
| Unit |
tests/Unit |
Tests a single function or class in isolation.
Does not load the Laravel framework or database.
|
Extremely Fast |
| Feature |
tests/Feature |
Tests a larger portion of code (e.g., a full HTTP request,
database interaction, or event dispatch).
|
Slower |
Pro Tip: If your test needs to touch the database or hit a URL, it belongs in Feature. If it
only tests a math calculation or string manipulation, it's a Unit test.
The Testing Environment
When running tests, Laravel automatically sets the configuration environment to testing
(defined in your phpunit.xml file).
- Environment Variables: You can override
.env values specifically for tests
(e.g., using an in-memory SQLite database).
- Database Refresh: Feature tests often use the
Illuminate\Foundation\Testing\RefreshDatabase trait. This wipes the
database and runs migrations before every test, ensuring a clean state.
Writing Your First Test (Pest)
If you chose Pest during your Laravel installation, a basic feature test looks like this:
// tests/Feature/ExampleTest.php
it('returns a successful response', function () {
$response = $this->get('/');
$response->assertStatus(200);
});
Running Your Tests
Laravel provides a dedicated Artisan command to run your suite. It provides a beautiful
summary of passed and failed tests.
- Run all tests:
php artisan test
- Run with coverage:
php artisan test --coverage (Requires Xdebug or PCOV)
- Filter by name:
php artisan test --filter NameOfTest
Common Assertions
Assertions are the "checks" at the end of your test. They verify that the outcome matches
your expectations.
| Assertion |
Description |
assertStatus(200) |
Checks the HTTP response code. |
assertSee('Welcome') |
Checks if the string exists in the response body. |
assertDatabaseHas('users', [...]) |
Checks if a record exists in the database. |
expect($value)->toBe(10) |
(Pest Syntax) Checks if a variable matches a value. |
Note
Parallel Testing
If your test suite grows to hundreds of tests, it may become slow. You can run tests
in parallel using php artisan test --parallel. This spins up multiple
processes to execute tests simultaneously, significantly reducing wait times.
Warning
Production Databases
Never run tests using your production database. Laravel's
RefreshDatabase trait will delete all your data. Always use a dedicated
testing database or an in-memory :memory: SQLite instance.
HTTP Tests
HTTP tests (a subset of Feature Tests) allow you to simulate a full
request-response lifecycle.
Instead of manually clicking through your app, you write code that "visits" a URL, submits
form data,
and asserts that the resulting page or JSON response is correct.
The Request-Response Flow
Laravel provides a fluent API for making requests to your application's routes. These tests
load the entire
framework, allowing you to test Middleware, Controllers, and even View rendering.
| Method |
Description |
get($url) |
Simulates a GET request. |
post($url, $data) |
Simulates a POST request with payload. |
put($url, $data) |
Simulates a PUT/PATCH request for updates. |
delete($url) |
Simulates a DELETE request. |
withHeaders($headers) |
Attaches custom headers (e.g., Accept: application/json). |
Authentication in Tests
Most of your routes will be protected by middleware. You don't need to manually fill out a
login form
in every test; you can use the actingAs method to specify which user is "logged
in"
for the duration of the request.
it('allows authenticated users to see the dashboard', function () {
$user = User::factory()->create();
$this->actingAs($user)
->get('/dashboard')
->assertStatus(200);
});
Common HTTP Assertions
Once a request is made, you use assertions to verify the response. Laravel's response object
is packed with helpful checks.
Response Status & Structure
| Assertion |
Purpose |
assertStatus(int) |
Asserts a specific HTTP code (200, 201, 403, 404, etc.). |
assertRedirect($url) |
Asserts the response is a redirect to a specific path. |
assertOk() |
Shortcut for assertStatus(200). |
assertForbidden() |
Shortcut for assertStatus(403). |
assertSee('Text') |
Checks if the string is present in the HTML/Body. |
assertDontSee('Error') |
Verifies a string is not present. |
assertViewIs('home') |
Ensures the correct Blade file was used. |
assertViewHas('user') |
Checks if specific data was passed to the view. |
Testing JSON APIs
If you are building an API, you will use specific assertions to check the JSON structure and
data returned by your endpoints.
it('returns a list of products', function () {
$response = $this->getJson('/api/products');
$response->assertStatus(200)
->assertJsonStructure([
'data' => [
'*' => ['id', 'name', 'price']
]
])
->assertJsonFragment(['name' => 'Laptop']);
});
Testing File Uploads
Testing file uploads is easy thanks to the Storage::fake and
UploadedFile::fake methods. This ensures no real files are ever written to your
disk during testing.
it('can upload an avatar', function () {
Storage::fake('avatars');
$file = UploadedFile::fake()->image('avatar.jpg');
$this->post('/profile/avatar', ['avatar' => $file]);
Storage::disk('avatars')->assertExists($file->hashName());
});
Session & Validation Errors
To test that your forms correctly validate data, you can assert that the session has errors
for specific fields.
it('requires an email to register', function () {
$this->post('/register', ['name' => 'John'])
->assertSessionHasErrors(['email']);
});
Note
Debugging Failed Tests
If a test fails and you aren't sure why, use $response->dump() or
$response->dd() (Die and Dump). This will output the full HTML or JSON
content of the response to your terminal so you can see exactly what went wrong.
Warning
CSRF Protection
Laravel automatically disables CSRF protection during tests. This allows you to make
POST requests easily. However, keep in mind that other middleware (like
custom auth or rate limiting) will still be active.
Console Tests
Console tests allow you to verify the behavior of your custom Artisan
commands. Just as HTTP tests simulate web requests, console tests simulate
terminal input and verify the output, exit codes, and side effects (like database changes)
of your CLI tools.
The Console Testing Flow
Laravel provides the artisan method to initiate a console test. You can chain
methods to provide "answers" to interactive prompts and assert that specific strings appear
in the terminal.
| Method |
Purpose |
expectsQuestion() |
Provides a mock answer to an interactive prompt. |
expectsOutput() |
Asserts that a specific string is printed to the console. |
expectsConfirmation() |
Answers a "yes/no" (Boolean) prompt. |
assertExitCode() |
Verifies the process return code (usually 0 for success). |
doesntExpectOutput() |
Ensures specific text is not displayed. |
Basic Command Test
Suppose you have a command mail:clean that asks for confirmation before deleting
old logs.
Pest Example:
it('cleans logs after user confirmation', function () {
$this->artisan('mail:clean')
->expectsConfirmation('Do you want to clean old logs?', 'yes')
->expectsOutput('Logs cleaned successfully!')
->assertExitCode(0);
// Also assert side effects
$this->assertDatabaseCount('logs', 0);
});
Handling Choices and Tables
If your command uses the choice or table methods, you can test
those specifically.
Testing Choices
$this->artisan('user:create')
->expectsChoice('Which role?', 'Admin', ['Admin', 'Editor', 'User'])
->assertExitCode(0);
Testing Tables
While there isn't a direct expectsTable method, you can use
expectsOutput to look for specific fragments of the table border or content.
$this->artisan('user:list')
->expectsOutput('Name') // Part of the table header
->expectsOutput('Jane Doe') // Part of the row data
->assertExitCode(0);
Testing Command Success/Failure
In CLI development, exit codes are the standard way to signal success or failure to other
system processes.
| Exit Code |
Meaning |
Laravel Assertion |
0 |
Success |
assertExitCode(0) or assertSuccessful() |
1 |
General Error |
assertExitCode(1) or assertFailed() |
2+ |
Specific Error |
assertExitCode(n) |
Mocking Console Components
In modern Laravel, you might use the components property for beautiful CLI
output (e.g., $this->components->info('...')). Testing these follows the same
expectsOutput logic, as Laravel captures all styled output into the same
buffer.
Note
Testing Schedule Execution
You don't usually test the Scheduler itself, but rather the commands the
scheduler runs. However, if you need to ensure a command is correctly scheduled, you
can use Bus::fake() or specialized community packages to assert that a
command was dispatched to the scheduler.
Warning
Blocking Commands
If your command contains an infinite loop or waits for external input that you
haven't mocked with expectsQuestion, your test will hang indefinitely.
Always ensure every prompt in your command has a corresponding expects
call in your test.
Browser Tests (Laravel Dusk)
While HTTP tests simulate requests at the server level, Laravel Dusk
provides an expressive, easy-to-use browser automation and testing API. It uses a real
Chrome browser (via ChromeDriver) to test your application, allowing you to
verify JavaScript, AJAX calls, and complex UI interactions that PHPUnit alone cannot see.
Why Use Dusk?
| Feature |
HTTP Tests |
Laravel Dusk |
| JavaScript |
Cannot execute JS. |
Full JS/Vue/React support. |
| Execution |
Internal (fast). |
External browser (slower). |
| Visuals |
No UI rendered. |
Real rendering (can take screenshots). |
| Best For |
APIs, Back-end logic. |
Front-end UI, User Journeys. |
Installation & Setup
Dusk is not included by default. You must install it via Composer:
1. composer require --dev laravel/dusk
2. php artisan dusk:install
This creates a tests/Browser directory and installs the latest ChromeDriver.
Writing a Browser Test
Dusk tests use a "Browser" instance to interact with your site. You can chain methods to
click links, fill forms, and wait for elements to appear.
Example: User Login Journey
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;
class LoginTest extends DuskTestCase
{
public function test_user_can_login_successfully(): void
{
$this->browse(function (Browser $browser) {
$browser->visit('/login')
->type('email', 'taylor@laravel.com')
->type('password', 'secret')
->press('Login')
->assertPathIs('/home')
->assertSee('Welcome back!');
});
}
}
Key Interaction Methods
Dusk provides a massive library of helpers to simulate user behavior.
| Method |
Action |
visit($url) |
Navigates to a specific page. |
click('#id') |
Clicks an element by selector or text. |
type('name', 'val') |
Fills an input field. |
check('id') |
Checks a checkbox. |
select('id', 'val') |
Selects an option from a dropdown. |
scrollIntoView('#id') |
Scrolls the browser to an element. |
Waiting for JavaScript
Since JS is asynchronous, Dusk allows you to "wait" for the DOM to change before continuing
the test.
$browser->click('#submit-btn')
->waitForText('Success!', 5) // Wait up to 5 seconds
->assertSee('Success!');
Debugging Tools
Testing in a browser can be tricky when things fail. Dusk includes built-in tools to help you
see what went wrong:
- Screenshots: When a test fails, Dusk automatically saves a screenshot
in
tests/Browser/screenshots.
- Console Logs: It also captures the browser's JavaScript console output
for inspection.
- Manual Screenshots: You can trigger one manually:
$browser->screenshot('filename');.
- Headless Mode: By default, Dusk runs "headless" (no visible window).
You can disable this in
DuskTestCase.php to watch the browser work in
real-time.
Note
Database Transactions
Unlike standard Feature tests, Dusk cannot use the
RefreshDatabase trait with transactions because the browser runs in a
separate process from the test. Instead, use the DatabaseMigrations
trait to ensure your DB is fresh.
Warning
Environment Configuration
Dusk requires its own environment file. Create a .env.dusk.local file to
ensure Dusk uses a dedicated test database, preventing it from overwriting your
local development data during execution.
Database Testing
Database testing ensures that your application properly stores, retrieves, and manipulates
data. Laravel provides a suite of tools to make database testing seamless, from resetting
the database state between tests to asserting that specific records exist.
Resetting the Database
To ensure each test runs in a clean environment, you should reset your database before every
test. Laravel provides traits to handle this automatically:
| Trait |
Behavior |
Best Use Case |
RefreshDatabase |
Migrates the database once, then uses transactions to reset data after each
test. |
Recommended for most feature tests. |
DatabaseMigrations |
Re-runs all migrations (Drop & Create) for every single test. |
Necessary when testing logic that involves ALTER TABLE or
specific schema changes. |
DatabaseTransactions |
Wraps the test in a transaction and rolls it back. |
Fast, but does not handle auto-increment resets or schema changes. |
Using Model Factories
Instead of manually creating arrays for your database records, use Model
Factories to generate "fake" data. This keeps your tests clean and resilient to
schema changes.
it('calculates the total price of an order', function () {
// Create a user with 3 related orders
$user = User::factory()
->has(Order::factory()->count(3), 'orders')
->create();
expect($user->orders)->toHaveCount(3);
});
Database Assertions
Once your logic has executed, you need to verify the state of the database. Laravel's
TestCase includes several specialized assertions:
| Assertion |
Description |
assertDatabaseHas($table, $data) |
Asserts that a row matching the data exists. |
assertDatabaseMissing($table, $data) |
Asserts that a row matching the data does not exist. |
assertDatabaseCount($table, $count) |
Asserts the total number of rows in a table. |
assertSoftDeleted($model) |
Asserts that the given model has been soft deleted. |
assertModelExists($model) |
Asserts the model exists in the database. |
Example: Deleting a Product
it('can delete a product', function () {
$product = Product::factory()->create();
$this->delete("/products/{$product->id}");
$this->assertDatabaseMissing('products', [
'id' => $product->id,
]);
});
Testing with SQLite (In-Memory)
For maximum speed, many developers use an in-memory SQLite database for testing. This lives
entirely in RAM and is destroyed the moment the test finishes.
Configuration (phpunit.xml):
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
Seeders in Tests
Sometimes your tests require "static" data (like a list of countries or roles) to be
present. You can run specific seeders within your test's setUp method or the
test itself:
beforeEach(function () {
$this->seed(RoleSeeder::class);
});
Note
Testing Relationships
When asserting relationships, remember that models are cached in memory. If you
update a relationship in your controller and then check $user->posts in
your test, it might show the old data. Use $user->refresh() to reload
the model and its relationships from the database.
Warning
Database Truncation
If you are using a real database (like MySQL) for testing and you stop a test
midway, the RefreshDatabase transaction might not roll back. This can
leave "junk" data in your tables. Running php artisan migrate:fresh
will fix this.
Mocking
Mocking is the practice of replacing a real component of your application with a "fake"
version that simulates its behavior. This is essential for testing code that interacts with
external services (like APIs or payment gateways) or slow processes (like file uploads),
ensuring your tests remain fast, predictable, and isolated.
Why Mock?
| Scenario |
Real Component Problem |
Mocking Solution |
| External APIs |
Network dependency, rate limits, costs. |
Return a hardcoded JSON response instantly. |
| Payment Gateways |
Risk of real transactions, slow. |
Simulate a "Success" or "Declined" token. |
| Time/Date |
Tests might fail depending on when they run. |
"Freeze" time at a specific second. |
| Email/SMS |
Sending real spam to test addresses. |
Assert that an email would have been sent. |
Faking Laravel Services
Laravel provides built-in "fakes" for its most common services. These allow you to intercept
actions and make assertions without actually executing the logic.
The Mail Fake
use Illuminate\Support\Facades\Mail;
use App\Mail\OrderShipped;
it('sends an order shipment email', function () {
Mail::fake();
// Perform action...
OrderShipped::dispatch($order);
// Assert a mailable was sent
Mail::assertSent(OrderShipped::class);
// Assert a mailable was NOT sent
Mail::assertNotSent(AnotherMail::class);
});
The Event & Queue Fakes
Useful for ensuring that side effects are triggered without actually running the listeners or
background jobs.
| Fake |
Assertion Example |
Event::fake() |
Event::assertDispatched(UserRegistered::class); |
Queue::fake() |
Queue::assertPushed(ProcessPodcast::class); |
Notification::fake() |
Notification::assertSentTo($user, InvoicePaid::class); |
The HTTP Client Fake
As discussed in 7.10, the HTTP client can be faked to avoid making real network requests. You
can even sequence responses to simulate a complex API conversation.
Http::fake([
'github.com/*' => Http::response(['id' => 1], 200),
'api.stripe.com/*' => Http::sequence()
->push(['status' => 'pending'])
->push(['status' => 'success']),
]);
Mocking Custom Objects (Mockery)
For your own internal classes or third-party libraries that don't have built-in Laravel
fakes, Laravel integrates with Mockery.
it('calls the price calculator service', function () {
$mock = $this->mock(PriceCalculator::class, function ($mock) {
$mock->shouldReceive('calculate')
->once()
->with(100)
->andReturn(120);
});
// When the app pulls PriceCalculator from the container,
// it gets this mock instead of the real class.
$response = $this->get('/calculate/100');
$response->assertSee('120');
});
Time Travel
Testing logic that depends on time (e.g., "trial expires in 30 days") can be difficult.
Laravel allows you to "travel" to the future or "freeze" the current time.
// Travel to the future
$this->travel(30)->days();
expect(now()->isFuture())->toBeTrue();
// Travel back to the present
$this->travelBack();
// Freeze time at a specific moment
$this->travelTo(now()->startOfYear());
Note
Mocking Facades
You can mock any Laravel Facade directly using the shouldReceive method.
For example:
Cache::shouldReceive('get')->once()->with('key')->andReturn('value');.
Warning
Over-Mocking
Avoid mocking everything. If you mock your Models, Repositories, and Services all at
once, you aren't testing your application—you're just testing your mocks. Use
Feature Tests with a real database whenever possible, and save mocking for external
boundaries.
Breeze & Jetstream (Auth Scaffolding)
Laravel provides two primary "starter kits" to jumpstart your application's authentication
and frontend structure. Instead of spending hours building login forms, password resets, and
email verification, you can install a scaffold that provides a professional, secure starting
point.
Comparing the Kits
Choosing between Breeze and Jetstream depends on the complexity of your application and your
familiarity with specific frontend stacks.
| Feature |
Laravel Breeze |
Laravel Jetstream |
| Complexity |
Simple, minimal, easy to customize. |
Advanced, feature-rich, opinionated. |
| Styling |
Tailwind CSS. |
Tailwind CSS. |
| Stacks |
Blade, Vue, or React. |
Livewire or Inertia.js. |
| Auth Features |
Login, Register, Password Reset. |
Login, Register, Two-Factor Auth (2FA). |
| Profile |
Basic profile page. |
API Tokens, Profile Photos, Account Deletion. |
| Teams |
No. |
Yes (Built-in team management). |
Laravel Breeze
Breeze is the "entry-level" starter kit. It is designed to be a minimal and simple starting
point for building a Laravel application with authentication. It is the perfect choice if
you want full control over your views and don't need advanced features like 2FA or team
management out of the box.
- Best For: Small to medium projects, learning Laravel, or developers who
want to write their own custom logic on top of a clean base.
- Installation:
composer require laravel/breeze --dev
php artisan breeze:install
Laravel Jetstream
Jetstream is a more robust application scaffolding for Laravel. It provides the
implementation for more complex features that modern applications require.
Key Advanced Features:
- Two-Factor Authentication: Uses a QR code and recovery codes via the
Fortify engine.
- API Support: Integrated with Laravel Sanctum, allowing
users to generate API tokens with specific permissions (abilities).
- Team Management: Allows users to create and join teams, manage team
roles, and switch between team contexts.
- Browser Sessions: Allows users to view and logout of their active
sessions on other devices.
- Best For: Enterprise SaaS applications, projects requiring high
security (2FA), or apps that revolve around "Teams."
- Installation:
composer require laravel/jetstream
php artisan jetstream:install livewire (or inertia)
The "Stack" Choice
When installing these kits, you will often have to choose between Livewire
and Inertia.js:
- Livewire: Allows you to build dynamic interfaces using mostly PHP. It
feels very "Laravel-native."
- Inertia.js: Allows you to build single-page apps (SPAs) using
Vue or React, but keeps the routing and controllers
inside Laravel.
Summary of Ecosystem Tools
| Engine |
Responsibility |
| Laravel Fortify |
A frontend-agnostic authentication backend (logic only). |
| Laravel Sanctum |
Provides a featherweight authentication system for SPAs and simple APIs.
|
Note
Customization
Once installed, the code for Breeze and Jetstream is published directly into your
application. You are encouraged to modify the controllers, views, and components to
match your specific needs. They are not "locked" libraries.
Warning
Choosing Too Early
Switching from Breeze to Jetstream mid-project can be difficult because they have
different directory structures and dependencies. It is better to start with Breeze
if you are unsure; it is easier to add features to Breeze than it is to strip
complexity out of Jetstream.
Cashier (Stripe/Paddle Subscriptions)
Laravel Cashier provides an expressive, fluent interface to subscription
billing services through Stripe or Paddle. It handles
almost all of the "boilerplate" billing code you would otherwise have to write manually,
including managing subscriptions, handling coupons, swapping subscription quantities, and
even generating PDF invoices.
Stripe vs. Paddle
Laravel offers two separate versions of Cashier depending on which payment provider you
choose.
| Feature |
Cashier (Stripe) |
Cashier (Paddle) |
| Model |
Direct payment processor. |
Merchant of Record (MoR). |
| Sales Tax |
You must calculate/collect via Stripe Tax. |
Paddle handles global sales tax/VAT for you. |
| Control |
High control over the checkout experience. |
Paddle manages the checkout "overlay." |
| Complexity |
Requires more manual tax/compliance setup. |
Simpler for international compliance. |
Core Concepts
Cashier works by adding a Billable trait to your User model, which
gives you access to various billing methods.
Subscribing a User
To create a subscription, you typically first create a "Setup Intent" or redirect the user to
a pre-built Stripe Checkout page.
// Creating a subscription with a specific price ID from Stripe
$user->newSubscription('default', 'price_premium_monthly')->create($paymentMethod);
Checking Subscription Status
You can easily verify if a user is "pro" or has access to specific features.
if ($user->subscribed('default')) {
// User is an active subscriber
}
if ($user->subscribedToPrice('price_premium_monthly', 'default')) {
// User is on a specific plan
}
Handling Common Billing Tasks
Cashier simplifies complex billing flows into single method calls:
| Task |
Method Example |
| Swap Plans |
$user->subscription('default')->swap('new-price-id'); |
| Cancel |
$user->subscription('default')->cancel(); |
| Resume |
$user->subscription('default')->resume(); |
| Check Grace Period |
$user->subscription('default')->onGracePeriod(); |
| Invoice PDF |
$user->downloadInvoice($invoiceId, ['vendor' => 'My App']);
|
Webhooks & Security
Since payments happen on Stripe/Paddle's servers, they need a way to tell your app when a
payment succeeds or a subscription is canceled. This is done via Webhooks.
- Laravel Hook: Cashier includes a built-in controller to handle these
incoming signals.
- Protection: Cashier automatically verifies the webhook signature to
ensure the request actually came from Stripe/Paddle and not a malicious actor.
Invoices and Receipts
Cashier allows you to easily retrieve a collection of a user's invoices so they can be
displayed in their account settings:
<table>
@foreach ($user->invoices() as $invoice)
<tr>
<td>{{ $invoice->date()->toFormattedDateString() }}</td>
<td>{{ $invoice->total() }}</td>
<td><a href="/user/invoice/{{ $invoice->id }}">Download</a></td>
</tr>
@endforeach
</table>
Note
Stripe Tax
For Stripe users, Cashier integrates with Stripe Tax, which can
automatically calculate the tax for your customers based on their location during
the checkout process.
Warning
Local Testing
Webhooks cannot reach your local machine because your localhost is not
accessible from the internet. You must use a tool like the Stripe CLI
(stripe listen --forward-to...) or a service like Ngrok to "tunnel" the
webhooks to your local development environment.
Echo (Frontend WebSockets)
Laravel Echo is a JavaScript library that makes it painless to subscribe to
channels and listen for events broadcast by Laravel. It works hand-in-hand with a WebSocket
"broadcaster" to bring real-time, interactive features—like live chat, notifications, and
status updates—to your application.
The Real-Time Stack
To use Echo, you need three main components working together:
| Component |
Role |
| Event |
A standard Laravel event class that implements the
ShouldBroadcast interface.
|
| Broadcaster |
The "driver" that pushes the event (e.g., Reverb, Pusher, or Ably). |
| Echo (Client) |
The JavaScript library on the frontend that "listens" for the broadcast.
|
Broadcasting Drivers
| Driver |
Description |
Best For |
| Laravel Reverb |
A first-party, high-performance WebSocket server written in PHP. |
Recommended for modern apps wanting full control and speed.
|
| Pusher / Ably |
Managed cloud services. No server setup required. |
Apps that prefer not to manage their own WebSocket infrastructure. |
| Redis / Socket.io |
A community-driven approach using Node.js. |
Legacy setups or highly custom Socket.io requirements. |
Channel Types
Not all data should be public. Laravel Echo supports three distinct types of channels:
| Channel Type |
Description |
Use Case |
| Public |
Anyone can listen; no authorization required. |
Live stock tickers, public wall posts. |
| Private |
Requires user authentication and authorization logic. |
Private messages, user-specific notifications. |
| Presence |
Like Private, but also tracks who else is on the channel. |
"Who is online" lists, "User is typing..." indicators. |
Broadcasting an Event
First, ensure your event implements ShouldBroadcast. Laravel will automatically
detect this and push the event to your queue (or broadcast immediately).
namespace App\Events;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
class OrderStatusUpdated implements ShouldBroadcast
{
use Dispatchable;
public function __construct(public $order) {}
// Define which channel the event should be sent to
public function broadcastOn(): Channel
{
return new PrivateChannel('orders.'.$this->order->id);
}
}
Listening with Echo
Once the event is dispatched from the server, you "catch" it in your JavaScript using Echo.
import Echo from 'laravel-echo';
// Listening on a private channel
Echo.private(`orders.${orderId}`)
.listen('OrderStatusUpdated', (e) => {
console.log('New Status:', e.order.status);
});
Presence Channels
Presence channels allow you to see a list of users currently viewing a page. This is
incredibly powerful for collaborative tools.
Echo.join(`chat.${roomId}`)
.here((users) => {
// Runs when you first join; returns all users present
})
.joining((user) => {
// Runs when a new user joins
})
.leaving((user) => {
// Runs when a user leaves
});
Note
Client-to-Client (Whisper)
Sometimes you want to send small updates between users without hitting the server
(like a "typing" indicator). Echo allows you to "Whisper" these events:
channel.whisper('typing', { name: 'John' });
Warning
Security & Authorization
For Private and Presence channels, you must define authorization rules in your
routes/channels.php file. If you forget this, Echo will fail to connect
because it cannot verify the user has permission to hear the data.
Envoy (Remote Task Execution)
Laravel Envoy is a tool for executing common tasks you run on your remote
servers. It uses a clean, minimal syntax—similar to Blade—to define "tasks" for deployment,
database migrations, and server maintenance.
Under the hood, Envoy uses SSH to connect to your servers, so you don't need to install
anything on the remote machines except for an SSH key.
Installation
Envoy is a standalone tool installed globally via Composer:
composer global require laravel/envoy
The Envoy.blade.php File
All of your tasks are defined in an Envoy.blade.php file in your project's root.
This file can include @servers declarations, @task definitions,
and even PHP logic.
| Directive |
Purpose |
@servers |
Defines the IP addresses or hostnames of your remote servers. |
@task |
Contains the Bash commands to be executed on the servers. |
@story |
Groups multiple tasks into a single execution sequence. |
@setup |
Allows you to define variables and PHP code before tasks run. |
Defining Tasks and Stories
Envoy allows you to run commands on multiple servers simultaneously or in sequence.
@servers(['web' => 'user@192.168.1.1', 'db' => 'user@192.168.1.2'])
@task('deploy', ['on' => 'web'])
cd /var/www/html
git pull origin main
composer install --no-interaction --quiet --optimize-autoloader
php artisan migrate --force
@endtask
@story('release')
deploy
optimize_stuff
@endstory
Key Features
| Feature |
Description |
| Parallel Execution |
Run a task on all defined servers at once using the parallel
option. |
| Confirmations |
Use confirm to prevent accidental execution:
@task('deploy', ['on' => 'web', 'confirm' => true]).
|
| Notifications |
Automatically send alerts to Slack,
Discord, or Telegram after a task
completes.
|
| Variables |
Pass data into your tasks via the command line:
envoy run deploy --branch=master.
|
Running Tasks
To execute a task or story, use the run command from your terminal:
- Run a single task:
envoy run deploy
- Run with a variable:
envoy run deploy --branch=develop
Envoy vs. Laravel Forge
| Tool |
Approach |
Best For |
| Envoy |
Code-driven, manual execution. |
Custom deployment scripts and quick server tweaks. |
| Forge |
UI-driven, automated. |
Server provisioning and "Push to Deploy" automation. |
Note
SSH Key Auth
Envoy requires SSH Key Authentication to function. It cannot handle
interactive password prompts. Ensure your local public key is added to the
~/.ssh/authorized_keys file on your remote server before running Envoy.
Warning
Security
Avoid hardcoding sensitive credentials (like database passwords) directly in your
Envoy.blade.php file. Instead, use environment variables on the remote
server or pass them securely as command-line arguments.
Fortify (Headless Authentication)
Laravel Fortify is a frontend-agnostic authentication backend implementation
for Laravel. It provides all the logic required to build secure authentication features—like
login, registration, password resets, and two-factor authentication—without providing the
actual UI (views).
While tools like Breeze and Jetstream use Fortify under the
hood, you can use Fortify directly if you want to build a completely custom frontend (using
Vue, React, or standard Blade) while keeping the robust security logic of the Laravel
ecosystem.
What Fortify Handles
Fortify takes care of the "invisible" work. When you submit a login form to a Fortify route,
it handles throttling, user validation, and session management automatically.
| Feature |
Responsibility |
| Registration |
Validates new users and stores them in the database. |
| Authentication |
Handles login attempts, session persistence, and logout. |
| Password Reset |
Generates secure tokens and handles email links. |
| Two-Factor Auth |
Generates QR codes and verifies TOTP (Time-based One-Time Password) codes.
|
| Email Verification |
Ensures users verify their email addresses before accessing routes. |
The "Actions" Pattern
Fortify doesn't use standard controllers that you can edit. Instead, it uses
Actions. When you install Fortify, several action classes are published to
app/Actions/Fortify. You can modify these classes to change how users are
validated or created.
Example: CreateNewUser.php
public function create(array $input): User
{
Validator::make($input, [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:User'],
'password' => $this->passwordRules(),
])->validate();
return User::create([
'name' => $input['name'],
'email' => $input['email'],
'password' => Hash::make($input['password']),
]);
}
Configuration and Features
You can enable or disable specific features in the config/fortify.php
configuration file.
| Feature |
Configuration Key |
| Registration |
Features::registration() |
| Reset Passwords |
Features::resetPasswords() |
| Email Verification |
Features::emailVerification() |
| Update Profiles |
Features::updateProfileInformation() |
| Two-Factor Auth |
Features::twoFactorAuthentication(['confirm' => true]) |
Defining the Views
Because Fortify is headless, you must tell it which Blade templates to show when a user
visits /login or /register. You do this in the boot
method of your App\Providers\FortifyServiceProvider.
use Laravel\Fortify\Fortify;
Fortify::loginView(function () {
return view('auth.login');
});
Fortify::registerView(function () {
return view('auth.register');
});
Security Features
Fortify is built with security as the priority:
- Rate Limiting: Automatically throttles login attempts per IP and
username to prevent brute-force attacks.
- Password Validation: Enforces secure password requirements (length,
uppercase, symbols).
- Session Hijacking Protection: Provides logic for "confirming" passwords
before sensitive actions.
Note
Fortify vs. Passport/Sanctum
Fortify is designed for Session-based authentication (standard web
apps). If you are building a mobile app or a strictly stateless API, you should use
Laravel Sanctum or Laravel Passport instead.
Warning
Service Provider Registration
After installing Fortify, you must manually register the
FortifyServiceProvider in your bootstrap/providers.php
file (or config/app.php in older versions). Without this, the
authentication routes will not exist.
Horizon (Redis Queue Dashboard)
Laravel Horizon provides a beautiful, real-time dashboard and code-driven
configuration for your Laravel-powered Redis queues. It allows you to easily monitor key
metrics of your queue system, such as job throughput, runtime, and job failures.
While standard Laravel queues can run using simple CLI commands, Horizon acts as a
"super-manager" for your queue workers, ensuring they stay running and scale according to
your needs.
Key Features
| Feature |
Description |
| Real-Time Dashboard |
A web-based UI to see everything happening in your queues. |
| Code-Driven Config |
Configure your workers in a PHP file (config/horizon.php)
rather than server-side scripts. |
| Auto-Scaling |
Horizon can automatically spin up more worker processes when a queue gets
busy and kill them when it's quiet. |
| Job Metrics |
Detailed analytics on how long jobs take to run and which jobs fail most
often. |
| Failed Job Management |
Easily view, retry, or discard failed jobs directly from the UI. |
Configuration
Instead of managing queue:work processes via Supervisor manually for every
queue, you define "environments" in config/horizon.php.
'environments' => [
'production' => [
'supervisor-1' => [
'connection' => 'redis',
'queue' => ['default', 'notifications'],
'balance' => 'auto', // Options: simple, auto, or null
'maxProcesses' => 10,
'tries' => 3,
],
],
],
Balancing Strategies
Horizon can manage how processes are distributed across your queues:
null (Simple): Fixed number of processes for each queue.
simple: Splits processes evenly between the queues.
auto: The most powerful mode. It shifts worker processes
to whichever queue has the most "backlog" (waiting jobs).
Monitoring & Tags
Horizon allows you to "tag" jobs so you can search for them in the dashboard. By default, if
a job is associated with an Eloquent model, Horizon automatically tags it with that model's
class and ID.
// In the dashboard, you can search for "App\Models\Order:1"
// to see every job that processed that specific order.
Installation & Execution
- Install:
composer require laravel/horizon
- Publish Assets:
php artisan horizon:install
- Run:
php artisan horizon
In a production environment, you should use a process monitor like
Supervisor to keep the php artisan horizon command running
indefinitely.
Note
Redis Requirement
Horizon is designed specifically for Redis. It will not work with
database, sqs, or beanstalkd queue drivers.
Warning
Dashboard Security
By default, the Horizon dashboard is only accessible in the local
environment. To access it in production, you must define an authorization gate in
your App\Providers\HorizonServiceProvider to restrict access to
specific users or email addresses.
Octane (High-Performance Server)
Laravel Octane supercharges your application's performance by serving it
using high-powered application servers like FrankenPHP, Swoole, or
RoadRunner.
While traditional PHP follows a "shared-nothing" architecture—where the entire framework is
booted and destroyed for every single request—Octane boots the framework
once, keeps it in memory (RAM), and then feeds requests to it at lightning
speed.
Performance Comparison
| Feature |
Traditional (PHP-FPM) |
Laravel Octane |
| Bootstrapping |
Every single request. |
Once (at server start). |
| Response Time |
Faster than most, but has overhead. |
Extremely Low (often <10ms). |
| Memory Usage |
High (boots app repeatedly). |
Efficient (shared memory). |
| Concurrency |
Limited by process count. |
Handles thousands of concurrent hits. |
Supported Servers
| Server |
Key Strength |
| FrankenPHP |
Modern, built on Go, supports early hints and 103 HTTP status codes. |
| Swoole |
Massive concurrency, supports coroutines and high-level async tasks. |
| RoadRunner |
Stable, written in Go, works well as a binary outside of PHP. |
The "Stateless" Rule
Because Octane keeps your application in memory, you must be careful about
State. In a traditional app, global variables reset every request. In
Octane, if you set a variable in a Singleton or a Static property, it stays
there for the next user.
- Bad: Storing user-specific data in a static property.
- Good: Relying on Laravel's Container to reset services (Octane handles
most of this for you).
Octane-Specific Features
Octane provides additional tools to take advantage of the persistent memory environment:
Concurrent Tasks
You can run multiple operations at the exact same time and wait for all of them to finish.
[$users, $orders] = Octane::concurrently([
fn () => User::all(),
fn () => Order::all(),
]);
Octane Cache
A specialized, ultra-fast cache driver that stores data in the server's memory rather than
hitting Redis or a database.
Octane::cache()->put('stats', $stats, 10);
Installation
- Install:
composer require laravel/octane
- Install Server:
php artisan octane:install (Choose
FrankenPHP, Swoole, or RoadRunner)
- Start:
php artisan octane:start
Note
Dependency Injection
If you inject the Request object into a Singleton's constructor, that
singleton will be "stuck" with the first request that ever hit the server.
Always resolve the request within methods or use a Closure to ensure
you are getting the current user's request.
Warning
Memory Leaks
Since the process never dies, a memory leak (like adding items to a global array
indefinitely) will eventually crash your server. Octane includes a
--max-requests flag to automatically restart workers after they handle
a certain amount of traffic to mitigate this.
Pennant (Feature Flags)
Laravel Pennant is a simple, lightweight library for managing
Feature Flags. Feature flags allow you to roll out new application features
with confidence by decoupling code deployment from feature release. You can deploy code to
production but keep the feature "hidden" or only visible to specific users.
Why Use Feature Flags?
| Benefit |
Description |
| Gradual Rollouts |
Enable a feature for 10% of users to test stability before a full release.
|
| Beta Testing |
Grant access to new tools only to users with a beta-tester tag.
|
| Kill Switch |
Instantly disable a buggy feature in production without a new deployment.
|
| A/B Testing |
Show different versions of a feature to different segments to measure
engagement. |
Defining Features
You typically define features in the boot method of your
AppServiceProvider. Features can be simple "on/off" switches or return complex
values.
use Laravel\Pennant\Feature;
use App\Models\User;
// A simple boolean feature
Feature::define('new-onboarding-flow', function (User $user) {
return $user->created_at->gt('2025-01-01');
});
// A feature with multiple values (A/B testing)
Feature::define('purchase-button-color', function (User $user) {
return Lottery::odds(1, 2)->choose(['red', 'blue']);
});
Checking Features
You can check the status of a feature in your Controllers, Blade templates, or Middleware.
In PHP Logic
if (Feature::active('new-onboarding-flow')) {
// Show the new experience
}
In Blade Templates
@feature('new-onboarding-flow')
<x-new-header />
@else
<x-old-header />
@endfeature
Storage Drivers
Pennant needs to remember which users have which features active so the experience stays
consistent for them.
| Driver |
Best For |
database |
Persistent storage. Recommended for production so flags
survive cache clears. |
array |
Testing. Resets every request; very fast. |
Advanced Usage
- Scope: Features are usually scoped to the authenticated
User, but you can scope them to a Team, Project,
or even an IP address.
- Rich Values: Instead of just
true / false, a
feature can return strings, arrays, or integers (e.g., a feature flag that determines
the "Max File Upload Size" for a specific tier).
- Pre-Caching: To avoid N+1 queries when checking features for a list of
users, you can use
Feature::load(['feature-name']).
Managing via CLI
Pennant includes Artisan commands to manage flags manually from the terminal:
- Activate for all:
php artisan pnn:activate feature-name
- Deactivate for all:
php artisan pnn:deactivate feature-name
- Purge old flags:
php artisan pnn:purge feature-name
Note
Consistency
Once a feature is resolved for a specific user, Pennant "pins" that value in the
database. Even if you change the logic in your code, the user will keep the value
they first received until the feature is purged or re-resolved.
Warning
Technical Debt
Feature flags are powerful but can lead to "conditional hell" if left in the codebase
too long. Once a feature is fully rolled out to 100% of users, make sure to delete
the flag logic and the @feature tags to keep your code clean.
Pulse (Health & Performance Monitoring)
Laravel Pulse is a first-party, real-time application performance monitoring
(APM) tool and dashboard. Unlike Laravel Telescope, which is designed for local debugging
and deep inspection of individual requests, Pulse is built for production
environment monitoring. It provides high-level aggregate insights into your
application's health, bottlenecks, and usage patterns.
At-A-Glance Insights
Pulse uses a card-based UI to display critical data about your stack. It is designed to be
lightweight enough to run directly on your production servers with minimal overhead.
| Monitor Card |
Description |
| Server Stats |
Real-time CPU, memory, and disk usage for one or multiple servers. |
| Application Usage |
Identifies the most active users, users experiencing the slowest endpoints,
and those dispatching the most jobs. |
| Exceptions |
Aggregates and tracks recurring exceptions, helping you spot anomalies in
production. |
| Slow Queries |
Lists the slowest database queries and identifies the exact line of code
that triggered them. |
| Queue Monitoring |
Visualizes throughput, pending jobs, and failure rates for your background
tasks. |
| Slow Outgoing Requests |
Tracks slow external API calls made via the Laravel HTTP client. |
Performance-First Architecture
Pulse is built to handle heavy production workloads using several strategies to ensure it
doesn't slow down your application:
- Minimal Data Storage: It stores only the essential aggregate data
needed for its charts.
- Storage Drivers: By default, Pulse uses your existing database
(MySQL/PostgreSQL), but for high-traffic sites, it can be configured to use
Redis Ingest.
- Sampling: You can enable sampling (e.g., only record 10% of requests)
to reduce the database write frequency on extremely busy applications.
Installation & Configuration
- Install:
composer require laravel/pulse
- Setup:
php artisan vendor:publish --provider="Laravel\Pulse\PulseServiceProvider"
- Migrate:
php artisan migrate
To enable server monitoring, you must run the following command on each of your application
servers (usually managed by Supervisor):
php artisan pulse:check
Customizing the Dashboard
Pulse is highly extensible because the dashboard is built with Livewire. You
can easily reorder, remove, or even create your own custom cards to track business-specific
metrics.
// Creating a custom card starts by extending the Pulse Card component
use Laravel\Pulse\Livewire\Card;
class TopSellers extends Card {
public function render() {
// Query your data and return a view
}
}
Note
Authorization
In production, the /pulse dashboard is locked by default. You must
authorize users by defining the viewPulse gate in your
App\Providers\AppServiceProvider to ensure only your team can see your
performance data.
Warning
Data Trimming
Pulse data can grow quickly in the database. Laravel provides a
pulse:check command that handles periodic data trimming, but you should
ensure this command is running as a daemon to prevent your
pulse_entries table from bloating your storage.
Sail (Docker Development Environment)
Laravel Sail is a light-weight command-line interface for interacting with
Laravel's default Docker development environment. It allows you to build
and run a professional-grade Laravel application stack—including MySQL, Redis, Meilisearch,
and more—without requiring you to install any of these software packages on your local
machine.
If you have Docker installed, Sail takes care of the rest, ensuring that every developer on
your team is working in an identical environment.
The Sail Stack
Sail uses a docker-compose.yml file (located in your root directory) to define
the "services" your application needs.
| Service |
Purpose |
laravel.test |
The main PHP container (running PHP 8.x, Composer, and Node.js). |
mysql |
The database engine for your application. |
redis |
Used for caching, sessions, and queues. |
mailpit |
A local mail server and UI to intercept and view outgoing emails. |
selenium |
Used for running browser tests (Laravel Dusk). |
Getting Started
If you are creating a new app, Sail is often installed by default. To add it to an existing
project:
- Install:
composer require laravel/sail --dev
- Configure:
php artisan sail:install (Select the services
you want, like MySQL or Redis).
- Launch:
./vendor/bin/sail up (Use -d to run
in the background).
Common Sail Commands
Since Sail wraps Docker, you use it to run commands "inside" the container. A good rule of
thumb: if you usually run a command with php artisan, you now run it with
sail artisan.
| Task |
Command |
| Start Services |
sail up |
| Stop Services |
sail stop |
| Artisan Command |
sail artisan migrate |
| Composer |
sail composer install |
| Node / NPM |
sail npm run dev |
| Running Tests |
sail test |
Pro Tip
Add alias sail='[ -f sail ] && sh sail || sh vendor/bin/sail' to your
shell configuration (like .zshrc or .bashrc) so you can
just type sail instead of the full path.
Customizing your Environment
- PHP Version: You can change the PHP version by modifying the
build context in your docker-compose.yml and rebuilding the
image.
- Dockerfiles: If you need to install specific Linux packages (like
libpng for image processing), you can "publish" the Sail Dockerfiles using
php artisan sail:publish and edit them directly.
- Devcontainers: Sail includes support for VS Code
Devcontainers, allowing you to run your entire IDE inside the Docker
container for a seamless experience.
Sharing Your Site
Sail includes a built-in "Share" feature (powered by Expose). This allows you to generate a
public URL for your local site so you can show your progress to a client or test webhooks.
Command: sail share
Note
Performance on Windows/Mac
For the best performance on Windows, you must use WSL2 (Windows
Subsystem for Linux) and keep your project files within the Linux filesystem (e.g.,
~/code/my-app), not the Windows filesystem (/mnt/c/...).
Warning
Port Conflicts
If you already have MySQL or Redis running natively on your computer, Sail might
fail to start because the ports (3306 or 6379) are already taken. You can change the
ports Sail uses by setting variables like FORWARD_DB_PORT=3307 in your
.env file.
Sanctum (API Token Authentication)
Laravel Sanctum provides a featherweight authentication system for Single
Page Applications (SPAs), mobile applications, and simple, token-based APIs. It is the go-to
choice for developers who need more than basic session authentication but don't require the
full complexity of OAuth2 provided by Laravel Passport.
How Sanctum Works
Sanctum solves two primary problems: API Token Authentication and
SPA Session Authentication.
| Authentication Method |
Ideal Use Case |
Mechanism |
| API Tokens |
Mobile Apps, Third-party scripts, Server-to-server. |
Bearer tokens sent in the Authorization header. |
| SPA Cookie-based |
Vue, React, or Next.js apps on the same domain. |
Secure, HttpOnly cookies (prevents XSS/CSRF). |
Issuing API Tokens
To issue tokens, your User model must use the HasApiTokens trait.
This allows users to generate multiple tokens with specific "abilities" (permissions).
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
}
Creating a Token
$token = $user->createToken('mobile-app', ['server:update'])->plainTextToken;
// Return this string to your mobile app/client
return response()->json(['token' => $token]);
Protecting Routes
To protect your API routes, use the auth:sanctum middleware. It automatically
checks for either a valid cookie or a valid Bearer token.
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
Token Abilities (Permissions)
Sanctum allows you to limit what a token can do. This is great for giving a third-party
script access to "read" data but not "delete" it.
| Task |
Code Example |
| Check Ability |
$request->user()->tokenCan('server:update') |
| Revoke Token |
$user->tokens()->where('id', $id)->delete(); |
| Revoke All |
$user->tokens()->delete(); |
SPA Authentication (CSRF Protection)
When building an SPA (like a Nuxt or React app) that lives on a subdomain of your API (e.g.,
app.myapp.com and api.myapp.com), Sanctum uses Laravel's built-in
cookie-based session authentication.
- Initialize CSRF: The SPA makes a GET request to
/sanctum/csrf-cookie.
- Login: The SPA sends credentials to
/login.
- Encrypted Cookie: Laravel sets a secure cookie. Subsequent requests are
authenticated as long as the cookie is sent.
Sanctum vs. Passport
| Feature |
Sanctum |
Passport |
| Complexity |
Simple / Minimalist. |
Complex / Robust. |
| OAuth2 Support |
No. |
Yes (Full implementation). |
| Architecture |
Token or Session based. |
OAuth2 Grant types (Authorization Code, etc.). |
| Best For |
Internal APIs, Mobile apps. |
Large scale APIs used by external developers. |
Note
Statefulness
For SPA authentication to work, you must ensure your SESSION_DOMAIN and
SANCTUM_STATEFUL_DOMAINS are correctly configured in your
.env file to allow the cookie to be shared between your frontend and
backend.
Warning
Token Storage
When using API Tokens in a mobile or desktop app, store them securely (e.g.,
Keychain on iOS or EncryptedSharedPreferences on Android). In a web browser, do not
store plain text tokens in localStorage as they are vulnerable to XSS
attacks; use the SPA cookie-based method instead.
Scout (Full-Text Search)
Laravel Scout provides a driver-based solution for adding full-text search
to your Eloquent models. Instead of writing complex SQL LIKE queries that are
slow and lack "fuzzy" matching, Scout automatically synchronizes your data with a search
engine index, allowing for lightning-fast, typo-tolerant searches.
The Search Workflow
Scout works by observing your Eloquent models. Whenever a model is created, updated, or
deleted, Scout automatically updates the corresponding search index.
| Component |
Responsibility |
| Searchable Trait |
Added to your model to enable search capabilities. |
| Search Engine |
The external service or database that stores the searchable index. |
| Scout Builder |
A fluent interface to perform searches:
Post::search('Laravel')->get().
|
Supported Engines
Choosing the right engine depends on your project's scale and features like typo tolerance or
geo-searching.
| Engine |
Best For |
Pros |
Cons |
| Database |
Small/Medium apps. |
No external services; easy setup. |
No typo tolerance; slower at scale. |
| Meilisearch |
Self-hosted production. |
Open-source; extremely fast; fuzzy search. |
Requires managing separate server. |
| Algolia |
Enterprise / Managed. |
Zero-config; powerful dashboard; ultra-fast. |
Can become expensive with high volume. |
| Typesense |
Modern, fast apps. |
Native vector search support; open-source. |
Newer ecosystem compared to Algolia. |
Basic Usage
Making a Model Searchable
Add the Laravel\Scout\Searchable trait and define which data should be indexed
using toSearchableArray.
use Laravel\Scout\Searchable;
class Article extends Model
{
use Searchable;
/**
* Get the indexable data array for the model.
*/
public function toSearchableArray(): array
{
return [
'id' => (int) $this->id,
'title' => $this->title,
'body' => strip_tags($this->body),
];
}
}
Performing a Search
Searching is as simple as calling the search method on your model. It returns an
Eloquent collection of matched models.
$articles = Article::search('Star Wars')->get();
// You can also use where clauses (strict equality only)
$articles = Article::search('Jedi')
->where('category_id', 5)
->paginate(15);
Key Features
- Batch Importing: If you have existing data, run
php artisan scout:import "App\Models\Article" to sync everything at once.
- Queued Indexing: Highly recommended for production. By setting
'queue' => true in config/scout.php, indexing happens in the
background.
- Conditional Search: You can control if a model should be searchable
based on its state:
public function shouldBeSearchable(): bool
{
return $this->is_published;
}
Database Engine Strategies
If you use the Database engine, you can choose how it searches your columns:
SearchUsingFullText: Uses native database full-text indexes
(MySQL/PostgreSQL).
SearchUsingPrefix: Searches for terms at the start of strings (e.g.,
word%).
Note
Hydration
When you perform a search, Scout gets the IDs of the matching
records from the search engine and then queries your local database to retrieve the
full Eloquent models. This ensures your results are always up-to-date with your
actual database records.
Warning
Data Limits
External engines like Algolia have record size limits (typically 10KB to 100KB).
Avoid sending entire HTML blobs or massive JSON fields in
toSearchableArray. Only index the text that users will actually search
for.
Socialite (OAuth Logins)
Laravel Socialite provides an expressive, fluent interface to
OAuth authentication with a wide variety of social providers. It handles
almost all of the boilerplate social login code, including exchanging authorization codes
for access tokens and retrieving user details from the provider.
Supported Providers
Socialite officially supports the most popular platforms out of the box. For others, the
community-driven Socialite Providers library adds support for hundreds more
(like Slack, Discord, and Spotify).
| Official Providers |
Community Examples (via Socialite Providers) |
| Google |
Discord |
| Facebook |
Slack |
| Twitter (X) |
Microsoft / Azure |
| GitHub |
Apple |
| LinkedIn |
TikTok |
The Authentication Flow
Socialite typically requires two routes: one to redirect the user to the provider, and one to
handle the callback after the user has authenticated.
Configuration (config/services.php)
You must add your credentials for each provider you wish to use.
'github' => [
'client_id' => env('GITHUB_CLIENT_ID'),
'client_secret' => env('GITHUB_CLIENT_SECRET'),
'redirect' => 'https://example.com/auth/github/callback',
],
The Controller Logic
use Laravel\Socialite\Facades\Socialite;
class AuthController extends Controller
{
// Redirect the user to the GitHub authentication page
public function redirect()
{
return Socialite::driver('github')->redirect();
}
// Handle the callback from GitHub
public function callback()
{
$githubUser = Socialite::driver('github')->user();
// Find or create a user in your local database
$user = User::updateOrCreate([
'github_id' => $githubUser->id,
], [
'name' => $githubUser->name,
'email' => $githubUser->email,
'github_token' => $githubUser->token,
'github_refresh_token' => $githubUser->refreshToken,
]);
Auth::login($user);
return redirect('/dashboard');
}
}
Retrievable User Data
When you call ->user(), Socialite returns a standardized user object regardless
of which provider you are using. This makes it easy to switch providers or add new ones
without rewriting your logic.
| Property |
Description |
getId() |
The unique ID assigned by the provider. |
getNickname() |
The user's handle/username (if available). |
getName() |
The user's full name. |
getEmail() |
The user's email address. |
getAvatar() |
The URL to the user's profile picture. |
token |
The Access Token used for future API requests to the provider. |
Advanced Scopes & Parameters
If you need more than just basic profile data (e.g., access to a user's GitHub repositories
or Google Calendar), you can define Scopes.
return Socialite::driver('github')
->scopes(['read:user', 'public_repo'])
->redirect();
Stateless Authentication
By default, Socialite uses Laravel's session to verify the "state" of the request (to prevent
CSRF). If you are building a stateless API (e.g., for a mobile app), you must use the
stateless method:
$user = Socialite::driver('google')->stateless()->user();
Note
Handling Email Conflicts
It is common for users to have the same email address across different providers
(e.g., the same email for GitHub and Google). You should decide whether to
automatically "link" these accounts or require the user to log in with their
original method to prevent account hijacking.
Warning
Redirect URLs
The redirect URL in your services.php must
exactly match the redirect URI you registered in the provider's
developer console (including https:// vs http://). Even a
missing trailing slash can cause a "redirect_uri_mismatch" error.
Telescope (Debug Assistant)
Laravel Telescope is an elegant debug assistant for the Laravel framework.
It provides insight into the requests coming into your application, exceptions, log entries,
database queries, queued jobs, mail, notifications, cache operations, scheduled tasks,
variable dumps, and more.
While Pulse (Section 9.9) is designed for production monitoring, Telescope
is primarily a local development tool designed to help you understand
exactly what happens during a single request or background process.
The Telescope Dashboard
Telescope provides a series of "Watchers" that record information about your application's
execution.
| Watcher |
What it Records |
| Requests |
Full HTTP request data: headers, payload, session, and response. |
| Commands |
Artisan commands executed and their output. |
| Queries |
Every SQL query, its execution time, and the code that triggered it. |
| Exceptions |
Detailed stack traces and data for every error. |
| Logs |
All entries written to your application logs. |
| Jobs |
Status and payloads of queued jobs. |
Key Features
Request Tagging
Telescope automatically tags entries. For example, it might tag a request with the
authenticated user's ID, making it easy to find all logs and queries associated with a
specific user's session.
Deep Inspection
Clicking into a request allows you to see everything that happened because of that
request: which emails were sent, which jobs were pushed to the queue, and every database
query executed.
The "Dump" Screen
If you use the dump() function in your code, the output won't clutter your
browser UI. Instead, it appears in real-time on the Telescope "Dumps" tab, keeping your
frontend clean.
Installation & Environment
- Install:
composer require laravel/telescope --dev
- Setup:
php artisan telescope:install
- Migrate:
php artisan migrate
Restricting to Local
By default, Telescope only records data in the local environment. If you decide
to use it in production, you must secure the dashboard:
// In App\Providers\TelescopeServiceProvider.php
protected function gate(): void
{
Gate::define('viewTelescope', function ($user) {
return in_array($user->email, [
'admin@example.com',
]);
});
}
Managing Data
Telescope records a lot of information, which can fill up your database quickly.
- Pruning: By default, Telescope entries older than 24 hours are deleted.
You can customize this or run the pruning command manually:
php artisan telescope:prune
- Filtering: You can define which entries should be recorded. For
example, you might want to ignore 200 OK responses but record all 404 or 500 errors.
Telescope vs. Pulse vs. Debugbar
| Tool |
Focus |
Use Case |
| Telescope |
The "How" |
Debugging complex flows (Jobs, Mail, Queries) in dev. |
| Pulse |
The "Health" |
Monitoring server vitals and slow routes in production. |
| Debugbar |
The "Page" |
Quick, per-page performance stats at the bottom of the browser. |
Note
Watcher Overhead
Each "Watcher" adds a small amount of overhead to your request as it writes to the
database. In local development, this is negligible, but it's the reason why
Telescope is generally not recommended for high-traffic production environments.
Warning
Sensitive Data
Telescope records request payloads. Ensure you use the
Telescope::hideFields(['password', 'card_number']) feature in your
TelescopeServiceProvider to prevent sensitive user information from
being stored in your debug logs.