Skip to main content

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 HasWorkflow trait
  • Detects status enum values from the model using HasWorkflow helpers
  • 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:

  1. Null/Empty Check: If value is null or empty string, validation passes (allows optional status)
  2. Flow Lookup: Loads the Flow from database using the provided Flow ID
  3. Subject Model Resolution: Extracts subject_type from the Flow
  4. Model Validation: Ensures the subject class exists and extends Model
  5. Trait Check: Verifies the model uses HasWorkflow trait
  6. Enum Detection: Calls flowStatusEnumValues() on the model instance
  7. 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_type is 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:

  1. Use HasWorkflow Trait:
use JobMetric\Flow\HasWorkflow;

class Order extends Model
{
use HasWorkflow;
}
  1. Have Status Column:
// Migration
Schema::create('orders', function (Blueprint $table) {
$table->id();
$table->string('status')->nullable();
// ...
});
  1. 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​

  1. 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
  2. Use with Present/Nullable: Combine with present and nullable for optional status

    'status' => [
    'present',
    'nullable',
    new CheckStatusInDriverRule($flowId),
    ]
  3. Handle Errors Gracefully: Provide user-friendly error messages

    try {
    $request->validate($rules);
    } catch (ValidationException $e) {
    return back()->withErrors($e->errors());
    }
  4. 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' => [...],
]);