The word "service" is often used in the Laravel community, but is it a "design pattern"?
Yes and no.
It is a pattern in the sense that it's a typical approach to offload the logic from the Controller to "somewhere else", that "somewhere" being the Service class.
However, identically to the Action classes, Services are not a core Laravel function, so there's no php artisan make:service
, and developers can name/structure them however they want.
I've seen two main use cases for Service classes:
UserService
: A class with multiple methods around a certain Model or other subject (more "loose" pattern)StripeService
: A class that helps to work with external functionality or a 3rd party library that can be replaced with another library in the future (more "strict" pattern)And let's see both in action.
A typical simple example is this:
app/Http/Controllers/UserController.php:
use App\Http\Requests\StoreUserRequest;use App\Services\UserService; class UserController extends Controller{ public function store(StoreUserRequest $request, UserService $userService) { $userService->store($request->validated()); return redirect()->route('users.index'); } // ...}
Then, inside that UserService
class, you have methods to store users, update users, and perform other operations related to the User model.
app/Services/UserService.php:
namespace App\Services; class UserService { public function store(array $userData): User { $user = User::create($userData); $user->roles()->sync($userData['roles']); // More actions with that user: let's say, 5+ more lines of code // - Upload avatar // - Email to the user // - Notify admins about new user // - Create some data for that user // - and more... return $user; } public function update(array $userData, User $user): User { $user->update($userData); $user->roles()->sync($userData['roles']); // Also, more actions with that user }}
So, you've seen this code:
public function store(StoreUserRequest $request, UserService $userService){ // ...
So, how does that "method injection" work, and why don't we need to write new UserService()
?
This is powered by a Laravel feature called Container, sometimes also called "Service Container," but it has nothing to do with the Service classes we're discussing here. Naming is hard, I know.
Without delving too deeply into how it is implemented, you need to know that some/most Laravel classes can auto-create class instances if you type-hint their classes in the parameter list.
The code above is actually a 2-in-1 example:
(Request $request)
type-hinting in ControllersInternally, Laravel just performs $request = new Request()
for us. That's it. Not much more you need to know.
Similarly, you may type-hint dependencies in the handle()
method of queued jobs.
This is called method injection, but you can also type-hint and inject classes in the constructor of a class that is resolved by the container, including Controllers, Event listeners, Middleware, and more. Then, there's no need to type-hint it in every method.
use App\Http\Requests\StoreUserRequest;use App\Http\Requests\UpdateUserRequest;use App\Models\User;use App\Services\UserService; class UserController extends Controller{ public function __construct( protected UserService $userService, ) {} public function store(StoreUserRequest $request) { $this->userService->store($request->validated()); return redirect()->route('users.index'); } public function update(UpdateUserRequest $request, User $user) { $this->userService->update($request->validated(), $user); return redirect()->route('users.index'); } // ...}
Final "notice": this is enabled only in Laravel classes processed in Container, but if you type-hint a Service class inside of another Service class method/constructor, for example, Laravel will not recognize it and will throw an error.
use app\Services\TwitterService; class UserService { public function store(array $userData, TwitterService $twitter): User { // THIS WILL NOT WORK // You need to manually write $twitter = new TwitterService();
The key point is that the Service method is a "black box":
It's a bit similar to Actions/Jobs from a previous chapter. The purpose of such separation is the same: offload the logic from the Controller to "somewhere else". But there are two differences between Services and Actions/Jobs:
handle()
or execute()
void
However, the example above is still a "Wild West": if a developer sees a Service class used, they wouldn't immediately know what's inside, as every developer individually creates every Service, and there's no strict pattern or set of rules.
But if that's a problem, we can introduce the rules if needed!
And let's discuss a more "strict" type of Services in the next lesson.