In the last post I shared how my code has evolved to write clean, succinct, and reusable code. If you haven’t read it, you can check this out below.

 Raheel Shan | Simplify Laravel CRUD with FormRequest and Lean Controllers | Laravel Tips
Raheel Shan | Simplify Laravel CRUD with FormRequest and Lean Controllers | Laravel Tips
Learn how to simplify Laravel CRUD operations using Resource Controllers, FormRequests, and a clean one-liner approach. Ideal for developers who want reusable, scalable, and DRY Laravel code.
raheelshan.com

In this post I will go a step further to tell how my previous structure can be even a little more concise. If you are ready, here we go.

From Resource Controller to Custom Controller

We will start by writing a controller again, but this time a custom controller with only 4 to 5 methods that will do all the work for us. Here is the sample.

                                <?php

namespace App\Http\Controllers;

use App\Http\Requests\Post\GetAllPostsRequest;
use App\Http\Requests\Post\GetPostRequest;
use App\Http\Requests\Post\SavePostRequest;
use App\Http\Requests\Post\DeletePostRequest;

class PostController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index(GetAllPostsRequest $request)
    {
        $response = $request->handle();
        return view('post.index', ['posts' => $response]);
    }

    /**
     * Show the form for creating a new resource.
     */
    public function form(GetPostRequest $request,$id)
    {
        $response = $request->handle($id);
        return view('post.form', ['post' => $response]);
    }

    /**
    * Update the specified resource in storage.
    */
    public function save(SavePostRequest $request, int $id)
    {
        $request->handle($id);

        if($id > 0){
            session()->flash('message', 'Post has been updated successfully.');
        }else{
            session()->flash('message', 'Post has been created successfully.');
        }

        return redirect()->route('post.index');
    }

    /**
     * Display the specified resource.
     */
    public function show(int $id, GetPostRequest $request)
    {
        $response = $request->handle($id);
        return view('post.show', ['post' => $response]);
    }

    /**
     * Remove the specified resource from storage.
     */
    public function destroy(int $id, DeletePostRequest $request)
    {
        $request->handle($id);
        session()->flash('message', 'Post has been deleted successfully.');
        return redirect()->route('post.index');
    }
}
                            

Now let’s define routes for them.

                                Route::prefix('posts')->name('posts.')->group(function () {
    Route::get('/', [PostController::class, 'index'])->name('index');
    Route::get('/form/{id?}', [PostController::class, 'form'])->name('form');
    Route::post('/form/{id?}', [PostController::class, 'saveForm'])->name('save');
    Route::delete('/{id}', [PostController::class, 'delete'])->name('delete');
});
                            

Here we have combined create and edit routes to the get-type form route. Similarly, we have combined store and update routes to post-type form routes.Now let’s see GetPostRequest.  

                                class GetPostRequest extends FormRequest
{
    public function handle($id)
    {
          return Post::findOrNew($id);
    }
}
                            

It is as intact as it was last time. We have made no changes in it.Before proceeding, I would like to shed some light on two options available here.  

Optional Parameter

You can define a route with an optional parameter and set a default parameter in GetPostRequest.

                                // optional id parameter. Useful for edit route
Route::get('/form/{id?}', [PostController::class, 'form'])->name('form');

class GetPostRequest extends FormRequest
{
    public function handle($id)
    {
          return Post::findOrNew($id);
    }
}
                            

And routes for this

                                <!-- load form in create mode -->
<a href="{{ route('posts.form') }}">Create</a>

<!-- load form in edit mode inside index.blade.php under $posts loop -->
<a href="{{ route('posts.form', [ 'id' => $post->id ]) }}">
    Edit
</a>
                            

Required Parameters

You can define a route with a required parameter and pass 0 in create mode.

                                // optional id parameter. Useful for edit route
Route::get('/form/{id}', [PostController::class, 'form'])->name('form');

                            

And routes for this.

                                <!-- load form in create mode by passing 0 -->
<a href="{{ route('posts.form', [ 'id' => 0 ]) }}">Create</a>

<!-- load form in edit mode inside index.blade.php under $posts loop -->
<a href="{{ route('posts.form', [ 'id' => $post->id ]) }}">
    Edit
</a>
                            

Now that we have deleted create.blade.php and renamed edit.blade.php to form.blade.php, we can say,

If we take a look at form.blade.php, we will have this.

                                <form action="{{ route('posts.form', [ 'id' => $post->id ]) }}" method="post">
    @csrf
    <div>
        <div>
            <label>Title</label>
            <input type="text" name="title" value="{{ old('title', $post->title) }}" />
        </div>
        <div>
            <label>Content </label>
            <textarea name="content"rows="100" cols="80">
                {{ old('content', $post->content) }}
            </textarea>
        </div>
        <div>
            <button type="submit">Save</button>            
        </div>
    </div>
</form>
                            

This will suffice to create and edit cases. So combining create and edit forms is worth it. Do pay attention to the second or default parameter in `old()` that will show either the default value in the add case or the populated value in the edit case or the failed-validation-value if form validation fails.

Blade views are now down to two or three.

  • index.blade.php to list all posts
  • form.blade.php to load new or existing form
  • optional show.blade.php to show single post view

Next, we will take a look at SaveFormRequest.

                                class SavePostRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'title:required,string',
            'content:required,string',
        ];
    }
    public function handle($id)
    {
        $data = $this->validated();

        if($id > 0){
            Post::where('id', $id)->update($data);
        }else{
            Post::create($data);
        }
        return true;
    } 
}
                            

This will add or update a post based on $id.

Summary

  • No more resource controller
  • Only 4 methods in each controller
  • Only 2 blade views for each resource
  • Form always loads resource or shows defaults

Final Thoughts

Laravel gives a lot out of the box. If utilized cleverly, you can minimize code as much as possible while still achieving a clean structure with full functionality. I hope you enjoyed this structure — and more importantly, I hope it makes your development smoother and your codebase cleaner.

What’s Next

In the next post, I’ll take this structure even further — making it a little more useful, a little more flexible, without adding any extra noise. Stay tuned. If you want to get notified, do subscribe to my profile.

Comments


Comment created and will be displayed once approved.