Skip to main content

SessionFactory with Chainable Methods - Usage Guide

Overview

The SessionFactory now supports chainable methods to inject dependencies before calling create(). This prevents unnecessary database records from being created.

Methods Available

withEvent(Event $event)

Pass an existing Event to use instead of auto-creating one.

withTimeslot(Timeslot $timeslot)

Pass an existing Timeslot to use instead of auto-creating one.

withLocation(int $locationId)

Pass a location ID to use instead of getting it from the event.

Usage Examples

1. Basic Usage (Auto-creates everything)

$session = Session::factory()->create();
// Creates: Event → EventType, EventCategory, Location
// Creates: Timeslot
// Creates: Session

2. With Existing Event

$event = Event::factory()->create();

$session = Session::factory()
->withEvent($event)
->create();
// Uses: $event
// Creates: Timeslot (for the event)
// Creates: Session

3. With Existing Event and Timeslot

$event = Event::factory()->create();
$timeslot = Timeslot::factory()->create(['event_id' => $event->id]);

$session = Session::factory()
->withEvent($event)
->withTimeslot($timeslot)
->create();
// Uses: $event, $timeslot
// Creates: Session only

4. With Custom Location

$location = Location::factory()->create();
$event = Event::factory()->create(['location_id' => $location->id]);

$session = Session::factory()
->withEvent($event)
->withLocation($location->id)
->create();
// Uses: $event, custom location_id
// Creates: Timeslot, Session

5. Full Control (Everything Provided)

$location = Location::factory()->create();
$event = Event::factory()->create(['location_id' => $location->id]);
$timeslot = Timeslot::factory()->create(['event_id' => $event->id]);

$session = Session::factory()
->withEvent($event)
->withTimeslot($timeslot)
->withLocation($location->id)
->create();
// Uses: Everything provided
// Creates: Session only (no extra database records!)

6. Create Multiple Sessions for Same Event

$event = Event::factory()->create();
$timeslot = Timeslot::factory()->create(['event_id' => $event->id]);

// Create 5 sessions for the same event/timeslot
$sessions = Session::factory()
->withEvent($event)
->withTimeslot($timeslot)
->count(5)
->create();
// Creates: 5 sessions, all using same event/timeslot

7. With Custom Attributes

$event = Event::factory()->create();

$session = Session::factory()
->withEvent($event)
->create([
'status' => 0, // Override default status
'seat_quantity' => 20, // Override default seat quantity
]);

Use in Tests

Example: Test Session Generation with Blackout Dates

public function test_sessions_respect_blackout_dates(): void
{
// Setup
$location = Location::factory()->create();
$event = Event::factory()->create([
'location_id' => $location->id,
'uses_subscription_model' => 1,
]);
$timeslot = Timeslot::factory()->create([
'event_id' => $event->id,
'weekday' => 1, // Monday
'time' => '10:00:00',
]);

// Create existing session using factory
$existingSession = Session::factory()
->withEvent($event)
->withTimeslot($timeslot)
->withLocation($location->id)
->create();

// Now test your logic
SessionRepository::generateSubscriptionSessions();

// Assertions...
}

Benefits

✅ Prevents Database Bloat

Only creates the records you actually need for the test.

✅ Faster Tests

Fewer database inserts = faster test execution.

✅ More Control

Explicitly control which records are shared vs. created.

✅ Readable Code

Chain reads naturally: Session::factory()->withEvent($event)->create()

✅ Type Safety

Methods accept typed parameters (Event, Timeslot objects, not just IDs)

How It Works Internally

class SessionFactory extends Factory
{
protected ?Event $event = null; // Stores injected event
protected ?Timeslot $timeslot = null; // Stores injected timeslot
protected ?int $locationId = null; // Stores injected location ID

public function withEvent(Event $event): self
{
$this->event = $event;
return $this; // Returns $this for chaining
}

public function definition()
{
// Use injected event OR create new one
$event = $this->event ?? Event::factory()->create();

// Same pattern for timeslot and location
// ...
}
}

Pattern for Other Factories

You can apply this same pattern to other factories:

// TimeslotFactory
$timeslot = Timeslot::factory()
->withEvent($event)
->create();

// SubscriptionFactory
$subscription = Subscription::factory()
->withAccount($account)
->withOrder($order)
->withTimeslot($timeslot)
->create();

Comparison

❌ Old Way (Pass IDs in create)

// This creates Event first, then you pass its ID
$event = Event::factory()->create();
$session = Session::factory()->create([
'event_id' => $event->id, // Passed as array
]);
// Problem: Factory still creates a NEW event internally, then overwrites it

✅ New Way (Inject before create)

// Factory uses YOUR event, doesn't create a new one
$event = Event::factory()->create();
$session = Session::factory()
->withEvent($event) // Injected before definition() runs
->create();
// Factory sees $this->event is set, uses it directly

The new way is cleaner, more efficient, and prevents database bloat! 🎉