RefPad

Laravel

Laravel 11.x / PHP 8.2+

Table of Contents

  1. Project Setup & Artisan
  2. Routing
  3. Controllers
  4. Request & Validation
  5. Eloquent ORM
  6. Migrations
  7. Relationships
  8. Blade Templates
  9. Middleware
  10. Authentication

1. Project Setup & Artisan

# Create a new project
composer create-project laravel/laravel my-app
cd my-app
 
# Start development server
php artisan serve             # http://localhost:8000
 
# Clear caches
php artisan cache:clear
php artisan config:clear
php artisan route:clear
php artisan view:clear
 
# Generate key
php artisan key:generate
 
# REPL — interactive shell (Tinker)
php artisan tinker

Tinker Examples

// Launch
php artisan tinker
 
// Eloquent queries
App\Models\User::count();
App\Models\Post::with('user')->latest()->first();
App\Models\User::find(1)->posts()->pluck('title');
 
// Create records
App\Models\User::factory()->create();
App\Models\Post::create(['title' => 'Test', 'body' => '...', 'user_id' => 1]);
 
// Call any class / helper
now();
str('hello world')->title();
bcrypt('secret');
dispatch(new App\Jobs\SendEmailJob());

Common make: Commands

php artisan make:model Post -mcr    # Model + Migration + Controller (resource)
php artisan make:controller PostController --resource
php artisan make:model Post -m      # Model + Migration
php artisan make:request StorePostRequest
php artisan make:middleware EnsureTokenIsValid
php artisan make:seeder PostSeeder
php artisan make:factory PostFactory
php artisan make:policy PostPolicy --model=Post
php artisan make:job SendEmailJob
php artisan make:event OrderShipped

2. Routing

// routes/web.php
 
use App\Http\Controllers\PostController;
 
// CRUD routes (manual, with named routes)
Route::get('/posts',              [PostController::class, 'index'])->name('posts.index');
Route::get('/posts/create',       [PostController::class, 'create'])->name('posts.create');
Route::post('/posts',             [PostController::class, 'store'])->name('posts.store');
Route::get('/posts/{post}',       [PostController::class, 'show'])->name('posts.show');
Route::get('/posts/{post}/edit',  [PostController::class, 'edit'])->name('posts.edit');
Route::put('/posts/{post}',       [PostController::class, 'update'])->name('posts.update');
Route::delete('/posts/{post}',    [PostController::class, 'destroy'])->name('posts.destroy');
 
// The above 7 routes are equivalent to:
Route::resource('posts', PostController::class);
 
// Partial resource (only specified actions)
Route::resource('posts', PostController::class)->only(['index', 'show']);
Route::resource('posts', PostController::class)->except(['create', 'edit']);
 
// API resource (no create / edit HTML routes)
Route::apiResource('posts', PostController::class);
 
// Named route (standalone)
Route::get('/dashboard', fn() => view('dashboard'))->name('dashboard');
 
// Route groups
Route::prefix('admin')->middleware('auth')->name('admin.')->group(function () {
    Route::resource('posts', PostController::class);
    // → /admin/posts, named: admin.posts.index etc.
});

Route Helpers

// Generate URL from name
route('dashboard');
route('posts.show', $post);   // → /posts/1
 
// Redirect
return redirect()->route('posts.index');
return back();

3. Controllers

// app/Http/Controllers/PostController.php
 
namespace App\Http\Controllers;
 
use App\Models\Post;
use App\Http\Requests\StorePostRequest;
use Illuminate\Http\Request;
 
class PostController extends Controller
{
    public function index()
    {
        $posts = Post::latest()->paginate(10);
        return view('posts.index', compact('posts'));
    }
 
    public function show(Post $post)       // route model binding
    {
        return view('posts.show', compact('post'));
    }
 
    public function store(StorePostRequest $request)
    {
        $post = Post::create($request->validated());
        return redirect()->route('posts.show', $post)->with('success', 'Created!');
    }
 
    public function update(StorePostRequest $request, Post $post)
    {
        $post->update($request->validated());
        return redirect()->route('posts.show', $post);
    }
 
    public function destroy(Post $post)
    {
        $post->delete();
        return redirect()->route('posts.index');
    }
}

JSON / API Response

// API controller
public function index()
{
    return Post::paginate(15);                     // auto JSON
}
 
public function show(Post $post)
{
    return response()->json($post, 200);
}
 
public function store(Request $request)
{
    $post = Post::create($request->validated());
    return response()->json($post, 201);
}

4. Request & Validation

Form Request

// app/Http/Requests/StorePostRequest.php
 
namespace App\Http\Requests;
 
use Illuminate\Foundation\Http\FormRequest;
 
class StorePostRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;    // or: $this->user()->can('create', Post::class)
    }
 
    public function rules(): array
    {
        return [
            'title'      => ['required', 'string', 'max:255'],
            'body'       => ['required', 'string'],
            'status'     => ['required', 'in:draft,published'],
            'tags'       => ['nullable', 'array'],
            'tags.*'     => ['string', 'max:50'],
            'thumbnail'  => ['nullable', 'image', 'max:2048'],  // max KB
        ];
    }
 
    public function messages(): array
    {
        return [
            'title.required' => 'A title is required.',
        ];
    }
}

Common Validation Rules

RuleDescription
requiredMust be present and non-empty
nullableAllow null
string / integer / booleanType check
min:n / max:nLength or numeric range
emailValid email format
unique:table,columnMust not exist in DB
exists:table,columnMust exist in DB
confirmedField must have _confirmation match
in:a,b,cMust be one of the listed values
imageMust be an image file
date / after:todayDate validation

Inline Validation

$validated = $request->validate([
    'email' => ['required', 'email', 'unique:users'],
    'password' => ['required', 'min:8', 'confirmed'],
]);

5. Eloquent ORM

Model Definition

// app/Models/Post.php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
 
class Post extends Model
{
    use SoftDeletes;
 
    protected $fillable = ['title', 'body', 'status', 'user_id'];
    // or block mass assignment with $guarded:
    // protected $guarded = [];
 
    protected $casts = [
        'published_at' => 'datetime',
        'is_featured'  => 'boolean',
        'metadata'     => 'array',
    ];
}

Query Builder

// Retrieve
Post::all();
Post::find(1);                          // null if not found
Post::findOrFail(1);                    // 404 if not found
Post::first();
Post::where('status', 'published')->get();
Post::where('views', '>=', 100)->orderBy('created_at', 'desc')->get();
Post::whereIn('status', ['draft', 'published'])->get();
Post::whereBetween('created_at', [now()->subMonth(), now()])->get();
Post::latest()->take(5)->get();
Post::count();
 
// Create
Post::create(['title' => 'Hello', 'body' => '...']);
 
// Update
Post::where('status', 'draft')->update(['status' => 'published']);
$post->update(['title' => 'Updated']);
 
// Delete
$post->delete();
Post::where('status', 'archived')->delete();
 
// Upsert
Post::updateOrCreate(
    ['slug' => 'hello-world'],          // match criteria
    ['title' => 'Hello World', 'body' => '...'],  // values to set
);
 
// Chunk for large datasets
Post::chunk(100, function ($posts) {
    foreach ($posts as $post) {
        // process
    }
});

Scopes

// In the model
public function scopePublished(Builder $query): Builder
{
    return $query->where('status', 'published');
}
 
// Usage
Post::published()->latest()->get();

6. Migrations

php artisan migrate
php artisan migrate:rollback
php artisan migrate:fresh          # drop all + re-migrate (dev only)
php artisan migrate:status
// database/migrations/xxxx_create_posts_table.php
 
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
 
return new class extends Migration
{
    public function up(): void
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->text('body');
            $table->string('status')->default('draft');
            $table->string('slug')->unique();
            $table->boolean('is_featured')->default(false);
            $table->json('metadata')->nullable();
            $table->timestamp('published_at')->nullable();
            $table->foreignId('user_id')->constrained()->cascadeOnDelete();
            $table->softDeletes();
            $table->timestamps();   // created_at, updated_at
        });
    }
 
    public function down(): void
    {
        Schema::dropIfExists('posts');
    }
};

Common Column Types

MethodSQL type
$table->id()BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT
$table->string('col')VARCHAR(255)
$table->text('col')TEXT
$table->integer('col')INT
$table->unsignedBigInteger('col')BIGINT UNSIGNED
$table->boolean('col')TINYINT(1)
$table->decimal('col', 8, 2)DECIMAL(8,2)
$table->json('col')JSON
$table->timestamp('col')TIMESTAMP
$table->foreignId('user_id')->constrained()FK → users.id
$table->softDeletes()deleted_at TIMESTAMP NULL
$table->timestamps()created_at, updated_at

7. Relationships

// One to Many
class User extends Model
{
    public function posts(): HasMany
    {
        return $this->hasMany(Post::class);
    }
}
 
class Post extends Model
{
    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }
}
 
// Many to Many
class Post extends Model
{
    public function tags(): BelongsToMany
    {
        return $this->belongsToMany(Tag::class)->withTimestamps();
    }
}
 
// Has Many Through
class Country extends Model
{
    public function posts(): HasManyThrough
    {
        return $this->hasManyThrough(Post::class, User::class);
    }
}

Eager Loading (avoids N+1)

// N+1 problem
$posts = Post::all();
foreach ($posts as $post) {
    echo $post->user->name;     // 1 query per post
}
 
// Eager load
$posts = Post::with('user')->get();                    // 2 queries total
$posts = Post::with(['user', 'tags'])->get();
$posts = Post::with('comments.user')->get();           // nested
 
// Conditional eager load
$posts = Post::with(['comments' => function ($q) {
    $q->where('approved', true)->latest()->take(3);
}])->get();

Filtering by Relationship (whereHas / has)

// Posts that have at least one comment
Post::has('comments')->get();
 
// Posts that have 3 or more comments
Post::has('comments', '>=', 3)->get();
 
// Posts that have a comment by a specific user (with constraint)
Post::whereHas('comments', function ($q) {
    $q->where('user_id', 5);
})->get();
 
// Posts that have NO comments
Post::doesntHave('comments')->get();
 
// Nested
Post::whereHas('comments.user', function ($q) {
    $q->where('role', 'admin');
})->get();
// Add comments_count attribute to each post
$posts = Post::withCount('comments')->get();
foreach ($posts as $post) {
    echo $post->comments_count;
}
 
// Multiple counts + alias
$posts = Post::withCount([
    'comments',
    'comments as approved_count' => fn($q) => $q->where('approved', true),
])->get();
 
// withCount + has combined
Post::withCount('comments')->has('comments')->orderByDesc('comments_count')->get();

Attach / Detach (Many to Many)

$post->tags()->attach([1, 2, 3]);
$post->tags()->detach([2]);
$post->tags()->sync([1, 3]);        // replace all with these IDs

8. Blade Templates

{{-- resources/views/posts/index.blade.php --}}
 
@extends('layouts.app')
 
@section('content')
    <h1>Posts</h1>
 
    @foreach ($posts as $post)
        <article>
            <h2>{{ $post->title }}</h2>               {{-- escaped --}}
            <div>{!! $post->body_html !!}</div>        {{-- unescaped --}}
 
            @if ($post->is_featured)
                <span>Featured</span>
            @endif
        </article>
    @endforeach
 
    {{ $posts->links() }}                              {{-- pagination --}}
@endsection

Common Directives

{{-- Variables --}}
{{ $var }}              {{-- echo, HTML escaped --}}
{!! $var !!}            {{-- echo, unescaped (trusted HTML only) --}}
{{ $var ?? 'default' }} {{-- null coalescing --}}
 
{{-- Control --}}
@if ($condition) ... @elseif (...) ... @else ... @endif
@unless ($condition) ... @endunless
@foreach ($items as $item) ... @endforeach
@forelse ($items as $item) ... @empty ... @endforelse
@for ($i = 0; $i < 10; $i++) ... @endfor
@while ($condition) ... @endwhile
 
{{-- Layout --}}
@extends('layouts.app')
@section('title', 'Page Title')
@section('content') ... @endsection
@yield('content')
@include('partials.nav')
@includeIf('partials.sidebar')
 
{{-- Auth --}}
@auth ... @endauth
@guest ... @endguest
@can('update', $post) ... @endcan
 
{{-- Components (Blade components) --}}
<x-alert type="error" :message="$error" />

9. Middleware

// app/Http/Middleware/EnsureTokenIsValid.php
 
namespace App\Http\Middleware;
 
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
 
class EnsureTokenIsValid
{
    public function handle(Request $request, Closure $next): Response
    {
        if ($request->bearerToken() !== config('app.api_token')) {
            return response()->json(['message' => 'Unauthorized'], 401);
        }
 
        return $next($request);
    }
}
// Register in bootstrap/app.php (Laravel 11)
->withMiddleware(function (Middleware $middleware) {
    $middleware->alias([
        'token' => \App\Http\Middleware\EnsureTokenIsValid::class,
    ]);
})
 
// Apply to route
Route::get('/api/data', handler(...))->middleware('token');
Route::get('/dashboard', handler(...))->middleware(['auth', 'verified']);

10. Authentication

Laravel Breeze (minimal scaffold)

composer require laravel/breeze --dev
php artisan breeze:install          # blade / react / vue / api
php artisan migrate
npm install && npm run dev

Auth Helpers

// Check auth state
auth()->check();            // bool
auth()->guest();            // bool
 
// Current user
$user = auth()->user();
$user = $request->user();
 
// Login / Logout
auth()->login($user);
auth()->logout();
$request->session()->invalidate();
 
// Attempt login
if (auth()->attempt(['email' => $email, 'password' => $password])) {
    $request->session()->regenerate();
    return redirect()->intended('dashboard');
}

Tips

Environment Variables

// .env
APP_ENV=local
APP_DEBUG=true
DB_CONNECTION=mysql
 
// Access in code
env('APP_ENV', 'production');   // default as second arg
config('app.env');               // via config/app.php

Collections

$users = User::all();                              // returns Collection
 
$users->pluck('email');                            // ["a@b.com", ...]
$users->where('active', true)->values();
$users->groupBy('role');
$users->sortByDesc('created_at');
$users->map(fn($u) => $u->name . ' <' . $u->email . '>');
$users->filter(fn($u) => $u->age >= 18);
$users->sum('orders_count');
$users->count();

Helper Functions

now()                          // Carbon instance
today()                        // Carbon::today()
str('hello world')->title()    // "Hello World"
str()->uuid()                  // UUID string
collect([1, 2, 3])             // Collection
abort(404)                     // throw HTTP exception
abort_if(!$post, 404)
abort_unless($user->isAdmin(), 403)
dd($value)                     // dump and die
dump($value)                   // dump and continue