Implementing Sortable Models
Introduction
This guide explains how to implement sortable models in your Laravel application, allowing you to easily manage the order of records in your database. The sortable functionality is built on a robust infrastructure that automatically handles record positioning.
Implementing the sortable interface allows your models to be automatically ordered, making it easy to create drag-and-drop interfaces and maintain consistent sorting across your application.
Implementation Steps
Step 1: Implement Interface and Trait
First, implement the SortableEntity interface and add the HasSorting trait to your model:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Services\SortOrder\Contracts\SortableEntity;
use App\Services\SortOrder\Traits\HasSorting;
class YourModel extends Model implements SortableEntity
{
use HasSorting;
// Your model properties and methods...
}
Step 2: Configure Sort Column (Optional)
If your sort column name is different from the default order, override the sortingColumnName method:
- Default Configuration
- Custom Column Name
// No need to override if using 'order' as your sort column
// The default implementation is:
public function sortingColumnName(): string
{
return 'order';
}
With the default configuration, the system will use the order column for sorting.
public function sortingColumnName(): string
{
return 'position'; // Or any other column name like 'sort_order', 'sequence', etc.
}
This tells the system to use your custom column name for sorting operations.
Make sure your table has the appropriate column (either order or your custom column name) defined as an integer in your migration:
$table->integer('order')->default(0);
Step 3: Define Sorting Scope (Optional)
If you need to limit sorting to specific groups of records (e.g., only sort items within the same category), override the baseSortQuery method:
- Default Scope
- Category-Based Scope
- Complex Scope
// No need to override if sorting all records together
// The default implementation is:
public function baseSortQuery(): Builder
{
return $this->newQuery();
}
This will sort all records of the model together.
public function baseSortQuery(): Builder
{
return $this->newQuery()
->where('category_id', $this->category_id);
}
This ensures that items are only sorted within their respective categories.
public function baseSortQuery(): Builder
{
return $this->newQuery()
->where('category_id', $this->category_id)
->where('is_active', true)
->where('user_id', auth()->id());
}
You can define complex sorting scopes with multiple conditions.
Available Features
After implementing the sortable interface and trait, your model will have the following capabilities:
1. Automatic Sorting of New Records
New records are automatically assigned the next available position in their sorting scope.
// The new record will be placed at the end of the list
$item = YourModel::create([
'name' => 'New Item',
'category_id' => 1
]);
// No need to manually set the order - it's handled automatically
2. Item Reordering
You can easily change the position of items:
// Move an item up one position
$item->moveUp();
// Move an item down one position
$item->moveDown();
// Move an item to a specific position
$item->moveTo(3);
// Move an item to the first position
$item->moveToStart();
// Move an item to the last position
$item->moveToEnd();
3. Automatic Reordering After Deletion
When a record is deleted, the system automatically reorders the remaining items to maintain consecutive positions.
// When this item is deleted, other items will be reordered automatically
$item->delete();
4. Order-Based Queries
Query your models in their sorted order:
// Get all items in their sorted order
$items = YourModel::ordered()->get();
// Combine with other query conditions
$items = YourModel::where('is_active', true)
->ordered()
->get();
Infrastructure
The sorting system uses an observer pattern that is automatically applied to models implementing the SortableEntity interface. This observer handles:
- Setting the initial order value for new records
- Reordering records when an item's position changes
- Cleaning up the order sequence when records are deleted
You don't need to configure the observer manually - it's automatically registered through Laravel's service provider system.
Implementation Example
- Model Definition
- Migration
- Controller Usage
- Blade View
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Services\SortOrder\Contracts\SortableEntity;
use App\Services\SortOrder\Traits\HasSorting;
class MenuItem extends Model implements SortableEntity
{
use HasSorting;
protected $fillable = [
'name',
'url',
'menu_id',
'is_active'
];
// Override to sort within the same menu
public function baseSortQuery(): Builder
{
return $this->newQuery()
->where('menu_id', $this->menu_id);
}
// Relationship to Menu
public function menu()
{
return $this->belongsTo(Menu::class);
}
}
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up()
{
Schema::create('menu_items', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('url');
$table->foreignId('menu_id')->constrained()->onDelete('cascade');
$table->boolean('is_active')->default(true);
$table->integer('order')->default(0); // Sort column
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('menu_items');
}
};
<?php
namespace App\Http\Controllers;
use App\Models\MenuItem;
use App\Models\Menu;
use Illuminate\Http\Request;
class MenuItemController extends Controller
{
public function index(Menu $menu)
{
// Get menu items in their sorted order
$items = $menu->menuItems()->ordered()->get();
return view('menu.items.index', compact('menu', 'items'));
}
public function reorder(Request $request, MenuItem $item)
{
$position = $request->input('position');
// Move the item to the requested position
$item->moveTo($position);
return response()->json(['success' => true]);
}
}
<div class="menu-items" data-sortable>
@foreach($items as $item)
<div class="menu-item" data-id="{{ $item->id }}">
<span class="handle">☰</span>
<span class="name">{{ $item->name }}</span>
<span class="url">{{ $item->url }}</span>
<div class="actions">
<button class="edit-btn">Edit</button>
<button class="delete-btn">Delete</button>
</div>
</div>
@endforeach
</div>
<script>
// Example JavaScript for drag-and-drop reordering
document.addEventListener('DOMContentLoaded', function() {
// Initialize sortable with your preferred library
// When item is moved, send AJAX request to update position
function updatePosition(itemId, newPosition) {
fetch(`/menu-items/${itemId}/reorder`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
},
body: JSON.stringify({ position: newPosition })
});
}
});
</script>
Conclusion
Implementing sortable models using the SortableEntity interface and HasSorting trait provides a robust foundation for managing ordered records in your Laravel application. This approach simplifies the creation of sortable lists, drag-and-drop interfaces, and any feature that requires maintaining a specific order of records.
By leveraging the automatic sorting capabilities, you can focus on building your application's unique features rather than worrying about the complexities of maintaining record order.