Skip to main content

StoreFlowTransitionRequest

Form request class for validating FlowTransition creation data. This request handles complex validation including state relationships, transition types, and uniqueness constraints.

Namespace

JobMetric\Flow\Http\Requests\FlowTransition\StoreFlowTransitionRequest

Overview

The StoreFlowTransitionRequest validates incoming data when creating a new FlowTransition entity. It ensures:

  • Required fields are present
  • Flow ID exists and is valid
  • Multi-language translations are valid
  • State relationships are valid (from/to states)
  • Transition uniqueness within flow
  • Business rules are enforced (START state rules, terminal state rules, etc.)

Validation Rules

Required Fields

FieldRuleDescription
flow_idrequired|integer|exists:flows,idID of the parent flow
translationrequired|arrayMulti-language translation data
translation.{locale}required|arrayTranslation data for each active locale
translation.{locale}.namerequired|stringTransition name in each locale (must be unique within flow)

Optional Fields

FieldRuleDescription
fromnullable|integer|exists:flow_states,idSource state ID (nullable for generic input)
tonullable|integer|exists:flow_states,idTarget state ID (nullable for generic output)
slugnullable|string|max:255|regex:/^[a-z0-9-]+$/|unique:flow_transitions,slugURL-friendly identifier

Transition Types

The request supports four transition types:

1. Specific Transition (from → to)

Both from and to are specified:

'from' => 1, // State ID
'to' => 2, // State ID

2. Self-Loop Transition (from → from)

Same state for both from and to:

'from' => 1,
'to' => 1, // Same state

Note: Not allowed for START states.

3. Generic Input Transition (null → to)

No source state, can enter from any state:

'from' => null,
'to' => 2,

4. Generic Output Transition (from → null)

No target state, can exit to any state:

'from' => 1,
'to' => null,

Note: Not allowed from terminal states.

Cross-Field Validation

The request includes extensive custom validation in withValidator():

At Least One Required

At least one of from or to must be set:

// ✅ Valid
'from' => 1, 'to' => 2,
'from' => null, 'to' => 2,
'from' => 1, 'to' => null,

// ❌ Invalid
'from' => null, 'to' => null,

START State Rules

  1. Cannot Self-Loop: START state cannot have self-loop transitions

    // ❌ Invalid if from is START state
    'from' => $startId,
    'to' => $startId,
  2. Cannot Point To: No transition can point to START state

    // ❌ Invalid
    'to' => $startId,
  3. Only One Exit: Only one transition can exit from START state

    // If START already has a transition, cannot create another
  4. First Transition: First transition in flow must start from START

    // If this is the first transition, from must be START

Terminal State Rules

Terminal states cannot have generic output transitions:

// ❌ Invalid if from is terminal state
'from' => $terminalStateId,
'to' => null,

Uniqueness Rules

The combination (flow_id, from, to) must be unique:

// ❌ Invalid - Duplicate transition
// If flow 1 already has: from=1, to=2
// Cannot create another: from=1, to=2

Translation Validation

Name Uniqueness

The translation.{locale}.name field is validated for uniqueness within the same flow:

  • Checks if a transition with the same name already exists in the same flow and locale
  • Prevents duplicate transition names within a flow
  • Trims whitespace before validation

Usage Examples

Specific Transition

$requestData = [
'flow_id' => 1,
'translation' => [
'en' => ['name' => 'Approve Order'],
],
'from' => 1, // Pending state
'to' => 2, // Approved state
];

Generic Input Transition

$requestData = [
'flow_id' => 1,
'translation' => [
'en' => ['name' => 'Enter Processing'],
],
'from' => null, // Can enter from any state
'to' => 3, // Processing state
];

Generic Output Transition

$requestData = [
'flow_id' => 1,
'translation' => [
'en' => ['name' => 'Cancel Order'],
],
'from' => 1, // Pending state
'to' => null, // Can exit to any state
];

Self-Loop Transition

$requestData = [
'flow_id' => 1,
'translation' => [
'en' => ['name' => 'Refresh Status'],
],
'from' => 2, // Processing state
'to' => 2, // Same state (self-loop)
];

With Slug

$requestData = [
'flow_id' => 1,
'translation' => [
'en' => ['name' => 'Approve Order'],
],
'from' => 1,
'to' => 2,
'slug' => 'approve-order', // URL-friendly identifier
];

Context Management

The request supports external context injection:

$request = new StoreFlowTransitionRequest();
$request->setContext(['flow_id' => 123]);

Static Rules Method

rulesFor()

Provides programmatic validation:

$input = [
'flow_id' => 1,
'translation' => [
'en' => ['name' => 'Transition'],
],
'from' => 1,
'to' => 2,
];

$context = ['flow_id' => 1];

$rules = StoreFlowTransitionRequest::rulesFor($input, $context);

Error Handling

Validation Errors

{
"message": "The given data was invalid.",
"errors": {
"flow_id": [
"The selected flow id is invalid."
],
"from": [
"The first transition must start from START state."
],
"to": [
"Terminal states cannot have generic output transitions."
]
}
}

Best Practices

  1. Understand Transition Types: Choose the right type for your use case

    // Specific: Most common, explicit state changes
    'from' => 1, 'to' => 2,

    // Generic Input: Flexible entry point
    'from' => null, 'to' => 2,

    // Generic Output: Flexible exit point
    'from' => 1, 'to' => null,
  2. Follow START State Rules: Always start from START state for first transition

    // ✅ Good - First transition
    'from' => $startStateId, 'to' => 2,
  3. Use Meaningful Slugs: Create URL-friendly slugs for API access

    // ✅ Good
    'slug' => 'approve-order'

    // ❌ Bad
    'slug' => 'transition_1'
  4. Respect Terminal States: Don't create generic outputs from terminal states

    // ❌ Invalid
    'from' => $terminalStateId, 'to' => null,