This guide covers migrating from Flowforge v2.x (Lexorank/string-based positions) to v3.x (DecimalPosition/DECIMAL-based positions).
- New Dependency:
ext-bcmathPHP extension required - Column Type: Position column changed from
VARCHARtoDECIMAL(20,10) - Service Removed:
Rank.phpreplaced byDecimalPosition.php - Laravel Version: Now requires Laravel 12+
Ensure the BCMath extension is installed:
php -m | grep bcmathIf not installed, add it to your PHP configuration.
composer require relaticle/flowforge:^3.0Create a migration to change the position column type:
<?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
{
// Option A: Drop and recreate (loses existing positions)
Schema::table('your_table', function (Blueprint $table) {
$table->dropColumn('position');
});
Schema::table('your_table', function (Blueprint $table) {
$table->flowforgePositionColumn();
$table->unique(['status', 'position'], 'unique_position_per_column');
});
// Option B: Keep existing order (recommended)
// See "Preserving Existing Order" section below
}
public function down(): void
{
Schema::table('your_table', function (Blueprint $table) {
$table->dropUnique('unique_position_per_column');
$table->dropColumn('position');
});
Schema::table('your_table', function (Blueprint $table) {
$table->string('position')->nullable();
});
}
};After migration, regenerate all positions:
php artisan flowforge:repair-positionsSelect the "regenerate" strategy when prompted.
Replace all references to the old Rank service:
- use Relaticle\Flowforge\Services\Rank;
+ use Relaticle\Flowforge\Services\DecimalPosition;
// Empty column position
- $position = Rank::forEmptySequence()->get();
+ $position = DecimalPosition::forEmptyColumn();
// Position after another
- $position = Rank::after($lastRank)->get();
+ $position = DecimalPosition::after($lastPosition);
// Position before another
- $position = Rank::before($nextRank)->get();
+ $position = DecimalPosition::before($nextPosition);
// Position between two
- $position = Rank::betweenRanks($prevRank, $nextRank)->get();
+ $position = DecimalPosition::between($afterPos, $beforePos);Run the diagnose command to check for issues:
php artisan flowforge:diagnose-positions \
--model=App\\Models\\YourModel \
--column=status \
--position=positionIf you need to preserve existing card order during migration:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
use Relaticle\Flowforge\Services\DecimalPosition;
return new class extends Migration
{
public function up(): void
{
// 1. Add new decimal column
Schema::table('tasks', function (Blueprint $table) {
$table->decimal('position_new', 20, 10)->nullable();
});
// 2. Convert existing positions maintaining order
$statuses = DB::table('tasks')->distinct()->pluck('status');
foreach ($statuses as $status) {
$tasks = DB::table('tasks')
->where('status', $status)
->orderBy('position') // Original string order
->get();
$lastPosition = null;
foreach ($tasks as $task) {
$newPosition = $lastPosition === null
? DecimalPosition::forEmptyColumn()
: DecimalPosition::after($lastPosition);
DB::table('tasks')
->where('id', $task->id)
->update(['position_new' => $newPosition]);
$lastPosition = $newPosition;
}
}
// 3. Swap columns
Schema::table('tasks', function (Blueprint $table) {
$table->dropColumn('position');
});
Schema::table('tasks', function (Blueprint $table) {
$table->renameColumn('position_new', 'position');
$table->unique(['status', 'position'], 'unique_position_per_column');
});
}
};Each position calculation now includes ±5% random jitter, preventing collisions when multiple users move cards simultaneously.
When gaps between positions fall below 0.0001, positions are automatically redistributed with 65535 spacing.
Unique constraint violations trigger automatic retry with exponential backoff (50ms, 100ms, 200ms).
flowforge:diagnose-positions- Check for position issuesflowforge:rebalance-positions- Redistribute positions evenlyflowforge:repair-positions- Interactive repair wizard
| v2.x (Rank) | v3.x (DecimalPosition) | Notes |
|---|---|---|
Rank::forEmptySequence() |
DecimalPosition::forEmptyColumn() |
Returns string directly |
Rank::after($rank) |
DecimalPosition::after($position) |
Returns string directly |
Rank::before($rank) |
DecimalPosition::before($position) |
Returns string directly |
Rank::betweenRanks($a, $b) |
DecimalPosition::between($a, $b) |
Includes jitter |
$rank->get() |
N/A | No longer needed, returns string |
The flowforgePositionColumn() macro now creates DECIMAL(20,10) instead of VARCHAR:
// v2.x created:
$table->string('position')->nullable()->collation('utf8mb4_bin');
// v3.x creates:
$table->decimal('position', 20, 10)->nullable();Error: Call to undefined function bcadd()
Install the BCMath extension:
- Ubuntu/Debian:
sudo apt-get install php-bcmath - macOS (Homebrew): Usually included, check
php -m | grep bcmath - Windows: Enable in php.ini:
extension=bcmath
If you see unique constraint violations after migration:
php artisan flowforge:repair-positions
# Select "regenerate" strategyIf card order changed after migration, the original string positions may not have been sorted correctly. Run:
php artisan flowforge:repair-positions
# Select "regenerate" strategyThis will reassign positions based on current database order.