Install the Dependency
Install the asantibanez/laravel-eloquent-state-machines package:
composer require asantibanez/laravel-eloquent-state-machines
- Configure the State Machine Create the BookingStateMachine class and define state transitions:
namespace App\StateMachines;
use Asantibanez\LaravelEloquentStateMachines\StateMachines\StateMachine;
class BookingStateMachine extends StateMachine
{
public function transitions(): array
{
return [
'Pending' => [
'status' => 0,
'transitions' => ['Confirmed', 'Auto-Cancelled']
],
'Confirmed' => [
'status' => 1,
'transitions' => ['Paid', 'Auto-Cancelled']
],
'Paid' => [
'status' => 2,
'transitions' => ['Vehicle Ready']
],
'Vehicle Ready' => [
'status' => 3,
'transitions' => ['In Progress']
],
'In Progress' => [
'status' => 4,
'transitions' => ['Completed', 'Issue Reported']
],
'Cancelled' => [
'status' => 5,
'transitions' => ['Refunded']
],
];
}
public function defaultState(): ?string
{
return 'Pending';
}
}
transitions(): Defines valid state transitions.
defaultState(): Specifies the default state when a new model is created.
Set Up the Model
Update the model to use the state machine:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Asantibanez\LaravelEloquentStateMachines\Traits\HasStateMachines;
use App\StateMachines\BookingStateMachine;
class Booking extends Model
{
use HasStateMachines;
protected $fillable = [
'user_name', 'destination', 'vechicle_type', 'brand', 'model', 'price',
'start_date', 'end_date', 'number', 'email', 'user_id', 'vender_id',
'vechicle_image', 'vechicle_id', 'status', 'shop_id', 'state'
];
public $stateMachines = [
'state' => BookingStateMachine::class,
];
}
public function getNumericStatus(): int
{
$transitions = (new BookingStateMachine)->transitions();
return $transitions[$this->state]['status'] ?? 0;
}
HasStateMachines: Trait that integrates the state machine into your model.
$stateMachines: Defines which state machine is used for a specific attribute (state).
step4: set state to pending
Modify the Code for Creating a Booking Add the state field when creating a booking record:
$post = Booking::create([
'user_name' => $user_name,
'destination' => $Pickupdestination,
'vechicle_type' => $vechicle_type,
'brand' => $brand,
'model' => $model,
'price' => $price,
'start_date' => $start_date,
'end_date' => $end_date,
'number' => $number,
'email' => $emails,
'user_id' => $user_id,
'vender_id' => $vender_ID,
'vechicle_image' => json_encode($img),
'vechicle_id' => $id,
'status' => '0',
'shop_id' => $shop_id,
'state' => 'Pending', // Set initial state
]);
step5: changes to confirmed state
public function change_status(Request $request)
{
Log::info("Entering the change_status() method in UploadController.");
Log::info("Request data: " . json_encode($request->all()));
// Retrieve the booking ID and new state from the request
$id = $request->input('id');
$newState = $request->input('state');
Log::info("Booking ID: {$id}");
Log::info("New State: {$newState}");
// Find the booking by ID
$booking = Booking::find($id);
if (!$booking) {
Log::error("Booking with ID {$id} not found.");
session()->flash('error', 'Booking not found.');
return back();
}
Log::info("Current booking data before update: " . json_encode($booking));
// Get the current state
$currentState = $booking->state;
try {
switch ($currentState) {
case 'Pending':
// Example logic to decide the next state
$autoCancelCondition = $request->input('auto_cancel', false); // External condition
$bookings = Booking::where('state', 'Pending')
->where('created_at', '<', Carbon::now()->subHour())
->get();
$confirmCondition = $request->input('confirm', false); // External condition
if ($autoCancelCondition) {
$booking->state()->transitionTo('Auto-Cancelled');
$status = 2;
Log::info("Booking ID {$id} transitioned to 'Auto-Cancelled'.");
}
elseif ($confirmCondition) {
$booking->state()->transitionTo('Confirmed');
$status = 1;
Log::info("Booking ID {$id} transitioned to 'Confirmed'.");
} else {
throw new \Exception("No valid condition met for transitioning from Pending.");
}
break;
case 'Confirmed':
// Example logic to decide the next state
$paymentSuccessful = $request->input('payment_successful', false); // Condition for payment success
$autoCancelCondition = $request->input('auto_cancel', false); // Condition for auto-cancel
if ($paymentSuccessful) {
$booking->state()->transitionTo('Paid');
$status = 3; // Numeric status for Paid
Log::info("Booking ID {$id} transitioned to 'Paid'.");
} elseif ($autoCancelCondition) {
$booking->state()->transitionTo('Auto-Cancelled');
$status = 2; // Numeric status for Auto-Cancelled
Log::info("Booking ID {$id} transitioned to 'Auto-Cancelled'.");
} else {
throw new \Exception("No valid condition met for transitioning from Confirmed.");
}
break;
case 'Paid':
if ($newState === 'Vehicle Ready') {
$booking->state()->transitionTo($newState);
$status = 4;
} else {
throw new \Exception("Invalid transition from Paid to {$newState}");
}
break;
case 'Vehicle Ready':
if ($newState === 'In Progress') {
$booking->state()->transitionTo($newState);
$status = 5;
} else {
throw new \Exception("Invalid transition from Vehicle Ready to {$newState}");
}
break;
case 'In Progress':
if (in_array($newState, ['Completed', 'Issue Reported'])) {
$booking->state()->transitionTo($newState);
$status = $newState === 'Completed' ? 6 : 7;
} else {
throw new \Exception("Invalid transition from In Progress to {$newState}");
}
break;
case 'Cancelled':
if ($newState === 'Refunded') {
$booking->state()->transitionTo($newState);
$status = 8;
} else {
throw new \Exception("Invalid transition from Cancelled to {$newState}");
}
break;
default:
throw new \Exception("Invalid current state: {$currentState}");
}
// Update the status field in the booking record
$booking->update(['status' => $status]);
Log::info("Booking ID {$id} transitioned to '{$newState}' with status {$status}.");
session()->flash('success', 'Status updated successfully.');
} catch (\Exception $e) {
Log::error("Error transitioning booking state: " . $e->getMessage());
session()->flash('error', $e->getMessage());
}
return back();
}
How to implement Auto Cancel Logic
add auto_cancelled_at field in model
protected $fillable = [
'user_name', 'destination', 'vechicle_type', 'brand', 'model', 'price',
'start_date', 'end_date', 'number', 'email', 'user_id', 'vender_id',
'vechicle_image', 'vechicle_id', 'status', 'shop_id', 'state',
'auto_cancelled_at', // Add this field
];
Auto-Cancel Logic
Command to Handle Auto-Cancel
Create a command that transitions stale bookings to Auto-Cancelled and records the auto_cancelled_at timestamp.
php artisan make:command AutoCancelBookings
*Update the generated command *(app/Console/Commands/AutoCancelBookings.php):
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\Booking;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
class AutoCancelBookings extends Command
{
protected $signature = 'bookings:auto-cancel';
protected $description = 'Automatically cancel bookings that have been pending for over 1 hour';
public function handle()
{
Log::info('Starting auto-cancel process for pending bookings.');
// Find bookings in the 'Pending' state older than 1 hour
$bookings = Booking::where('state', 'Pending')
->where('created_at', '<', Carbon::now()->subHour())
->get();
foreach ($bookings as $booking) {
try {
$booking->state()->transitionTo('Auto-Cancelled');
$booking->update(['auto_cancelled_at' => Carbon::now()]);
Log::info("Booking ID {$booking->id} auto-cancelled.");
} catch (\Exception $e) {
Log::error("Error auto-cancelling Booking ID {$booking->id}: " . $e->getMessage());
}
}
Log::info('Auto-cancel process completed.');
}
}
Scheduler for Auto-Cancel
Schedule the AutoCancelBookings command in app/Console/Kernel.php:
protected function schedule(Schedule $schedule)
{
$schedule->command('bookings:auto-cancel')->everyTenMinutes();
}
Add a cron job to your server:
* * * * * php /path-to-your-project/artisan schedule:run >> /dev/null 2>&1
- Handle Auto-Cancel in the Booking Creation Process Ensure new bookings start in the Pending state, which the auto-cancel logic handles if they remain unchanged for over an hour.
$post = Booking::create([
'user_name' => $user_name,
'destination' => $Pickupdestination,
'vechicle_type' => $vechicle_type,
'brand' => $brand,
'model' => $model,
'price' => $price,
'start_date' => $start_date,
'end_date' => $end_date,
'number' => $number,
'email' => $emails,
'user_id' => $user_id,
'vender_id' => $vender_ID,
'vechicle_image' => json_encode($img),
'vechicle_id' => $id,
'status' => '0',
'shop_id' => $shop_id,
'state' => 'Pending', // Initial state
]);
- Testing the Auto-Cancel Logic Test Command Manually run the command to check if bookings are auto-cancelled:
php artisan bookings:auto-cancel
Verify that:
Bookings in the Pending state for over an hour are transitioned to Auto-Cancelled.
The auto_cancelled_at field is updated with the current timestamp.
Test Scheduled Execution
Allow the scheduled command to run and confirm that auto-cancel logic is applied periodically.
Top comments (0)