CheckStatusInDriverRule
The CheckStatusInDriverRule is a Laravel validation rule that ensures a given status value is allowed for the subject model bound to a Flow. It validates that the status value exists in the model's status enum values, which are detected through the HasWorkflow trait.
Namespace
JobMetric\Flow\Rules\CheckStatusInDriverRule
Overview
This validation rule is used when creating or updating Flow States to ensure that the provided status value is valid for the Flow's subject model. It:
- Loads the Flow by ID and resolves its subject model class
- Ensures the subject model uses the
HasWorkflowtrait - Detects status enum values from the model using
HasWorkflowhelpers - Validates the incoming status value exists among those enum values
- Provides clear error messages for various failure scenarios
When to Use
This rule is primarily used in:
- FlowState Creation: Validating status when creating new flow states
- FlowState Updates: Validating status when updating existing flow states
- Form Requests: Ensuring status values match the model's enum
Constructor
Basic Usage
use JobMetric\Flow\Rules\CheckStatusInDriverRule;
$rule = new CheckStatusInDriverRule($flowId);
Parameters:
int $flowId: The Flow ID used to resolve the subject model class
How It Works
Validation Process
The rule follows this validation process:
- Null/Empty Check: If value is
nullor empty string, validation passes (allows optional status) - Flow Lookup: Loads the Flow from database using the provided Flow ID
- Subject Model Resolution: Extracts
subject_typefrom the Flow - Model Validation: Ensures the subject class exists and extends
Model - Trait Check: Verifies the model uses
HasWorkflowtrait - Enum Detection: Calls
flowStatusEnumValues()on the model instance - Value Matching: Checks if the provided value matches any enum value (strict or stringified)
Status Enum Detection
The rule uses HasWorkflow::flowStatusEnumValues() to detect allowed status values:
// Inside the rule
$subject = new $subjectClass();
$allowed = $subject->flowStatusEnumValues();
This method:
- Detects the status column name from model configuration
- Resolves the enum class from the status column
- Returns array of enum values
Value Matching
The rule performs flexible matching:
// Strict matching
in_array($value, $allowed, true)
// Stringified matching (for form submissions)
in_array((string)$value, array_map('strval', $allowed), true)
Supported Value Types:
- String values (e.g.,
'pending','shipped') - Enum instances (e.g.,
OrderStatus::PENDING) - Numeric strings (e.g.,
'1','2') - Boolean values (converted to
'1'or'0')
Usage in Form Requests
StoreFlowStateRequest
namespace App\Http\Requests\FlowState;
use Illuminate\Foundation\Http\FormRequest;
use JobMetric\Flow\Rules\CheckStatusInDriverRule;
class StoreFlowStateRequest extends FormRequest
{
public function rules(): array
{
$flowId = $this->input('flow_id');
return [
'flow_id' => 'required|exists:flows,id',
'status' => [
'present',
'nullable',
new CheckStatusInDriverRule($flowId),
],
// ... other rules
];
}
}
UpdateFlowStateRequest
namespace App\Http\Requests\FlowState;
use Illuminate\Foundation\Http\FormRequest;
use JobMetric\Flow\Rules\CheckStatusInDriverRule;
class UpdateFlowStateRequest extends FormRequest
{
public function rules(): array
{
$flow = $this->route('flowState')->flow;
$flowId = $flow->id;
return [
'status' => [
'present',
'nullable',
new CheckStatusInDriverRule($flowId),
],
// ... other rules
];
}
}
Validation Scenarios
Valid Status Values
The rule accepts status values that match the model's enum:
// Model with status enum
class Order extends Model
{
use HasWorkflow;
protected $fillable = ['status'];
// Status enum: pending, processing, shipped, delivered
}
// Valid status values
'pending' // ✅ Valid
'processing' // ✅ Valid
'shipped' // ✅ Valid
'delivered' // ✅ Valid
Invalid Status Values
The rule rejects values not in the enum:
'invalid_status' // ❌ Not in enum
'PENDING' // ❌ Case-sensitive (unless enum has it)
' pending' // ❌ Whitespace
'1' // ❌ Numeric string (unless enum has it)
Null and Empty Values
Null and empty strings pass validation (allows optional status):
null // ✅ Passes (optional)
'' // ✅ Passes (optional)
Error Messages
The rule provides specific error messages for different failure scenarios:
Flow Not Found
trans('workflow::base.validation.flow_not_found')
Triggered when: Flow ID doesn't exist in database
Invalid Subject Model
trans('workflow::base.validation.subject_model_invalid')
Triggered when:
subject_typeis not a string- Class doesn't exist
- Class doesn't extend
Model
Model Must Use HasWorkflow
trans('workflow::base.validation.model_must_use_has_workflow', [
'model' => $subjectClass
])
Triggered when: Subject model doesn't use HasWorkflow trait
Status Column Missing
trans('workflow::base.validation.status_column_missing')
Triggered when: Model doesn't have a status column configured
Status Enum Error
trans('workflow::base.validation.status_enum_error')
Triggered when: Error occurs while detecting enum values
Status Enum Missing
trans('workflow::base.validation.status_enum_missing')
Triggered when: No enum values are detected
Invalid Status Value
trans('workflow::base.validation.check_status_in_driver', [
'status' => implode(', ', $allowedString),
])
Triggered when: Provided status value doesn't match any enum value
Complete Examples
Example 1: Basic Usage in Form Request
namespace App\Http\Requests\FlowState;
use Illuminate\Foundation\Http\FormRequest;
use JobMetric\Flow\Rules\CheckStatusInDriverRule;
class StoreFlowStateRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
$flowId = $this->input('flow_id');
return [
'flow_id' => 'required|exists:flows,id',
'translation' => 'required|array',
'status' => [
'present',
'nullable',
new CheckStatusInDriverRule($flowId),
],
'config' => 'sometimes|array',
'color' => 'sometimes|nullable|hex_color',
'icon' => 'sometimes|nullable|string',
'is_terminal' => 'sometimes|boolean',
];
}
public function messages(): array
{
return [
'flow_id.required' => 'Flow ID is required',
'flow_id.exists' => 'Selected flow does not exist',
'status.*' => 'Invalid status value for this flow',
];
}
}
Example 2: Dynamic Flow ID Resolution
class UpdateFlowStateRequest extends FormRequest
{
public function rules(): array
{
// Get flow ID from route model binding
$flowState = $this->route('flowState');
$flowId = $flowState->flow_id;
return [
'status' => [
'sometimes',
'nullable',
new CheckStatusInDriverRule($flowId),
],
// ... other rules
];
}
}
Example 3: Conditional Validation
class StoreFlowStateRequest extends FormRequest
{
public function rules(): array
{
$flowId = $this->input('flow_id');
$rules = [
'flow_id' => 'required|exists:flows,id',
];
// Only validate status if provided
if ($this->has('status')) {
$rules['status'] = [
'required',
new CheckStatusInDriverRule($flowId),
];
}
return $rules;
}
}
Example 4: Custom Error Handling
use Illuminate\Validation\ValidationException;
use JobMetric\Flow\Rules\CheckStatusInDriverRule;
try {
$request->validate([
'status' => new CheckStatusInDriverRule($flowId),
]);
} catch (ValidationException $e) {
// Custom error handling
if ($e->errors()['status'][0] === trans('workflow::base.validation.flow_not_found')) {
// Handle flow not found
} elseif ($e->errors()['status'][0] === trans('workflow::base.validation.check_status_in_driver')) {
// Handle invalid status
}
}
Model Requirements
Required Setup
For the rule to work, your model must:
- Use HasWorkflow Trait:
use JobMetric\Flow\HasWorkflow;
class Order extends Model
{
use HasWorkflow;
}
- Have Status Column:
// Migration
Schema::create('orders', function (Blueprint $table) {
$table->id();
$table->string('status')->nullable();
// ...
});
- Configure Status Enum:
class Order extends Model
{
use HasWorkflow;
protected $fillable = ['status'];
// Status enum is detected automatically from the status column
// Or configure explicitly in HasWorkflow trait
}
Status Enum Detection
The rule automatically detects status enum values using:
$model = new Order();
$allowedStatuses = $model->flowStatusEnumValues();
This method:
- Looks for status column configuration
- Resolves enum class from column definition
- Returns array of enum values
Edge Cases and Behavior
Case Sensitivity
Status values are case-sensitive:
// Enum: ['pending', 'processing']
'pending' // ✅ Valid
'PENDING' // ❌ Invalid (case mismatch)
'Pending' // ❌ Invalid (case mismatch)
Whitespace Handling
Whitespace in values causes validation failure:
'pending' // ✅ Valid
' pending' // ❌ Invalid (leading space)
'pending ' // ❌ Invalid (trailing space)
' pending ' // ❌ Invalid (both)
Type Coercion
The rule performs type coercion for matching:
// Enum: [OrderStatus::PENDING, OrderStatus::PROCESSING]
'pending' // ✅ Valid (string match)
OrderStatus::PENDING // ✅ Valid (strict match)
1 // ❌ Invalid (unless enum has numeric value)
Null and Empty Values
Null and empty strings always pass (allows optional status):
null // ✅ Always passes
'' // ✅ Always passes
Error Handling
Flow Not Found
$rule = new CheckStatusInDriverRule(999); // Non-existent flow
$rule->validate('status', 'pending', function ($message) {
// $message = trans('workflow::base.validation.flow_not_found')
});
Invalid Subject Model
// Flow with invalid subject_type
$flow = Flow::create([
'subject_type' => 'NonExistentClass',
// ...
]);
$rule = new CheckStatusInDriverRule($flow->id);
$rule->validate('status', 'pending', function ($message) {
// $message = trans('workflow::base.validation.subject_model_invalid')
});
Model Without HasWorkflow
// Flow with model that doesn't use HasWorkflow
$flow = Flow::create([
'subject_type' => \App\Models\User::class, // User doesn't use HasWorkflow
// ...
]);
$rule = new CheckStatusInDriverRule($flow->id);
$rule->validate('status', 'pending', function ($message) {
// $message = trans('workflow::base.validation.model_must_use_has_workflow', ['model' => User::class])
});
Invalid Status Value
// Valid statuses: ['pending', 'processing', 'shipped']
$rule = new CheckStatusInDriverRule($flow->id);
$rule->validate('status', 'invalid', function ($message) {
// $message = trans('workflow::base.validation.check_status_in_driver', [
// 'status' => 'pending, processing, shipped'
// ])
});
Testing
Unit Test Example
namespace Tests\Unit\Rules;
use App\Models\Order;
use App\Models\Flow;
use JobMetric\Flow\Rules\CheckStatusInDriverRule;
use Tests\TestCase;
class CheckStatusInDriverRuleTest extends TestCase
{
public function test_validates_status_against_enum(): void
{
$flow = Flow::factory()->create([
'subject_type' => Order::class,
]);
$rule = new CheckStatusInDriverRule($flow->id);
$failCalled = false;
// Valid status
$rule->validate('status', 'pending', function ($message) use (&$failCalled) {
$failCalled = true;
});
$this->assertFalse($failCalled);
}
public function test_rejects_invalid_status(): void
{
$flow = Flow::factory()->create([
'subject_type' => Order::class,
]);
$rule = new CheckStatusInDriverRule($flow->id);
$failMessage = null;
// Invalid status
$rule->validate('status', 'invalid_status', function ($message) use (&$failMessage) {
$failMessage = $message;
});
$this->assertNotNull($failMessage);
}
public function test_allows_null_value(): void
{
$flow = Flow::factory()->create([
'subject_type' => Order::class,
]);
$rule = new CheckStatusInDriverRule($flow->id);
$failCalled = false;
$rule->validate('status', null, function ($message) use (&$failCalled) {
$failCalled = true;
});
$this->assertFalse($failCalled);
}
}
Best Practices
-
Always Provide Flow ID: Ensure Flow ID is valid before creating the rule
// ✅ Good
$flowId = $request->input('flow_id');
$request->validate([
'flow_id' => 'required|exists:flows,id',
'status' => new CheckStatusInDriverRule($flowId),
]);
// ❌ Bad
$rule = new CheckStatusInDriverRule($request->input('flow_id')); // May be null -
Use with Present/Nullable: Combine with
presentandnullablefor optional status'status' => [
'present',
'nullable',
new CheckStatusInDriverRule($flowId),
] -
Handle Errors Gracefully: Provide user-friendly error messages
try {
$request->validate($rules);
} catch (ValidationException $e) {
return back()->withErrors($e->errors());
} -
Cache Flow Lookups: If validating multiple fields, cache the Flow instance
$flow = Flow::findOrFail($flowId);
$rules = [
'status' => new CheckStatusInDriverRule($flow->id),
// Other rules using $flow
];
Integration with FlowState Service
The rule is automatically used in FlowState service when creating/updating states:
use JobMetric\Flow\Facades\FlowState;
// Creating a state - status is validated automatically
FlowState::store($flowId, [
'status' => 'pending', // Validated by CheckStatusInDriverRule
'translation' => [...],
]);
Related Documentation
- FlowState Service - State management
- StoreFlowStateRequest - State creation validation
- UpdateFlowStateRequest - State update validation
- HasWorkflow - Workflow integration trait
- HasFlow - Simple flow binding trait
- Flow Service - Flow management