🧳 Real-life Use Case: Travel Booking Service
Let’s say we’re building an API where users can book a tour. Instead of putting everything in the controller, we’ll:
- Create a
BookingService
for booking logic. - Define a
BookingServiceInterface
. - Bind it using a Service Provider.
- Use a Form Request class to handle validation.
- Inject the service into a controller for actual usage.
1. Create the Interface
php artisan make:interface Services/BookingServiceInterface
app/Services/BookingServiceInterface.php
:
<?php
namespace App\Services;
interface BookingServiceInterface
{
public function createBooking(array $data);
}
2. Create the Service Class
php artisan make:service Services/BookingService
app/Services/BookingService.php
:
<?php
namespace App\Services;
use App\Models\Booking;
class BookingService implements BookingServiceInterface
{
public function createBooking(array $data)
{
return Booking::create([
'user_id' => $data['user_id'],
'tour_id' => $data['tour_id'],
'date' => $data['date'],
'people' => $data['people'],
]);
}
}
3. Create a Custom Form Request for Validation
php artisan make:request StoreBookingRequest
app/Http/Requests/StoreBookingRequest.php
:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreBookingRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'user_id' => 'required|integer',
'tour_id' => 'required|integer',
'date' => 'required|date',
'people' => 'required|integer|min:1',
];
}
}
✅ Now all validation logic is separated and reusable!
4. Create a Service Provider
php artisan make:provider BookingServiceProvider
app/Providers/BookingServiceProvider.php
:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Services\BookingService;
use App\Services\BookingServiceInterface;
class BookingServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind(BookingServiceInterface::class, BookingService::class);
}
public function boot()
{
//
}
}
Then register this provider in config/app.php
:
App\Providers\BookingServiceProvider::class,
5. Use the Service in a Controller
php artisan make:controller BookingController
app/Http/Controllers/BookingController.php
:
<?php
namespace App\Http\Controllers;
use App\Services\BookingServiceInterface;
use App\Http\Requests\StoreBookingRequest;
class BookingController extends Controller
{
protected $bookingService;
public function __construct(BookingServiceInterface $bookingService)
{
$this->bookingService = $bookingService;
}
public function store(StoreBookingRequest $request)
{
$booking = $this->bookingService->createBooking($request->validated());
return response()->json([
'message' => 'Booking created successfully!',
'data' => $booking,
]);
}
}
Now the controller is super clean — all it does is call the service with validated data.
✅ Why This Pattern Works in the Real World
- Separation of Concerns: Controllers don’t care about validation or logic.
-
Testable: Easily mock
BookingServiceInterface
in tests. - Reusable: Same service can be used elsewhere — even in commands or jobs.
-
Maintainable: Validation logic is centralized in
StoreBookingRequest
.
🧪 Bonus: Mocking the Service in a Test
public function test_booking_creation()
{
$mock = \Mockery::mock(BookingServiceInterface::class);
$mock->shouldReceive('createBooking')->once()->andReturn(['id' => 1]);
$this->app->instance(BookingServiceInterface::class, $mock);
$response = $this->postJson('/api/bookings', [
'user_id' => 1,
'tour_id' => 2,
'date' => '2025-06-01',
'people' => 2,
]);
$response->assertStatus(200);
}
🧵 Wrapping Up
This is how you build Laravel applications that scale without becoming messy.
Top comments (0)