Laravel Business Logic: A Cleaner, Smarter Approach
August 30th, 2020 — 4 min read
While working with Laravel, I often found myself wondering where exactly to put my business logic. If you’ve felt the same way, you’re not alone! Laravel’s documentation doesn’t provide a clear answer, leaving it up to developers to decide. For business logic many people turn to the Repository Pattern or use controller method making controllers thick, but I am not a fan of these approaches. So, I went looking for a better solution, and I found one that makes perfect sense. Let me share it with you!
The power of Form Request Validation
Laravel has this wonderful feature called Form Request Validation. You’ve probably used it to authorize request, define validation rules and customize error messages. Here is a sample of this class.
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StorePostRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
];
}
}
Then it is called it in the controller like this:
<?php
namespace App\Http\Controllers;
use App\Http\Requests\StorePostRequest;
use App\Http\Controllers\Controller;
class StoreController extends Controller
{
/**
* Store a new blog post.
*/
public function store(StorePostRequest $request): RedirectResponse
{
// The incoming request is valid...
// Retrieve the validated input data...
$validated = $request->validated();
// your business logic here
return redirect('/posts');
}
}
Pretty handy, right? But here’s where I had a lightbulb moment. Instead of just calling the validated() method in the controller, why not take it a step further and let the Form Request handle some business logic?
The Handle Method Trick
What I did was simple: I added a handle method in my Form Request class. In this method, I called the validated() function and performed the business logic. Here's modified code for request class:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StorePostRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
];
}
public function handle()
{
$data = $this->validated();
// Your business logic here
return $data;
}
}
Now call this method in controller
<?php
namespace App\Http\Controllers;
use App\Http\Requests\StorePostRequest;
use App\Http\Controllers\Controller;
class StoreController extends Controller
{
/**
* Store a new blog post.
*/
public function store(StorePostRequest $request): RedirectResponse
{
$request->handle();
return redirect('/posts');
}
}
How Does It Work?
At this point, you might be wondering how we access request data in the handle
method. We’re not passing the request object! The answer is simple — $this
is your friend here. Since we’re already inside the context of the BaseRequest, you can use $this
to access any data from the request.
public function handle()
{
$data = $this->validated();
$params = $this->all();
// Your business logic here
return $data;
}
This approach keeps your controllers lean and lets your Form Request take care of both validation and related business logic. Pretty neat, right? Here you must rest assured this method will function correctly. If validation fails it will redirect back with validation errors. But if validation passes handle method will be executed and it will redirect to posts route.
Finally my controller is totally lean now. Here is a sample.
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Http\Requests\Post\GetAllPostsRequest;
use App\Http\Requests\Post\GetPostRequest;
use App\Http\Requests\Post\CreatePostRequest;
use App\Http\Requests\Post\UpdatePostRequest;
use App\Http\Requests\Post\DeletePostRequest;
class PostController extends Controller
{
public function index(GetAllPostsRequest $request)
{
$posts = $request->handle();
return view('post.index', compact('posts') );
}
public function create()
{
return view('post.create');
}
public function store(CreatePostRequest $request)
{
$request->handle();
return redirect()->route('posts.index');
}
public function show(GetPostRequest $request, $id)
{
$post = $request->handle($id);
return view('post.show', compact('post') );
}
public function edit($id, GetPostRequest $request)
{
$post = $request->handle($id);
return view('post.edit', compact('post') );
}
public function update(UpdatePostRequest $request, $id)
{
$request->handle($id);
return redirect()->route('posts.index');
}
public function destroy(DeletePostRequest $request, $id)
{
$request->handle($id);
return redirect()->route('posts.index');
}
}
All my request classes have now handle method containing my business logc.
I hope this little trick helps someone out there struggling with the same dilemma. Happy coding! 😊