Database Schema
Required Fields
Your model needs these essential fields for Kanban functionality:
Schema::create('tasks', function (Blueprint $table) {
$table->id();
$table->string('title'); // Card title
$table->string('status'); // Column identifier
$table->flowforgePositionColumn(); // Drag-and-drop ordering
$table->timestamps();
});
Enum Support
Flowforge automatically handles PHP BackedEnum for status fields. No manual conversion needed:
// Define your enum
enum TaskStatus: string
{
case Todo = 'todo';
case InProgress = 'in_progress';
case Done = 'done';
}
// Cast in your model
class Task extends Model
{
protected $casts = [
'status' => TaskStatus::class,
];
}
// Flowforge automatically converts column IDs to enum values
Column::make('todo') // → TaskStatus::Todo
Column::make('in_progress') // → TaskStatus::InProgress
Column::make('done') // → TaskStatus::Done
BackedEnum cast and converts the column identifier to the appropriate enum value automatically.Position Column
The flowforgePositionColumn() method creates a DECIMAL(20,10) column for drag-and-drop functionality:
// Default column name 'position'
$table->flowforgePositionColumn();
// Custom column name
$table->flowforgePositionColumn('sort_order');
// Equivalent to:
$table->decimal('position', 20, 10)->nullable();
Position Storage Details
Flowforge v3.x uses DECIMAL(20,10) for position storage:
| Property | Value | Purpose |
|---|---|---|
| Total Digits | 20 | Maximum precision |
| Decimal Places | 10 | Supports ~33 bisections before precision loss |
| Default Gap | 65535 | Initial spacing between positions |
| Min Gap | 0.0001 | Triggers automatic rebalancing |
The system uses BCMath for arbitrary precision arithmetic, ensuring consistent calculations across all database systems.
Example Migration
Here's a complete example for adding Flowforge support to an existing table:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('tasks', function (Blueprint $table) {
$table->flowforgePositionColumn('position');
// Recommended: Add unique constraint for concurrent safety
$table->unique(['status', 'position'], 'unique_position_per_column');
});
}
public function down(): void
{
Schema::table('tasks', function (Blueprint $table) {
$table->dropUnique('unique_position_per_column');
$table->dropColumn('position');
});
}
};
[column_field, position] enables Flowforge's retry mechanism for concurrent operations.Factory Integration
When using factories, ensure position values are generated correctly using the DecimalPosition service:
use Relaticle\Flowforge\Services\DecimalPosition;
class TaskFactory extends Factory
{
/** @var array<string, string> Track last position per status */
private static array $lastPositions = [];
public function definition(): array
{
$status = $this->faker->randomElement(['todo', 'in_progress', 'done']);
return [
'title' => $this->faker->sentence(3),
'status' => $status,
'position' => $this->generatePositionForStatus($status),
];
}
private function generatePositionForStatus(string $status): string
{
if (!isset(self::$lastPositions[$status])) {
$position = DecimalPosition::forEmptyColumn();
} else {
$position = DecimalPosition::after(self::$lastPositions[$status]);
}
self::$lastPositions[$status] = $position;
return $position;
}
}
DecimalPosition Methods
| Method | Purpose |
|---|---|
forEmptyColumn() | Get initial position for empty column (65535) |
after($position) | Get position after given position |
before($position) | Get position before given position |
between($after, $before) | Get position between two positions (with jitter) |
betweenExact($after, $before) | Get exact midpoint (deterministic) |
Position Management Commands
Flowforge provides three artisan commands for managing positions:
Diagnose Positions
Check for position issues (gaps, inversions, duplicates):
php artisan flowforge:diagnose-positions \
--model=App\\Models\\Task \
--column=status \
--position=position
Rebalance Positions
Redistribute positions evenly when gaps become small:
php artisan flowforge:rebalance-positions \
--model=App\\Models\\Task \
--column=status \
--position=position
Repair Positions
Interactive command to fix corrupted position data:
php artisan flowforge:repair-positions
Strategies available:
- regenerate - Fresh start for all positions
- fix_missing - Only fix null positions
- fix_duplicates - Fix duplicate positions only
- fix_all - Both missing + duplicates (recommended)
Concurrent Safety
Jitter Mechanism
Each position calculation adds ±5% random jitter, ensuring concurrent users never generate identical positions. This is handled automatically by the DecimalPosition::between() method.
Auto-Rebalancing
When the gap between adjacent cards falls below 0.0001, positions are automatically redistributed with 65535 spacing. This happens transparently during card moves.
Retry Mechanism
If a unique constraint violation occurs (rare with jitter), Flowforge automatically retries:
- Max attempts: 3
- Backoff: 50ms, 100ms, 200ms (exponential)
- Supported databases: SQLite, MySQL, PostgreSQL