One of the most common questions I see about Laravel is how to structure the project. Or, in other words, where to put the logic out of the Controllers? In this article, I will try to show the options, trying to shorten one Controller method as an example.
This is a text-form tutorial based on the section of my video course How To Structure Laravel Projects
We will discuss how to move the logic from the Controller, to...:
- Form Requests
- Eloquent Mutators
- Eloquent Observers
- Service Classes
- Action Classes
- Jobs
- Events and Listeners
- Global Helpers
- Traits
- Base Controllers
- Repository Classes
A lot to cover, huh? So let's get started.
Initial Controller code
Before starting to cleanup the Controller, here's the code which we will try to make shorter:
1public function store(Request $request) 2{ 3 $this->authorize('user_create'); 4 5 $userData = $request->validate([ 6 'name' => 'required', 7 'email' => 'required|unique:users', 8 'password' => 'required', 9 ]);10 11 $userData['start_at'] = Carbon::createFromFormat('m/d/Y', $request->start_at)->format('Y-m-d');12 $userData['password'] = bcrypt($request->password);13 14 $user = User::create($userData);15 $user->roles()->sync($request->input('roles', []));16 17 Project::create([18 'user_id' => $user->id,19 'name' => 'Demo project 1',20 ]);21 Category::create([22 'user_id' => $user->id,23 'name' => 'Demo category 1',24 ]);25 Category::create([26 'user_id' => $user->id,27 'name' => 'Demo category 2',28 ]);29 30 MonthlyReport::where('month', now()->format('Y-m'))->increment('users_count');31 $user->sendEmailVerificationNotification();32 33 $admins = User::where('is_admin', 1)->get();34 Notification::send($admins, new AdminNewUserNotification($user));35 36 return response()->json([37 'result' => 'success',38 'data' => $user,39 ], 200);40}
Quite a big method, right? Now, let's walk through the options to shorten it.
Notice: at the end of the day, it's your personal preference where to move the code, you MAY choose any option listed below.
Validation to Form Request
We will start by extracting validation into Form Request. In this example, validation is simple, with three fields, but in real life, you could have 10+ fields.
Actually, we have two parts of the validation:
- permissions
- input data validation
Both of them CAN be moved to the Form Request class.
Let's start by creating a Form Request:
1php artisan make:request StoreUserRequest
Now we have the app\Http\Requests\StoreUserRequest.php file which has two methods inside: authorize() for permissions and rules() for data validation. So, Form Request would look like this:
app\Http\Requests\StoreUserRequest.php:
1class StoreUserRequest extends FormRequest 2{ 3 public function authorize() 4 { 5 return Gate::allows('user_create'); 6 } 7 8 public function rules() 9 {10 return [11 'name' => 'required',12 'email' => 'required|unique:users',13 'password' => 'required',14 ];15 }16}
In the Controller, instead of the default Request class, we need to inject our StoreUserRequest, and validated data can be accessed using the validated() method from the Request. Now, the Controller will look like this:
1public function store(StoreUserRequest $request) 2{ 3 $userData = $request->validated(); 4 5 $userData['start_at'] = Carbon::createFromFormat('m/d/Y', $request->start_at)->format('Y-m-d'); 6 $userData['password'] = bcrypt($request->password); 7 8 $user = User::create($userData); 9 $user->roles()->sync($request->input('roles', []));10 //11}
A few first lines of the Controller shortened. Let's move on.
Data Change to Mutators or Observers
Let's say that you want to transform some data before saving it into the database.
Two examples here formatting the date and encrypting the password.
Now, we do it in the Controller, but let's use Laravel Eloquent features for that. I will show you two methods, one using Mutators and the another using Observers.
Mutators
In Eloquent models, you can...
Premium Members Only
This advanced tutorial is available exclusively to Laravel Daily Premium members.
Already a member? Login here
Premium membership includes:
Comments & Discussion
Thanks for filling out such a comprehensive list of possibilities.
Please let me add that Responsable interface introduced in Laravel 5.5 is also a viable approach for fat Laravel controllers reorganization. Basically a Responsable object has to implement toResponse() method which will be called by framework Router as shown here: https://github.com/laravel/framework/blob/5.7/src/Illuminate/Routing/Router.php#L734-L736
In Laravel, the Illuminate\Contracts\Support\Responsable interface provides a convenient way to return a HTTP response which reduces the controller method body to only one line of code mostly. The response layer could also contain transformation code or content negotiation instead of using these features directly in the controller. Elaborating on your example that illustrates a user CRUD use-case, I would like to share with you a POC of the idea here hosting it on "replit.com": https://replit.com/@garata/Laravel-10-Responsable-Interface. This example returns four custom responses you can find hosted within App\Http\Responses namespace. Even if the implementation technique is different here, I also like a lot your APIResponsesTrait approach which is more Laravel style exploting trait PHP language feature.
In order to start my replit shared project, just press on the large play button and then use Show code button placed on the right just in case you're curious about the usage of Laravel's Responsable interface to create view model objects or dedicated HTTP response objects.
Best regards.
thanks for sharing detailed on the above topics