NexusCS

Laravel

PHP
Laravel is a PHP web application framework with expressive, elegant syntax. This cheatsheet covers Laravel 11.x/12.x - routing, Eloquent ORM, Blade templates, validation, and Artisan commands.
php
framework
web

Getting started

Installation

# Create new Laravel project
laravel new example-app

# Or via Composer
composer create-project laravel/laravel example-app

Requirements

Requirement Version
PHP 8.1+
Composer Latest
Database SQLite (default)

Development server

# Start development server
php artisan serve
# Or
composer run dev

# Runs on http://localhost:8000

Quick example

// routes/web.php
Route::get('/hello', function () {
    return view('hello', ['name' => 'World']);
});

// resources/views/hello.blade.php
<h1>Hello, {{ $name }}</h1>

Routing

Basic routes

// routes/web.php
Route::get('/user', [UserController::class, 'index']);
Route::post('/user', [UserController::class, 'store']);
Route::put('/user/{id}', [UserController::class, 'update']);
Route::patch('/user/{id}', [UserController::class, 'patch']);
Route::delete('/user/{id}', [UserController::class, 'destroy']);

Route parameters

// Required parameter
Route::get('/user/{id}', function ($id) {
    return "User $id";
});

// Optional parameter
Route::get('/user/{name?}', function ($name = 'Guest') {
    return "Hello, $name";
});

// Multiple parameters
Route::get('/post/{id}/comment/{commentId}', function ($id, $commentId) {
    return "Post $id, Comment $commentId";
});

Named routes

// Define named route
Route::get('/profile', [ProfileController::class, 'show'])
    ->name('profile');

// Generate URL to named route
$url = route('profile');

// Redirect to named route
return redirect()->route('profile');

// With parameters
Route::get('/user/{id}', [UserController::class, 'show'])
    ->name('user.show');
$url = route('user.show', ['id' => 1]);

Route groups

// Middleware
Route::middleware(['auth'])->group(function () {
    Route::get('/dashboard', [DashboardController::class, 'index']);
    Route::get('/settings', [SettingsController::class, 'index']);
});

// Prefix
Route::prefix('admin')->group(function () {
    Route::get('/users', [AdminController::class, 'users']);
    Route::get('/posts', [AdminController::class, 'posts']);
});

// Combined
Route::prefix('admin')
    ->middleware(['auth', 'admin'])
    ->group(function () {
        Route::get('/dashboard', [AdminController::class, 'index']);
    });

Route model binding

// Automatic binding
Route::get('/user/{user}', function (User $user) {
    return $user->email;
});

// Custom key
Route::get('/post/{post:slug}', function (Post $post) {
    return $post->title;
});

// Model configuration
class Post extends Model {
    public function getRouteKeyName() {
        return 'slug';
    }
}

Available methods

Method Description
Route::get($uri, $callback) GET route
Route::post($uri, $callback) POST route
Route::put($uri, $callback) PUT route
Route::patch($uri, $callback) PATCH route
Route::delete($uri, $callback) DELETE route
Route::any($uri, $callback) Any method
Route::match(['get', 'post'], $uri, $callback) Specific methods
Route::redirect($from, $to) Redirect

Eloquent ORM

Creating models

# Create model
php artisan make:model Flight

# With migration
php artisan make:model Flight -m

# With controller
php artisan make:model Flight -c

# All together
php artisan make:model Flight -mcr

Model definition

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    // Table name (optional, auto-inferred)
    protected $table = 'flights';

    // Primary key (optional, defaults to 'id')
    protected $primaryKey = 'flight_id';

    // Mass assignable attributes
    protected $fillable = ['name', 'airline'];

    // Guarded attributes
    protected $guarded = ['price'];

    // Timestamps (enabled by default)
    public $timestamps = true;
}

Retrieving models

// All records
$flights = Flight::all();

// Find by primary key
$flight = Flight::find(1);

// Find or fail (throws 404)
$flight = Flight::findOrFail(1);

// First record
$flight = Flight::first();

// Where clause
$flights = Flight::where('active', 1)->get();

// Multiple conditions
$flights = Flight::where('active', 1)
    ->where('destination', 'Paris')
    ->get();

// First or fail
$flight = Flight::where('number', 'FR900')->firstOrFail();

Creating & updating

// Create
$flight = new Flight;
$flight->name = 'Paris to London';
$flight->save();

// Mass assignment
$flight = Flight::create([
    'name' => 'Paris to London',
    'airline' => 'Air France'
]);

// Update
$flight = Flight::find(1);
$flight->name = 'New name';
$flight->save();

// Update via query
Flight::where('active', 1)
    ->update(['delayed' => 1]);

// Find or create
$flight = Flight::firstOrCreate(
    ['name' => 'Tokyo to Sydney'],
    ['airline' => 'Qantas']
);

// Update or create
$flight = Flight::updateOrCreate(
    ['name' => 'Tokyo to Sydney'],
    ['airline' => 'Qantas']
);

Deleting models

// Delete instance
$flight = Flight::find(1);
$flight->delete();

// Delete by ID
Flight::destroy(1);
Flight::destroy([1, 2, 3]);

// Delete via query
Flight::where('active', 0)->delete();

// Soft deletes (requires SoftDeletes trait)
use Illuminate\Database\Eloquent\SoftDeletes;

class Flight extends Model {
    use SoftDeletes;
}

Relationships

// One to one (hasOne)
class User extends Model {
    public function phone() {
        return $this->hasOne(Phone::class);
    }
}
$phone = User::find(1)->phone;

// One to many (hasMany)
class Post extends Model {
    public function comments() {
        return $this->hasMany(Comment::class);
    }
}
$comments = Post::find(1)->comments;

// Belongs to (inverse)
class Comment extends Model {
    public function post() {
        return $this->belongsTo(Post::class);
    }
}
$post = Comment::find(1)->post;

// Many to many (belongsToMany)
class User extends Model {
    public function roles() {
        return $this->belongsToMany(Role::class);
    }
}
$roles = User::find(1)->roles;

Query methods

Method Description
all() Get all records
find($id) Find by ID
first() First record
get() Execute query
where($col, $val) Where clause
orderBy($col) Order by
limit($n) Limit results
skip($n) Skip records
count() Count records
pluck($col) Get column values

Blade Templates

Displaying data

// Escaped output
Hello, {{ $name }}

// Raw output (unescaped)
{!! $htmlContent !!}

// Default value
{{ $name ?? 'Guest' }}

// JSON encoding
<script>
    var app = @json($array);
</script>

Control structures

// If statements
@if ($records)
    <p>Records found</p>
@elseif ($pending)
    <p>Pending records</p>
@else
    <p>No records</p>
@endif

// Unless
@unless (Auth::check())
    <p>You are not signed in</p>
@endunless

// For loop
@for ($i = 0; $i < 10; $i++)
    <p>Item {{ $i }}</p>
@endfor

// Foreach
@foreach ($users as $user)
    <p>{{ $user->name }}</p>
@endforeach

// Forelse (with empty case)
@forelse ($users as $user)
    <p>{{ $user->name }}</p>
@empty
    <p>No users</p>
@endforelse

// While
@while (true)
    <p>Looping</p>
@endwhile

Loop variable

@foreach ($users as $user)
    @if ($loop->first)
        <p>First iteration</p>
    @endif

    <p>{{ $loop->index }}: {{ $user->name }}</p>

    @if ($loop->last)
        <p>Last iteration</p>
    @endif
@endforeach
Property Description
$loop->index Current index (0-based)
$loop->iteration Current iteration (1-based)
$loop->remaining Iterations remaining
$loop->count Total items
$loop->first First iteration
$loop->last Last iteration
$loop->even Even iteration
$loop->odd Odd iteration
$loop->depth Nesting level
$loop->parent Parent loop variable

Template inheritance

// Layout (resources/views/layouts/app.blade.php)
<!DOCTYPE html>
<html>
<head>
    <title>@yield('title')</title>
</head>
<body>
    @yield('content')

    @section('sidebar')
        <p>Default sidebar</p>
    @show
</body>
</html>

// Child view
@extends('layouts.app')

@section('title', 'Page Title')

@section('content')
    <p>This is the page content</p>
@endsection

@section('sidebar')
    @parent
    <p>Appended to sidebar</p>
@endsection

Components

// Anonymous component (resources/views/components/alert.blade.php)
<div class="alert alert-{{ $type }}">
    {{ $slot }}
</div>

// Usage
<x-alert type="success">
    Operation completed!
</x-alert>

// With attributes
<x-alert type="error" class="mb-4">
    Error occurred!
</x-alert>

// Named slots
<x-card>
    <x-slot:title>
        Card Title
    </x-slot>

    Card content
</x-card>

Comments

{{-- This comment will not be in HTML --}}

<!-- This comment will be in HTML -->

Directives

Directive Description
@csrf CSRF token field
@method('PUT') HTTP method field
@auth If authenticated
@guest If guest
@error('field') Validation error
@include('view') Include view
@includeIf('view') Include if exists
@each('view', $items, 'item') Iterate include

Validation

Basic validation

// In controller
public function store(Request $request)
{
    $validated = $request->validate([
        'title' => 'required|max:255',
        'body' => 'required',
        'email' => 'required|email|unique:users',
    ]);

    // Validation passed, use $validated
}

Validation rules

// Multiple rules
'email' => 'required|email|unique:users,email'

// Array syntax
'email' => ['required', 'email', 'unique:users']

// Numeric rules
'age' => 'required|numeric|min:18|max:120'

// String rules
'username' => 'required|string|min:3|max:20|alpha_dash'

// File rules
'photo' => 'required|image|mimes:jpeg,png|max:2048'

// Date rules
'birthday' => 'required|date|before:today'

// Conditional rules
'email' => 'required_if:type,subscriber|email'

Common rules

Rule Description
required Field must be present
email Valid email format
unique:table,column Unique in database
exists:table,column Exists in database
max:value Maximum value/length
min:value Minimum value/length
numeric Must be numeric
integer Must be integer
string Must be string
confirmed Must match field_confirmation
alpha Only letters
alpha_num Letters and numbers
alpha_dash Letters, numbers, dashes, underscores
url Valid URL
date Valid date
image Must be image
mimes:jpg,png Specific MIME types

Displaying errors

// In Blade template
@error('email')
    <div class="alert alert-danger">{{ $message }}</div>
@enderror

// All errors
@if ($errors->any())
    <ul>
        @foreach ($errors->all() as $error)
            <li>{{ $error }}</li>
        @endforeach
    </ul>
@endif

// Old input
<input type="text" name="email" value="{{ old('email') }}">

Form request validation

# Create form request
php artisan make:request StorePostRequest
// app/Http/Requests/StorePostRequest.php
class StorePostRequest extends FormRequest
{
    public function authorize()
    {
        return true;
    }

    public function rules()
    {
        return [
            'title' => 'required|max:255',
            'body' => 'required',
        ];
    }

    public function messages()
    {
        return [
            'title.required' => 'A title is required',
            'body.required' => 'A message is required',
        ];
    }
}

// In controller
public function store(StorePostRequest $request)
{
    // Validation automatically applied
    $validated = $request->validated();
}

Artisan Commands

Make commands

# Model
php artisan make:model Post
php artisan make:model Post -m  # with migration

# Controller
php artisan make:controller PostController
php artisan make:controller PostController --resource

# Migration
php artisan make:migration create_posts_table

# Seeder
php artisan make:seeder PostSeeder

# Factory
php artisan make:factory PostFactory

# Request
php artisan make:request StorePostRequest

# Middleware
php artisan make:middleware EnsureTokenIsValid

# Command
php artisan make:command SendEmails

Database commands

# Run migrations
php artisan migrate

# Rollback last migration
php artisan migrate:rollback

# Rollback all migrations
php artisan migrate:reset

# Fresh migration (drop all)
php artisan migrate:fresh

# Fresh with seeding
php artisan migrate:fresh --seed

# Run seeders
php artisan db:seed

# Run specific seeder
php artisan db:seed --class=UserSeeder

Cache commands

# Clear application cache
php artisan cache:clear

# Clear route cache
php artisan route:clear

# Cache routes
php artisan route:cache

# Clear config cache
php artisan config:clear

# Cache config
php artisan config:cache

# Clear view cache
php artisan view:clear

# Clear all caches
php artisan optimize:clear

Utility commands

# Start tinker (REPL)
php artisan tinker

# List all routes
php artisan route:list

# Run queue worker
php artisan queue:work

# Run scheduler
php artisan schedule:run

# Generate app key
php artisan key:generate

# Create storage link
php artisan storage:link

# Server
php artisan serve
php artisan serve --port=8080

Request & Response

Request input

// Get input value
$name = $request->input('name');

// With default
$name = $request->input('name', 'Guest');

// All input
$input = $request->all();

// Only specific fields
$input = $request->only(['username', 'password']);

// Except fields
$input = $request->except(['credit_card']);

// Query parameters
$name = $request->query('name');

// Check if present
if ($request->has('name')) {
    //
}

// Check if filled
if ($request->filled('name')) {
    //
}

Request methods

// Get request method
$method = $request->method();

// Check method
if ($request->isMethod('post')) {
    //
}

// Get URL
$url = $request->url();

// Get full URL
$url = $request->fullUrl();

// Get path
$path = $request->path();

File uploads

// Get uploaded file
$file = $request->file('photo');

// Check if uploaded
if ($request->hasFile('photo')) {
    //
}

// Check if valid
if ($request->file('photo')->isValid()) {
    //
}

// Store file
$path = $request->file('photo')->store('photos');

// Store with custom name
$path = $request->file('photo')->storeAs('photos', 'custom.jpg');

// Store to public disk
$path = $request->file('photo')->store('photos', 'public');

Responses

// Return view
return view('welcome');

// With data
return view('welcome', ['name' => 'John']);

// Redirect
return redirect('/home');

// Redirect to route
return redirect()->route('profile');

// Redirect with data
return redirect('/home')->with('status', 'Success!');

// JSON response
return response()->json([
    'name' => 'John',
    'status' => 'active'
]);

// Download response
return response()->download($pathToFile);

// File response
return response()->file($pathToFile);

Database & Migrations

Creating migrations

# Create migration
php artisan make:migration create_flights_table

# Create migration for existing table
php artisan make:migration add_votes_to_users_table

Migration structure

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateFlightsTable extends Migration
{
    public function up()
    {
        Schema::create('flights', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('airline');
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('flights');
    }
}

Column types

$table->id();                    // Auto-incrementing ID
$table->string('name');          // VARCHAR
$table->string('name', 100);     // VARCHAR(100)
$table->text('description');     // TEXT
$table->integer('votes');        // INTEGER
$table->bigInteger('votes');     // BIGINT
$table->decimal('amount', 8, 2); // DECIMAL
$table->boolean('confirmed');    // BOOLEAN
$table->date('created_date');    // DATE
$table->datetime('created_at');  // DATETIME
$table->timestamp('added_on');   // TIMESTAMP
$table->json('options');         // JSON
$table->timestamps();            // created_at & updated_at
$table->softDeletes();           // deleted_at

Column modifiers

$table->string('email')->nullable();
$table->string('name')->default('Guest');
$table->integer('votes')->unsigned();
$table->string('email')->unique();
$table->integer('user_id')->index();
$table->decimal('amount')->unsigned()->default(0);

Indexes & constraints

// Primary key
$table->primary('id');

// Unique
$table->unique('email');

// Index
$table->index('user_id');

// Foreign key
$table->foreignId('user_id')
      ->constrained()
      ->onDelete('cascade');

// Composite index
$table->index(['user_id', 'created_at']);

// Drop index
$table->dropIndex('users_email_unique');

Modifying tables

// Add columns
Schema::table('users', function (Blueprint $table) {
    $table->string('avatar')->nullable();
});

// Rename column
Schema::table('users', function (Blueprint $table) {
    $table->renameColumn('from', 'to');
});

// Drop column
Schema::table('users', function (Blueprint $table) {
    $table->dropColumn('votes');
});

// Drop multiple columns
Schema::table('users', function (Blueprint $table) {
    $table->dropColumn(['votes', 'avatar']);
});

Authentication

Laravel Breeze

# Install Breeze (starter kit)
composer require laravel/breeze --dev

# Install Breeze scaffolding
php artisan breeze:install

# With Blade
php artisan breeze:install blade

# With React
php artisan breeze:install react

# With Vue
php artisan breeze:install vue

# Run migrations
php artisan migrate
npm install && npm run dev

Auth helpers

// Check if authenticated
if (Auth::check()) {
    // User is logged in
}

// Get authenticated user
$user = Auth::user();

// Get user ID
$id = Auth::id();

// Authenticate user
Auth::login($user);

// Logout
Auth::logout();

// Attempt login
if (Auth::attempt(['email' => $email, 'password' => $password])) {
    // Authentication passed
}

Route protection

// Protect routes with middleware
Route::middleware(['auth'])->group(function () {
    Route::get('/dashboard', [DashboardController::class, 'index']);
});

// Single route
Route::get('/profile', [ProfileController::class, 'show'])
    ->middleware('auth');

// In controller constructor
public function __construct()
{
    $this->middleware('auth');
}

Sanctum (API tokens)

# Install Sanctum
composer require laravel/sanctum

# Publish config
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"

# Run migrations
php artisan migrate
// Create token
$token = $user->createToken('token-name')->plainTextToken;

// Protect routes
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
    return $request->user();
});

Examples

Basic CRUD controller

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\Request;

class PostController extends Controller
{
    public function index()
    {
        $posts = Post::all();
        return view('posts.index', compact('posts'));
    }

    public function create()
    {
        return view('posts.create');
    }

    public function store(Request $request)
    {
        $validated = $request->validate([
            'title' => 'required|max:255',
            'body' => 'required',
        ]);

        $post = Post::create($validated);

        return redirect()->route('posts.show', $post)
            ->with('success', 'Post created!');
    }

    public function show(Post $post)
    {
        return view('posts.show', compact('post'));
    }

    public function edit(Post $post)
    {
        return view('posts.edit', compact('post'));
    }

    public function update(Request $request, Post $post)
    {
        $validated = $request->validate([
            'title' => 'required|max:255',
            'body' => 'required',
        ]);

        $post->update($validated);

        return redirect()->route('posts.show', $post)
            ->with('success', 'Post updated!');
    }

    public function destroy(Post $post)
    {
        $post->delete();

        return redirect()->route('posts.index')
            ->with('success', 'Post deleted!');
    }
}

Eloquent relationship query

// One-to-many relationship
class User extends Model
{
    public function posts()
    {
        return $this->hasMany(Post::class);
    }
}

class Post extends Model
{
    public function user()
    {
        return $this->belongsTo(User::class);
    }

    public function comments()
    {
        return $this->hasMany(Comment::class);
    }
}

// Query with relationships
$user = User::with('posts')->find(1);

// Eager load multiple relationships
$posts = Post::with(['user', 'comments'])->get();

// Nested eager loading
$posts = Post::with('comments.user')->get();

// Lazy eager loading
$posts = Post::all();
$posts->load('comments');

// Constraining eager loads
$users = User::with(['posts' => function ($query) {
    $query->where('published', 1)->orderBy('created_at', 'desc');
}])->get();

// Count relationships
$posts = Post::withCount('comments')->get();
foreach ($posts as $post) {
    echo $post->comments_count;
}

Blade template with loop

{{-- resources/views/posts/index.blade.php --}}
@extends('layouts.app')

@section('title', 'All Posts')

@section('content')
    <div class="container">
        <h1>All Posts</h1>

        @if (session('success'))
            <div class="alert alert-success">
                {{ session('success') }}
            </div>
        @endif

        @forelse ($posts as $post)
            <article class="post">
                <h2>
                    <a href="{{ route('posts.show', $post) }}">
                        {{ $post->title }}
                    </a>
                </h2>

                <p>{{ Str::limit($post->body, 200) }}</p>

                <div class="meta">
                    <span>By {{ $post->user->name }}</span>
                    <span>{{ $post->created_at->diffForHumans() }}</span>
                    <span>{{ $post->comments_count }} comments</span>
                </div>

                @if ($loop->last)
                    <p>Last post</p>
                @endif
            </article>
        @empty
            <p>No posts found.</p>
        @endforelse

        {{ $posts->links() }}
    </div>
@endsection

Form validation example

{{-- resources/views/posts/create.blade.php --}}
@extends('layouts.app')

@section('content')
    <div class="container">
        <h1>Create New Post</h1>

        <form method="POST" action="{{ route('posts.store') }}">
            @csrf

            <div class="form-group">
                <label for="title">Title</label>
                <input
                    type="text"
                    name="title"
                    id="title"
                    class="form-control @error('title') is-invalid @enderror"
                    value="{{ old('title') }}"
                >
                @error('title')
                    <div class="invalid-feedback">{{ $message }}</div>
                @enderror
            </div>

            <div class="form-group">
                <label for="body">Body</label>
                <textarea
                    name="body"
                    id="body"
                    class="form-control @error('body') is-invalid @enderror"
                    rows="10"
                >{{ old('body') }}</textarea>
                @error('body')
                    <div class="invalid-feedback">{{ $message }}</div>
                @enderror
            </div>

            <button type="submit" class="btn btn-primary">
                Create Post
            </button>
        </form>
    </div>
@endsection
// Controller with validation
public function store(Request $request)
{
    $validated = $request->validate([
        'title' => [
            'required',
            'string',
            'max:255',
            'unique:posts,title'
        ],
        'body' => [
            'required',
            'string',
            'min:50'
        ],
    ], [
        'title.required' => 'Please provide a post title.',
        'title.unique' => 'This title has already been used.',
        'body.min' => 'Post body must be at least 50 characters.',
    ]);

    $post = auth()->user()->posts()->create($validated);

    return redirect()
        ->route('posts.show', $post)
        ->with('success', 'Post created successfully!');
}

Gotchas

Mass assignment protection

Always define $fillable or $guarded properties to prevent mass assignment vulnerabilities:

class User extends Model
{
    // Allow these fields only
    protected $fillable = ['name', 'email', 'password'];

    // Or guard specific fields
    protected $guarded = ['is_admin'];
}

N+1 query problem

Use eager loading to avoid N+1 queries:

// Bad - N+1 queries
$posts = Post::all();
foreach ($posts as $post) {
    echo $post->user->name; // Executes query for each post
}

// Good - 2 queries total
$posts = Post::with('user')->get();
foreach ($posts as $post) {
    echo $post->user->name;
}

Route caching

Route caching doesn't work with closure-based routes. Use controllers:

// Won't work with route:cache
Route::get('/', function () {
    return view('welcome');
});

// Use controllers instead
Route::get('/', [HomeController::class, 'index']);

Config caching

After running config:cache, environment variables won't be loaded. Use config() helper:

// Bad - won't work with config:cache
$apiKey = env('API_KEY');

// Good - works with config:cache
$apiKey = config('services.api.key');

Timezone handling

Laravel stores dates in UTC by default. Set timezone in config/app.php:

'timezone' => 'UTC', // or 'America/New_York', etc.

Also see