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! 🎉