Laravel Structure: Move Code From Controller to... Where?

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 MonthlyRepost::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...

The full tutorial [13 mins, 2509 words] is only for Premium Members

Become a Premium Member for $129/year or $29/month

Recent Premium Tutorials