Courses

Laravel Web to Mobile API: Reuse Old Code with Services

Restaurant Service

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.

Without Service

In the best-case scenario, we want to avoid duplicate code and merge that logic into a single entity.

With Service

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);
}

No comments or questions yet...