🛠️ Build Better Laravel Apps with Service Interfaces, Providers & Requests
Tahsin Abrar

Tahsin Abrar @tahsin000

Joined:
May 17, 2023

🛠️ Build Better Laravel Apps with Service Interfaces, Providers & Requests

Publish Date: May 23
13 0

🧳 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
Enter fullscreen mode Exit fullscreen mode

app/Services/BookingServiceInterface.php:

<?php

namespace App\Services;

interface BookingServiceInterface
{
    public function createBooking(array $data);
}
Enter fullscreen mode Exit fullscreen mode

2. Create the Service Class

php artisan make:service Services/BookingService
Enter fullscreen mode Exit fullscreen mode

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'],
        ]);
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Create a Custom Form Request for Validation

php artisan make:request StoreBookingRequest
Enter fullscreen mode Exit fullscreen mode

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',
        ];
    }
}
Enter fullscreen mode Exit fullscreen mode

✅ Now all validation logic is separated and reusable!


4. Create a Service Provider

php artisan make:provider BookingServiceProvider
Enter fullscreen mode Exit fullscreen mode

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()
    {
        //
    }
}
Enter fullscreen mode Exit fullscreen mode

Then register this provider in config/app.php:

App\Providers\BookingServiceProvider::class,
Enter fullscreen mode Exit fullscreen mode

5. Use the Service in a Controller

php artisan make:controller BookingController
Enter fullscreen mode Exit fullscreen mode

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,
        ]);
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

🧵 Wrapping Up

This is how you build Laravel applications that scale without becoming messy.

Comments 0 total

    Add comment