1. Go to this page and download the library: Download andydefer/laravel-roster library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
andydefer / laravel-roster example snippets
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Roster\Traits\HasRoster;
class Doctor extends Model
{
use HasRoster;
}
// Create an availability for a doctor
$availability = availability_for($doctor)->create([
'type' => 'consultation',
'daily_start' => '09:00:00',
'daily_end' => '17:00:00',
'days' => ['monday', 'wednesday', 'friday'],
'validity_start' => '2038-01-01',
'validity_end' => '2038-12-31',
]);
// Book a slot in this availability
$schedule = schedule_for($availability)->create([
'title' => 'Annual Checkup - Patient A',
'start_datetime' => '2038-01-04 10:00:00',
'end_datetime' => '2038-01-04 11:00:00',
'status' => \Roster\Enums\ScheduleStatus::BOOKED,
'metadata' => ['patient_id' => 123],
]);
// Block a slot for training
$impediment = impediment_for($availability)->create([
'reason' => 'Mandatory medical training',
'start_datetime' => '2038-01-04 09:00:00',
'end_datetime' => '2038-01-04 12:00:00',
]);
// Find the next available slot
$nextSlot = schedule_for($availability)->findNextSlot(
durationMinutes: 45,
type: 'consultation',
startFrom: now()->addDay()
);
// Check availability for a specific slot
$isAvailable = schedule_for($availability)->isTimeSlotAvailable(
start: '2038-01-06 14:00:00',
end: '2038-01-06 15:00:00',
type: 'consultation'
);
// In AbstractRule.php - Protected against configuration errors
private const ABSOLUTE_MIN_DURATION_MINUTES = 10;
protected function getMinimumDuration(EntityType $entityType): int
{
$configuredMinutes = match ($entityType) {
EntityType::AVAILABILITY => config('roster.durations.minimum_availability_minutes', 10),
EntityType::SCHEDULE => config('roster.durations.minimum_schedule_minutes', 10),
EntityType::IMPEDIMENT => config('roster.durations.minimum_impediment_minutes', 5),
};
// FORCE absolute minimum - Configuration cannot go below 10 minutes
if ($configuredMinutes < self::ABSOLUTE_MIN_DURATION_MINUTES) {
$actualMinutes = $configuredMinutes;
$configuredMinutes = self::ABSOLUTE_MIN_DURATION_MINUTES;
// Automatic warning when configuration is overridden
logger()->warning('Minimum duration configuration overridden for performance reasons', [
'entity_type' => $entityType->value,
'configured_minutes' => $actualMinutes,
'enforced_minutes' => self::ABSOLUTE_MIN_DURATION_MINUTES,
'reason' => 'Durations below 10 minutes would generate too many iterations and slow down the system',
]);
}
return $configuredMinutes;
}
// In config/roster.php
'durations' => [
'minimum_availability_minutes' => 5, // ❌ Will be forced to 10
'minimum_schedule_minutes' => 3, // ❌ Will be forced to 10
'minimum_impediment_minutes' => 1, // ❌ Will be forced to 10
],
// The system automatically:
// 1. Detects the configuration below 10 minutes
// 2. Logs a warning for debugging
// 3. Enforces 10 minutes as the actual minimum
// 4. Prevents infinite loops and performance issues
// Attempt to create an availability with 5 minutes duration
$context = $this->createMock(ValidationContextInterface::class);
$context->method('getEntityType')->willReturn(EntityType::AVAILABILITY);
$context->method('safeData')->willReturn([
'start_time' => '09:00:00',
'end_time' => '09:05:00', // 5 minutes - BELOW absolute minimum
]);
// This will FAIL with a clear error message:
// "Minimum duration of 10 minutes
use Roster\Traits\AttachableToSchedules;
// Add the trait to your models
class Room extends Model
{
use AttachableToSchedules;
}
class Vehicle extends Model
{
use AttachableToSchedules;
}
class Equipment extends Model
{
use AttachableToSchedules;
}
// Usage: attach resources to a schedule
$schedule = schedule_for($availability)->create([
'title' => 'Scheduled Surgery',
'start_datetime' => '2038-01-04 08:00:00',
'end_datetime' => '2038-01-04 12:00:00',
]);
// Attach resources with metadata
$room = Room::find(1);
$vehicle = Vehicle::find(1);
$doctor = Doctor::find(1);
$service = schedule_for($availability)->schedule($schedule);
$service->attach($room, ['role' => 'operating_room', 'equipment' => 'surgical']);
$service->attach($vehicle, ['role' => 'transport', 'urgent' => true]);
$service->attach($doctor, ['role' => 'surgeon', 'specialty' => 'orthopedics']);
// Attach multiple resources at once
$service->attachMany([$room, $vehicle, $doctor], ['operation_id' => 'OP123']);
// Check if a resource is attached
$service->hasAttached($room); // true
// Retrieve all attached resources
$attachedResources = $service->getAttached();
// Collection containing room, vehicle, doctor
// Filter by model type
$rooms = $service->getAttachedByType(Room::class);
$doctors = $service->getAttachedByType(Doctor::class);
// Detach resources
$service->detach($vehicle);
$service->detachMany([$room, $doctor]);
// Synchronize resources completely
$service->sync([$room, $doctor], ['session' => 'morning']);
// Detach all resources
$service->detachAll();
// From an attachable model
$room->isAttachedToSchedule($schedule); // true/false
$room->attachToSchedule($schedule, ['role' => 'consultation']);
$room->detachFromSchedule($schedule);
// Get all schedules with metadata
$schedulesWithMetadata = $room->attachedSchedulesWithLinkMetadata();
// Filter by metadata
$surgeries = $room->attachedSchedulesWithMetadata('role', 'operating_room');
// Synchronize schedules
$room->syncSchedules([$schedule1, $schedule2], ['default_room' => true]);
// The polymorphic relationship is automatically available
$room->attachedSchedules; // Collection of schedules
$schedule->linkables; // Collection of attached models (via pivot)
// With link metadata
$room->attachedSchedules()->withPivot('metadata')->get();
// Two different schedules sharing the same resources
$schedule1 = schedule_for($availability)->create([...]);
$schedule2 = schedule_for($availability)->create([...]);
$sharedRoom = Room::find(1);
$sharedEquipment = Equipment::find(1);
$service1 = schedule_for($availability)->schedule($schedule1);
$service2 = schedule_for($availability)->schedule($schedule2);
$service1->attach($sharedRoom, ['usage' => 'consultation']);
$service2->attach($sharedRoom, ['usage' => 'training']);
$service1->attach($sharedEquipment, ['reserved' => true]);
// The system tracks which resource is used where and when
// 1. Get all items (impediments + schedules) in a period
$items = $model->getRosterItemsInPeriod($start, $end);
// Returns: ['impediments' => Collection, 'schedules' => Collection]
// 2. Get only impediments in a period
$impediments = $model->getImpedimentsInPeriod($start, $end);
// 3. Get only schedules in a period
$schedules = $model->getSchedulesInPeriod($start, $end);
// 4. Check for conflicts
$hasConflicts = $model->hasConflictsInPeriod($start, $end);
// Returns true if at least one impediment or schedule exists
// A doctor with the HasRoster trait
$doctor = Doctor::find(1);
// Check availability for tomorrow 10am-11am
$start = Carbon::parse('2024-06-10 10:00:00');
$end = Carbon::parse('2024-06-10 11:00:00');
// Check for conflicts
if ($doctor->hasConflictsInPeriod($start, $end)) {
// Get details
$conflicts = $doctor->getRosterItemsInPeriod($start, $end);
echo "Conflicting schedules: " . $conflicts['schedules']->count();
echo "Conflicting impediments: " . $conflicts['impediments']->count();
} else {
echo "Time slot available";
}
// Before creating a new schedule
public function createSchedule(Doctor $doctor, array $data)
{
$start = Carbon::parse($data['start_datetime']);
$end = Carbon::parse($data['end_datetime']);
// Check if the time slot is free
if ($doctor->hasConflictsInPeriod($start, $end)) {
return response()->json([
'error' => 'Time slot not available',
'conflicts' => $doctor->getRosterItemsInPeriod($start, $end)
], 422);
}
// Create the schedule
return schedule_for($doctor->availabilities()->first())
->create($data);
}
// ❌ FORBIDDEN: Direct modification
$availability->update(['daily_end' => '18:00:00']); // Throws exception
// ✅ ALLOWED: Via service
availability_for($doctor)->update($availability->id, [
'daily_end' => '18:00:00'
]);
// ❌ FORBIDDEN: Service reuse
$service = availability_for($doctor);
$service->create([...]);
$service->update(1, [...]); // Corrupted context
// ✅ ALLOWED: New context for each action
availability_for($doctor)->create([...]);
availability_for($doctor)->update(1, [...]);
// 1. Mutation context (internal)
// Used by repositories to allow CRUD operations
RosterMutationContext::allow(function () {
return Availability::create([...]); // Allowed in this context
});
// 2. Service context (public)
// Used by helpers to allow service usage
RosterServiceContext::allow(function () {
return $service->create([...]); // Allowed via helper
});
// Retrieve the first availability matching criteria
$availability = availability_for($doctor)
->whereType('consultation')
->first();
// Retrieve the next upcoming appointment
$nextAppointment = schedule_for($availability)
->setFilter('start_datetime', '>', now())
->first();
// Retrieve the first scheduled impediment
$firstImpediment = impediment_for($availability)
->setFilter('reason', 'like', '%training%')
->first();
// During an update, days outside the period are automatically reconciled
$availability = availability_for($doctor)->create([
'validity_start' => '2024-01-01',
'validity_end' => '2024-01-07', // Week from January 1-7
'days' => ['monday', 'wednesday', 'friday'],
]);
// If you extend the period, days are automatically adjusted
availability_for($doctor)->update($availability->id, [
'validity_end' => '2024-01-14', // Two weeks
// Days remain consistent with the new period
]);
// Reconciliation behavior configuration
// In config/roster.php:
'reconciliation_warning' => env('ROSTER_RECONCILIATION_WARNING', false),
// If true: PHP warning when days are outside the period
// If false: silent reconciliation
$days = roster_days_in_period('2024-01-01', '2024-01-07');
// Returns: ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday']
// Automatically sorted in standard order
return [
// Allowed activity types
'allowed_types' => [
'consultation',
'surgery',
'emergency',
'training',
'room_a',
'echography',
'scan',
],
// Minimum durations (in minutes)
// IMPORTANT: The system enforces an absolute minimum of 10 minutes
// for ALL entity types to prevent infinite loops and performance issues.
// Any value below 10 will be automatically forced to 10.
'durations' => [
'minimum_availability_minutes' => 15, // Will be enforced to >= 10
'minimum_schedule_minutes' => 15, // Will be enforced to >= 10
'minimum_impediment_minutes' => 5, // Will be enforced to >= 10
'max_search_period_days' => 365,
'max_availability_days' => 365,
],
// Validation rule cache
'cache' => [
'enabled' => env('ROSTER_CACHE_ENABLED', true),
'cache_file' => storage_path('framework/cache/roster_rules.php'),
'cache_max_age_hours' => 24,
],
// Days reconciliation
'reconciliation_warning' => env('ROSTER_RECONCILIATION_WARNING', false),
// Controls behavior during updates when days are
// outside the validity period:
// - true: triggers a PHP warning (E_USER_WARNING)
// - false: silent reconciliation
];
use Roster\Validation\Exceptions\ValidationFailedException;
try {
$schedule = schedule_for($availability)->create($data);
} catch (ValidationFailedException $e) {
// Get detailed violations with rule information
$violations = $e->getViolations();
// Array of ViolationData objects containing:
// - field name
// - error message
// - rule that triggered the violation
// - rule description for context
$detailedReport = $e->toDetailedArray();
// Includes rule descriptions for better debugging
return response()->json([
'error' => 'validation_failed',
'message' => $e->getFormattedMessage(),
'violations' => $detailedReport['violations'],
], 422);
}