andydefer/laravel-roster 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

andydefer/laravel-roster

最新稳定版本:0.14.5

Composer 安装命令:

composer require andydefer/laravel-roster

包简介

Clean and flexible scheduling for Laravel applications.

README 文档

README

PHP Version Laravel Version License Tests Coverage

Roster is a comprehensive Laravel package for advanced scheduling, availability, and booking management. Built with a robust architecture, it handles recurring availability, booked slots, and impediments with exhaustive business validation.

📦 Installation

composer require andydefer/laravel-roster

Publish package resources:

php artisan roster:install

Or manually:

# Configuration
php artisan vendor:publish --tag=roster-config

# Migrations
php artisan vendor:publish --tag=roster-migrations

# Run migrations
php artisan migrate

🚀 Quick Start

1. Add the trait to your models

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Roster\Traits\HasRoster;

class Doctor extends Model
{
    use HasRoster;
}

2. Create recurring availabilities

// 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',
]);

3. Schedule appointments

// 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],
]);

4. Manage temporary unavailability

// 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',
]);

5. Search for available slots

// 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'
);

🛡️ Performance Protection: Absolute Minimum Duration

To prevent infinite loops and performance degradation, Roster enforces an absolute minimum duration of 10 minutes for ALL entity types (Availability, Schedule, Impediment).

Why 10 minutes?

When searching for available slots, the system generates time slots based on the duration. A duration that is too small would generate an enormous number of iterations:

Duration Generated Slots (1 year) Performance Impact
1 minute ~525,600 slots 🔴 Infinite loop risk - System overload, memory exhaustion
5 minutes ~105,120 slots 🟠 Very slow - Timeout possible, poor user experience
10 minutes ~52,560 slots 🟢 Optimal - Fast and stable
15 minutes ~35,040 slots 🟢 Excellent - Best performance
30 minutes ~17,520 slots 🟢 Perfect - Maximum efficiency

Technical Protection Implementation

The protection is enforced at the lowest level of the validation system:

// 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;
}

What happens if you try to configure less than 10 minutes?

// 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

Validation in Action

// 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 required for availability. Got 5 minutes"

// Attempt with 10 minutes
$context->method('safeData')->willReturn([
    'start_time' => '09:00:00',
    'end_time' => '09:10:00', // 10 minutes - MEETS absolute minimum
]);

// This will PASS validation

🔗 Polymorphic Scheduling Link System

Roster includes an advanced system that allows any Eloquent model to be associated with schedules with customizable metadata.

Attach resources to schedules

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']);

Manage attached resources

// 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();

Direct usage from models

// 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]);

Eloquent relationships

// 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();

Advanced use cases

1. Operating room management

// Prepare surgery with all necessary resources
$surgerySchedule = schedule_for($availability)->create([
    'title' => 'Knee Arthroscopy',
    'start_datetime' => '2038-01-04 08:00:00',
    'end_datetime' => '2038-01-04 10:00:00',
]);

$service = schedule_for($availability)->schedule($surgerySchedule);

$service->attach($operatingRoom, [
    'role' => 'operating_room',
    'equipment' => ['arthroscope', 'monitor', 'instruments'],
    'sterilization' => 'level_2'
]);

$service->attach($surgeon, [
    'role' => 'primary_surgeon',
    'specialty' => 'orthopedics',
    'assistant_required' => true
]);

$service->attach($anesthesiologist, [
    'role' => 'anesthesiologist',
    'type_anesthesia' => 'general'
]);

$service->attach($nurse, [
    'role' => 'instrument_nurse',
    'experience' => 'senior'
]);

2. Shared resource booking

// 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

3. Complex metadata for tracking

$service->attach($patient, [
    'medical_history' => ['hypertension', 'diabetes'],
    'insurance' => 'ABC Insurance',
    'priority' => 'high',
    'contact' => [
        'phone' => '555-0123',
        'email' => 'patient@example.com'
    ],
    'notes' => ['allergic to penicillin', 'needs interpreter']
]);

📋 Model Query Methods (HasRoster Trait)

The HasRoster trait includes methods to retrieve impediments and schedules of a model within a given period.

Added Methods

// 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

Simple Example

// 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";
}

Practical Use Case

// 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);
}

📖 Core Concepts

Immutability Principle

Roster prevents direct model mutations to ensure data integrity. All operations must go through appropriate services:

// ❌ 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'
]);

Single-action context

Each service is designed for a single action with its own context:

// ❌ 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, [...]);

The 3 main entities

  1. Availability: Defines when a resource is available (days, times, period)
  2. Schedule: Represents a booked slot in an availability
  3. Impediment: Temporarily blocks an availability

🛡️ Secure Architecture

Mutation access control

The system uses two contexts to control access:

// 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
});

Secure helpers

The availability_for(), schedule_for(), and impediment_for() helpers automatically create the necessary context:

// These helpers automatically handle:
// 1. Execution context creation
// 2. Schedulable entity validation
// 3. Reuse prevention

🔍 Advanced Search and Data Consistency

first() method for targeted search

// 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();

Automatic days consistency

The system automatically ensures consistency between specified days and validity periods:

// 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

Standardized days sorting

Utility functions always return days in standard week order (Monday → Sunday):

$days = roster_days_in_period('2024-01-01', '2024-01-07');
// Returns: ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday']
// Automatically sorted in standard order

🎯 Exhaustive Business Validation

Roster includes 17 validation rules that guarantee system consistency:

Main rules:

  • SchedulableValidationRule (110) - Checks for schedulable context presence
  • RequiredFieldsRule (100) - Validates required fields per operation
  • AvailabilityTemporalCoherenceRule (100) - Ensures temporal coherence
  • TemporalConflictRule (80) - Prevents scheduling overlaps
  • AvailabilityOverlapRule (80) - Prevents availability overlaps
  • TimeRangeRule (85) - Validates time ranges (no multi-day spans)
  • DurationRule (90) - Enforces minimum duration (with 10 minutes absolute minimum)

Rule visualization:

# List all available rules
php artisan roster:debug-rules

# See rules for a specific entity
php artisan roster:debug-rules availability --operation=create

📊 Real-world Usage Examples

Medical clinic management

// Create availabilities for different specialists
$cardiologist = Doctor::where('specialty', 'cardiology')->first();
$availability = availability_for($cardiologist)->create([
    'type' => 'consultation',
    'daily_start' => '08:30:00',
    'daily_end' => '12:30:00',
    'days' => ['monday', 'wednesday', 'friday'],
    'validity_start' => '2024-01-01',
    'validity_end' => '2024-12-31',
]);

// Patient booking
$appointment = schedule_for($availability)->create([
    'title' => 'Cardiac Consultation',
    'start_datetime' => '2024-06-10 10:00:00',
    'end_datetime' => '2024-06-10 11:00:00',
    'status' => ScheduleStatus::BOOKED,
    'metadata' => [
        'patient_id' => 'CARD001',
        'priority' => 'medium',
        'tests_required' => ['echocardiogram', 'stress_test']
    ],
]);

// Quick search for next availability
$nextAvailability = availability_for($cardiologist)
    ->setFilter('validity_start', '>', now())
    ->first();

// Manage unavailability (training)
impediment_for($availability)->create([
    'reason' => 'Continuing education',
    'start_datetime' => '2024-06-15 09:00:00',
    'end_datetime' => '2024-06-15 12:00:00',
    'metadata' => ['mandatory' => true, 'location' => 'Auditorium'],
]);

Room booking system

// Two doctors sharing a room
$room = Room::find(1);

// First doctor uses the room on Monday
$doctor1Availability = availability_for($doctor1)->create([
    'type' => 'room_a',
    'daily_start' => '09:00:00',
    'daily_end' => '17:00:00',
    'days' => ['monday', 'wednesday', 'friday'],
    'validity_start' => '2024-01-01',
    'validity_end' => '2024-12-31',
]);

// Second doctor uses the room on Tuesday
$doctor2Availability = availability_for($doctor2)->create([
    'type' => 'room_a',
    'daily_start' => '09:00:00',
    'daily_end' => '17:00:00',
    'days' => ['tuesday', 'thursday'],
    'validity_start' => '2024-01-01',
    'validity_end' => '2024-12-31',
]);

// Search for first availability for urgent slot
$urgentSlot = schedule_for($doctor1Availability)
    ->setFilter('status', ScheduleStatus::AVAILABLE)
    ->first();

// System automatically prevents conflicts
schedule_for($doctor1Availability)->create([
    'title' => 'Room A usage - Dr. Smith',
    'start_datetime' => '2024-06-10 10:00:00', // Monday
    'end_datetime' => '2024-06-10 12:00:00',
]);

// ❌ This booking will fail (inter-doctor conflict)
schedule_for($doctor2Availability)->create([
    'title' => 'Room A usage - Dr. Jones',
    'start_datetime' => '2024-06-10 11:00:00', // Same day as Dr. Smith
    'end_datetime' => '2024-06-10 13:00:00',
]);

Recurrent impediment management

// Create weekly availability
$weeklyAvailability = availability_for($doctor)->create([
    'type' => 'consultation',
    'daily_start' => '08:00:00',
    'daily_end' => '18:00:00',
    'days' => ['monday', 'tuesday', 'wednesday', 'thursday', 'friday'],
    'validity_start' => '2024-01-01',
    'validity_end' => '2024-12-31',
]);

// Recurrent impediments (lunch break)
$weekdays = ['2024-01-08', '2024-01-09', '2024-01-10', '2024-01-11', '2024-01-12'];

foreach ($weekdays as $weekday) {
    impediment_for($weeklyAvailability)->create([
        'reason' => 'Lunch break',
        'start_datetime' => Carbon::parse($weekday)->setTime(12, 0, 0),
        'end_datetime' => Carbon::parse($weekday)->setTime(13, 0, 0),
        'metadata' => ['type' => 'lunch', 'recurring' => true],
    ]);
}

// Find first available slot after impediments
$firstAvailableSlot = schedule_for($weeklyAvailability)
    ->setFilter('start_datetime', '>', now())
    ->first();

// Find available slots despite impediments
$availableSlots = schedule_for($weeklyAvailability)->findAvailableSlots(
    startDate: '2024-01-08',
    endDate: '2024-01-12',
    durationMinutes: 60,
    type: 'consultation'
);

🔧 Complete API

Availability Service

// CRUD
availability_for($schedulable)->create($data);
availability_for($schedulable)->find($id);
availability_for($schedulable)->update($id, $data);
availability_for($schedulable)->delete($id);

// Search
availability_for($schedulable)->all();
availability_for($schedulable)->setFilter('type', 'consultation')->all();
availability_for($schedulable)->first(); // New method

// Checks
availability_for($schedulable)->isAvailableOnDate($date, $type);
availability_for($schedulable)->getAvailabilityForTimeSlot($start, $end, $type);

Schedule Service

// Booking
schedule_for($availability)->create($data);
schedule_for($availability)->update($id, $data);
schedule_for($availability)->delete($id);

// Slot search
schedule_for($availability)->findNextSlot($durationMinutes, $type, $startFrom);
schedule_for($availability)->findAvailableSlots($startDate, $endDate, $durationMinutes, $type);
schedule_for($availability)->first(); // New method

// Checks
schedule_for($availability)->isTimeSlotAvailable($start, $end, $type);
schedule_for($availability)->isPeriodAvailable($start, $end, $type);

// Polymorphic link management
schedule_for($availability)->schedule($scheduleModel); // Set context
schedule_for($availability)->schedule($scheduleModel)->attach($model, $metadata);
schedule_for($availability)->schedule($scheduleModel)->detach($model);
schedule_for($availability)->schedule($scheduleModel)->getAttached();
schedule_for($availability)->schedule($scheduleModel)->sync($models, $metadata);

Impediment Service

// Impediment management
impediment_for($availability)->create($data);
impediment_for($availability)->update($id, $data);
impediment_for($availability)->delete($id);

// Search
impediment_for($availability)->first(); // New method

// Checks
impediment_for($availability)->isTimeSlotBlocked($start, $end);
impediment_for($availability)->getAvailableTimeSlots($start, $end, $type);

⚙️ Configuration

Configuration file (config/roster.php)

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
];

Environment variables

ROSTER_TIMEZONE=Europe/Paris
ROSTER_CACHE_ENABLED=true
ROSTER_RECONCILIATION_WARNING=false

🧪 Comprehensive Tests

The package includes 2300 tests covering all scenarios:

# Run all tests
php artisan test

# Integration tests
php artisan test --group=integration

# Performance tests
php artisan test --filter=test_performance_and_load_scenario

# Complex scenario tests
php artisan test --filter=test_real_world_complex_scenario

Tested scenarios:

  • ✅ Full availability lifecycle
  • ✅ Impediment management with conflicts
  • ✅ Intelligent booking system
  • ✅ Complex interactions (availabilities + impediments + schedules)
  • ✅ Multi-user conflicts with shared resources
  • ✅ Error handling and edge cases
  • ✅ Performance testing with massive data
  • ✅ Recovery after errors
  • ✅ Realistic complex scenario (hospital with multiple specialists)
  • ✅ Data consistency with automatic reconciliation
  • first() method for targeted search
  • ✅ Polymorphic link system with metadata
  • ✅ Attached resource management (rooms, vehicles, equipment)
  • ✅ Synchronization and detachment tests
  • Minimum duration enforcement (10 minutes absolute minimum)
  • Protection against infinite loops in slot generation

🚨 Error Handling

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);
}

Duration validation error example

try {
    schedule_for($availability)->create([
        'start_datetime' => '2024-06-10 09:00:00',
        'end_datetime' => '2024-06-10 09:05:00', // 5 minutes
    ]);
} catch (ValidationFailedException $e) {
    // Error message:
    // "Minimum duration of 10 minutes required for Schedule. Got 5 minutes"
    
    // The system automatically prevents durations below 10 minutes
    // to protect against infinite loops in slot generation
}

Reconciliation warning handling

// Configuration to enable warnings
config()->set('roster.reconciliation_warning', true);

// Capture warnings
set_error_handler(function ($errno, $errstr) {
    if ($errno === E_USER_WARNING && str_contains($errstr, 'outside the validity period')) {
        // Log or handle the warning
        Log::warning('Days reconciliation detected', ['message' => $errstr]);
        return true; // Prevents propagation
    }
    return false;
});

// During an update with days outside the period:
availability_for($doctor)->update($availability->id, [
    'validity_end' => '2024-01-10',
    'days' => ['monday', 'saturday'], // 'saturday' will be filtered with warning
]);

restore_error_handler();

📊 Development Tools

Validation rule debugging

# Display all rules
php artisan roster:debug-rules

# Filter by entity
php artisan roster:debug-rules availability

# Filter by operation
php artisan roster:debug-rules availability --operation=create

# Display methods
php artisan roster:debug-rules availability --show-methods

# Display sources
php artisan roster:debug-rules availability --show-source

Cache management

# Generate rule cache
php artisan roster:cache-rules

# Display cache statistics
php artisan roster:cache-rules --show

# Clear cache
php artisan roster:cache-rules --clear

# Force regeneration
php artisan roster:cache-rules --force

🤝 Contribution

  1. Fork the repository
  2. Create a branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Run tests

# All tests
composer test

# With code coverage
composer test-coverage

# Check code style
composer lint

📄 License

This package is open-source and available under the MIT license.

🔗 Useful Links

Roster - A professional solution for advanced scheduling management, designed for critical applications where every minute counts. ⚕️⏰✨

With advanced search features, data consistency, exhaustive business validation, automatic protection against infinite loops (10 minutes absolute minimum duration), and a comprehensive polymorphic link system, Roster ensures the integrity of your scheduling systems in the most demanding environments.

统计信息

  • 总下载量: 40
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 5
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 1
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2025-12-21

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固