🧳 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
BookingServicefor 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
BookingServiceInterfacein 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)