baril/orderly
Add sortable, orderable behavior to Laravel Eloquent models. Store a position column (default: position), use the Orderable trait, and move records with helpers like moveToOffset() and moveToStart(). Supports Laravel 6–12 with version mapping.
Installation:
composer require baril/orderly
Register the service provider in config/app.php (if not using discovery):
'providers' => [
Baril\Orderly\OrderlyServiceProvider::class,
],
Database Migration:
Add a position column (or custom name) to your table:
Schema::table('articles', function (Blueprint $table) {
$table->unsignedInteger('position');
});
Model Integration:
Use the Orderable trait and guard the position column:
class Article extends Model {
use \Baril\Orderly\Concerns\Orderable;
protected $guarded = ['position'];
}
First Use Case: Fetch ordered models:
$articles = Article::ordered()->get();
Single Model Reordering:
$article = Article::find(1);
$article->moveUp(); // Moves up by 1 position
$article->moveToPosition(5); // Sets explicit position
Grouped Ordering (e.g., by section_id):
class Article extends Model {
use \Baril\Orderly\Concerns\Orderable;
protected $groupColumn = 'section_id';
}
Many-to-Many Relationships:
class Post extends Model {
use \Baril\Orderly\Concerns\HasOrderableRelationships;
public function tags() {
return $this->belongsToManyOrderable(Tag::class);
}
}
Reorder tags:
$post->tags()->setOrder([1, 3, 2]);
Mass Reordering:
$articles = Article::all()->sortBy('title');
$articles->saveOrder(); // Persists new order
moveToOffset() for drag-and-drop UIs (e.g., Vue/Draggable.js):
$article->moveToOffset($newIndex);
previous()/next() for navigation:
$article->next()->first(); // Get next article
Article::ordered()->get()) to reduce DB load.Database Locking:
move* operations may cause race conditions. Use transactions:
DB::transaction(function () use ($model) {
$model->moveUp();
});
Grouped Ordering Quirks:
$groupColumn causes global ordering instead of per-group.groupColumn values are consistent (e.g., section_id exists).Many-to-Many Pitfalls:
setOrder() only reorders models within the collection. Exclude unrelated models:
$post->tags()->whereIn('id', [1, 2, 3])->setOrder([3, 1, 2]);
Performance:
move* in loops (e.g., foreach ($models) { $model->moveUp(); }). Use saveOrder() instead.php artisan orderly:fix-positions App\Article
$groupColumn in grouped models.position column values (guarded by default).ordered().Custom Order Logic:
Override getOrderColumn() in your model:
protected function getOrderColumn(): string {
return 'custom_order_field';
}
Query Scopes:
Extend the ordered() scope for complex logic:
public function scopePublishedOrdered($query) {
return $query->where('published', true)->ordered();
}
Event Hooks:
Listen for orderly.saving to validate positions:
Event::listen('orderly.saving', function ($model) {
if ($model->position < 0) {
throw new \Exception('Invalid position');
}
});
Database Support:
For non-MySQL (e.g., PostgreSQL), ensure position is integer (not bigint if using moveToOffset(-1)).
How can I help you explore Laravel packages today?