---
# System prepended metadata

title: 'Lecture 9 & 10: Dive Deep in Laravel'

---

# Lecture 9: Dive Deep in Laravel

Welcome to Lecture 9! In this lecture, we will build the part of the Booking System for our Expert Platform using Laravel 12. This includes creating the booking model, defining relationships, securing the booking endpoint with authentication, implementing a service layer for business logic, and reusing unified response handling with a trait.

By the end of this lecture, you'll:

- Create bookings with validations
- Implement relationships between user, expert, and booking
- Learn service classes and dependency injection in Laravel
- Implement search, filters, pagination, and ordering
- Use traits for unified API responses

---

## Step 1: Booking Model and Migration

### Generate Booking Model

```bash
php artisan make:model Booking -m
```

### Migration File: `database/migrations/xxxx_xx_xx_create_bookings_table.php`

```php
Schema::create('bookings', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->constrained()->onDelete('cascade');
    $table->foreignId('expert_id')->constrained()->onDelete('cascade');
    $table->dateTime('scheduled_at');
    $table->enum('status', ['pending', 'confirmed', 'cancelled'])->default('pending');
    $table->text('notes')->nullable();
    $table->timestamps();
});
```

Run the migration:

```bash
php artisan migrate
```

---

## Step 2: Model Relationships

### In `User.php`

```php
public function bookings() {
    return $this->hasMany(Booking::class);
}
```

### In `Expert.php`

```php
public function bookings() {
    return $this->hasMany(Booking::class);
}
```

### In `Booking.php`

```php
public function user() {
    return $this->belongsTo(User::class);
}

public function expert() {
    return $this->belongsTo(Expert::class);
}
```

---

## Step 3: Booking Controller

### Generate Controller:

```bash
php artisan make:controller Api/BookingController
```

### Add Routes in `routes/api.php`

```php
Route::middleware('auth:sanctum')->group(function () {
    Route::post('/bookings', [BookingController::class, 'store']);
    Route::get('/bookings', [BookingController::class, 'index']);
});
```

---

## Step 4: Service Layer (Recommended Practice)

### Why Use a Service Layer?

To keep your controller clean and move the business logic to a separate class.

### Create BookingService Class

```bash
php artisan make:service BookingService
```

### Implementation:

**app/Services/BookingService.php**

```php
namespace App\Services;

use App\Models\Booking;
use Illuminate\Support\Facades\Auth;

class BookingService {
    public function create(array $data): Booking {
        return Booking::create([
            'user_id' => Auth::id(),
            'expert_id' => $data['expert_id'],
            'scheduled_at' => $data['scheduled_at'],
            'notes' => $data['notes'] ?? null,
        ]);
    }

    public function getFilteredBookings($filters) {
        return Booking::with(['expert', 'user'])
            ->when($filters['status'] ?? null, fn($q, $status) => $q->where('status', $status))
            ->when($filters['expert_id'] ?? null, fn($q, $id) => $q->where('expert_id', $id))
            ->orderBy($filters['order_by'] ?? 'scheduled_at', $filters['order'] ?? 'desc')
            ->paginate($filters['per_page'] ?? 10);
    }
}
```

### Inject it in the Controller:

**BookingController.php**

```php
use App\Services\BookingService;

class BookingController extends Controller {
    public function __construct(protected BookingService $bookingService) {}

    public function store(Request $request) {
        $data = $request->validate([
            'expert_id' => 'required|exists:experts,id',
            'scheduled_at' => 'required|date|after:now',
            'notes' => 'nullable|string'
        ]);

        $booking = $this->bookingService->create($data);
        return $this->successResponse($booking, 'Booking created successfully.');
    }

    public function index(Request $request) {
        $bookings = $this->bookingService->getFilteredBookings($request->all());
        return $this->successResponse($bookings);
    }
}
```

---

## Step 5: Unified JSON Response Using Traits

### Why Traits?

Avoid repeating response formatting logic in every controller.

### Create Trait

**app/Traits/ApiResponseTrait.php**

```php
namespace App\Traits;

trait ApiResponseTrait {
    public function successResponse($data, $message = null, $code = 200) {
        return response()->json([
            'success' => true,
            'message' => $message,
            'data' => $data
        ], $code);
    }

    public function errorResponse($message, $code = 400) {
        return response()->json([
            'success' => false,
            'message' => $message,
            'data' => null
        ], $code);
    }
}
```

### Use Trait in Controller

```php
use App\Traits\ApiResponseTrait;

class BookingController extends Controller {
    use ApiResponseTrait;
    ...
}
```

---

## Step 6: Final Test With Postman

1. Register and login a user to get token (via Lecture 8)
2. Make authenticated `POST /api/bookings`
3. Make authenticated `GET /api/bookings?status=pending&order_by=scheduled_at&order=asc&per_page=5`

---



