🏠 Home / Hub

📝 Laravel 05 — CRUD & Validation

← Laravel Menu · ← Prev: Database

1. Resource Routes

// routes/web.php
Route::resource('posts', PostController::class);
// Generates 7 routes automatically:
// GET    /posts              → index
// GET    /posts/create       → create
// POST   /posts              → store
// GET    /posts/{post}       → show
// GET    /posts/{post}/edit  → edit
// PUT/PATCH /posts/{post}    → update
// DELETE /posts/{post}       → destroy

// Nested resources
Route::resource('posts.comments', CommentController::class)
     ->shallow();

// See all routes:
php artisan route:list

2. Controller — Full CRUD

php artisan make:controller PostController --resource

// app/Http/Controllers/PostController.php
class PostController extends Controller
{
    public function index()
    {
        $posts = Post::with('user')
                     ->latest()
                     ->paginate(15);
        return view('posts.index', compact('posts'));
    }

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

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

        $data['user_id'] = auth()->id();
        $data['slug']    = Str::slug($data['title']);

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

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

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

    public function edit(Post $post)
    {
        $this->authorize('update', $post);    // policy check
        return view('posts.edit', compact('post'));
    }

    public function update(Request $request, Post $post)
    {
        $this->authorize('update', $post);

        $data = $request->validate([
            'title' => ['required', 'min:3', 'max:255'],
            'body'  => ['required', 'min:10'],
        ]);

        $data['slug'] = Str::slug($data['title']);
        $post->update($data);

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

    public function destroy(Post $post)
    {
        $this->authorize('delete', $post);
        $post->delete();
        return redirect()->route('posts.index')
                         ->with('success', 'Post deleted!');
    }
}

3. Validation Rules Reference

RuleMeaning
requiredMust be present and non-empty
nullableCan be null/empty (opposite of required)
stringMust be a string
min:3 / max:255String length / numeric min-max
emailValid email format
unique:users,emailMust not exist in users.email
unique:users,email,{$id}Unique except for this record (update)
confirmedMust have matching _confirmation field
numeric / integerMust be a number
in:draft,publishedMust be one of these values
exists:categories,idMust exist in categories.id column
imageMust be image (jpeg/png/gif/webp)
mimes:pdf,docSpecific file MIME types
max:2048 (file)File max size in KB (2MB)
urlValid URL format
dateValid date string
after:todayDate must be after today

4. Form Request (Reusable Validation)

php artisan make:request StorePostRequest

// app/Http/Requests/StorePostRequest.php
class StorePostRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;   // or auth()->check()
    }

    public function rules(): array
    {
        return [
            'title'       => ['required', 'string', 'min:3', 'max:255'],
            'body'        => ['required', 'string', 'min:10'],
            'category_id' => ['nullable', 'exists:categories,id'],
            'image'       => ['nullable', 'image', 'max:2048'],
        ];
    }

    public function messages(): array
    {
        return [
            'title.required' => 'Post title is required',
            'body.min'       => 'Body must be at least 10 characters',
        ];
    }
}

// Controller — inject instead of Request
public function store(StorePostRequest $request)
{
    $data = $request->validated();   // already validated!
    Post::create($data);
    return redirect()->route('posts.index');
}

5. Blade Form with Validation Errors

{{-- resources/views/posts/create.blade.php --}}
<form method="POST" action="{{ route('posts.store') }}" enctype="multipart/form-data">
    @csrf

    <div>
        <label>Title</label>
        <input type="text" name="title" value="{{ old('title') }}"
               class="{{ $errors->has('title') ? 'is-invalid' : '' }}">
        @error('title')
            <span class="error">{{ $message }}</span>
        @enderror
    </div>

    <div>
        <label>Body</label>
        <textarea name="body">{{ old('body') }}</textarea>
        @error('body')
            <span class="error">{{ $message }}</span>
        @enderror
    </div>

    <div>
        <label>Image</label>
        <input type="file" name="image" accept="image/*">
        @error('image')
            <span class="error">{{ $message }}</span>
        @enderror
    </div>

    <button type="submit">Create Post</button>
</form>

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

6. File Uploads

// Controller — handle image upload
public function store(StorePostRequest $request)
{
    $data = $request->validated();

    if ($request->hasFile('image')) {
        // Store in storage/app/public/images/
        $path = $request->file('image')->store('images', 'public');
        $data['image'] = $path;   // e.g. "images/abc123.jpg"
    }

    Post::create($data);
    return redirect()->route('posts.index');
}

// Don't forget: php artisan storage:link
// Creates public/storage → symbolic link to storage/app/public

// In Blade — display the image
<img src="{{ Storage::url($post->image) }}" alt="Post image">
// or:
<img src="{{ asset('storage/' . $post->image) }}" alt="Post image">

7. Delete with Confirmation

{{-- Blade delete button (HTML form — no JavaScript needed) --}}
<form method="POST" action="{{ route('posts.destroy', $post) }}"
      onsubmit="return confirm('Delete this post?')">
    @csrf
    @method('DELETE')
    <button type="submit">Delete</button>
</form>

{{-- Or use Alpine.js for nicer confirmation --}}
<form method="POST" action="{{ route('posts.destroy', $post) }}"
      x-data x-on:submit.prevent="if(confirm('Delete?')) $el.submit()">
    @csrf
    @method('DELETE')
    <button>Delete</button>
</form>
Browser form က GET/POST ပဲ support လုပ်လို့ @method('DELETE') နဲ့ spoof လုပ်တယ်။ Laravel က _method hidden field ကို check တယ်။

8. Pagination in Blade

// Controller
$posts = Post::latest()->paginate(15);
return view('posts.index', compact('posts'));

// Blade
@foreach($posts as $post)
    <div>{{ $post->title }}</div>
@endforeach

{{ $posts->links() }}   {{-- renders pagination links --}}

// With Tailwind styling:
{{ $posts->links('pagination::tailwind') }}

// Append query string to pagination links
{{ $posts->appends(request()->query())->links() }}

📌 Study Checklist