In this lesson, our goal is to refactor Controllers into Service classes.
Let's look into this diagram. The problem is that two restaurant controllers are implementing the same business logic highlighted in the red area of the illustration.
In the best-case scenario, we want to avoid duplicate code and merge that logic into a single entity.
If we have changes to business logic, it also brings the benefit of not updating it in multiple places.
RestaurantService Class
Create a new RestaurantService
class.
app/Services/RestaurantService.php
namespace App\Services; use App\Enums\RoleName;use App\Models\Restaurant;use App\Models\Role;use App\Models\User;use App\Notifications\RestaurantOwnerInvitation;use Illuminate\Support\Facades\DB; class RestaurantService{ public function createRestaurant(array $attributes): Restaurant { return DB::transaction(function () use ($attributes) { $user = User::create([ 'name' => $attributes['owner_name'], 'email' => $attributes['email'], 'password' => '', ]); $user->roles()->sync(Role::where('name', RoleName::VENDOR->value)->first()); $restaurant = $user->restaurant()->create([ 'city_id' => $attributes['city_id'], 'name' => $attributes['restaurant_name'], 'address' => $attributes['address'], ]); $user->notify(new RestaurantOwnerInvitation($attributes['restaurant_name'])); return $restaurant; }); } public function updateRestaurant(Restaurant $restaurant, array $attributes): Restaurant { $restaurant->update([ 'city_id' => $attributes['city_id'], 'name' => $attributes['restaurant_name'], 'address' => $attributes['address'], ]); return $restaurant; }}
Here we have implemented the createRestaurant
and updateRestaurant
methods that we can call from controllers.
Web Controller
Now we can use constructor property promotion on the Controller to inject RestaurantService
and imported classes we no longer need there.
app/Http/Controllers/Admin/RestaurantController.php
namespace App\Http\Controllers\Admin; use App\Enums\RoleName; use App\Http\Controllers\Controller;use App\Http\Requests\Admin\StoreRestaurantRequest;use App\Http\Requests\Admin\UpdateRestaurantRequest;use App\Models\City;use App\Models\Restaurant;use App\Models\Role; use App\Models\User; use App\Notifications\RestaurantOwnerInvitation; use App\Services\RestaurantService;use Illuminate\Http\RedirectResponse;use Illuminate\Support\Facades\DB; use Inertia\Inertia;use Inertia\Response; class RestaurantController extends Controller{ public function __construct(public RestaurantService $restaurantService) { } // ...
Update the store
method and call the createRestaurant
method on injected service.
app/Http/Controllers/Admin/RestaurantController.php
public function store(StoreRestaurantRequest $request): RedirectResponse{ DB::transaction(function () use ($validated) { $user = User::create([ 'name' => $validated['owner_name'], 'email' => $validated['email'], 'password' => '', ]); $user->roles()->sync(Role::where('name', RoleName::VENDOR->value)->first()); $user->restaurant()->create([ 'city_id' => $validated['city_id'], 'name' => $validated['restaurant_name'], 'address' => $validated['address'], ]); $user->notify(new RestaurantOwnerInvitation($validated['restaurant_name'])); }); $this->restaurantService->createRestaurant( $request->validated() ); return to_route('admin.restaurants.index');}
In the same way, we update the update
method.
app/Http/Controllers/Admin/RestaurantController.php
public function update(UpdateRestaurantRequest $request, Restaurant $restaurant): RedirectResponse{ $validated = $request->validated(); $restaurant->update([ 'city_id' => $validated['city_id'], 'name' => $validated['restaurant_name'], 'address' => $validated['address'], ]); $this->restaurantService->updateRestaurant( $restaurant, $request->validated() ); return to_route('admin.restaurants.index') ->withStatus('Restaurant updated successfully.');}
API Controller
Then we apply the same changes to the API controller.
app/Http/Controllers/Api/V1/Admin/RestaurantController.php
use App\Enums\RoleName; use App\Http\Controllers\Controller;use App\Http\Requests\Admin\StoreRestaurantRequest;use App\Http\Requests\Admin\UpdateRestaurantRequest;use App\Http\Resources\Api\V1\Admin\RestaurantCollection;use App\Http\Resources\Api\V1\Admin\RestaurantResource;use App\Models\Restaurant;use App\Models\Role; use App\Models\User; use App\Notifications\RestaurantOwnerInvitation; use App\Services\RestaurantService;use Illuminate\Http\JsonResponse;use Illuminate\Http\Response;use Illuminate\Support\Facades\DB; class RestaurantController extends Controller{ public function __construct(public RestaurantService $restaurantService) { } // ...
app/Http/Controllers/Api/V1/Admin/RestaurantController.php
public function store(StoreRestaurantRequest $request): RestaurantResource{ $validated = $request->validated(); $restaurant = DB::transaction(function () use ($validated) { $user = User::create([ 'name' => $validated['owner_name'], 'email' => $validated['email'], 'password' => '', ]); $user->roles()->sync(Role::where('name', RoleName::VENDOR->value)->first()); $user->restaurant()->create([ 'city_id' => $validated['city_id'], 'name' => $validated['restaurant_name'], 'address' => $validated['address'], ]); $user->notify(new RestaurantOwnerInvitation($validated['restaurant_name'])); }); $restaurant = $this->restaurantService->createRestaurant( $request->validated() ); return new RestaurantResource($restaurant);}
app/Http/Controllers/Api/V1/Admin/RestaurantController.php
public function update(UpdateRestaurantRequest $request, Restaurant $restaurant): JsonResponse{ $validated = $request->validated(); $restaurant->update([ 'city_id' => $validated['city_id'], 'name' => $validated['restaurant_name'], 'address' => $validated['address'], ]); $restaurant = $this->restaurantService->updateRestaurant( $restaurant, $request->validated() ); return (new RestaurantResource($restaurant)) ->response() ->setStatusCode(Response::HTTP_ACCEPTED);}
Dear Povilas I hope this comment finds well i have looked through this service class and i wondering what if i have multiple return of status code let me show an example
Auth Controller
Register Service Class
While this is an okay approach - it has a huge drawback. It will limit you to just API response and no other use cases.
It would be better to:
200
response status as simplereturn
statementthrow ValidationException()
That way, you can still re-use this method anywhere if needed.
ps. You could also separate this into a few smaller functions for even better structure