Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Lexorank Sortable Laravel Package

alexcrawford/lexorank-sortable

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require alexcrawford/lexorank-sortable
    

    Ensure compatibility with your Laravel version (see version table).

  2. Add the trait and column:

    use AlexCrawford\Sortable\Sortable;
    
    class Article extends Model
    {
        use Sortable;
    
        protected $sortable = [
            'column' => 'position', // Default column name
            'order'  => 'asc',      // Default order (asc/desc)
        ];
    }
    

    Run a migration to add the position column (lexorank string type):

    Schema::create('articles', function (Blueprint $table) {
        $table->lexorank('position')->nullable(); // or ->default('0')
    });
    
  3. First use case:

    // Create and sort items
    $article = Article::create(['title' => 'First Article']);
    $article->setPositionBefore($article->fresh()); // Insert before itself (top)
    
    // Query sorted results
    $sortedArticles = Article::sorted()->get();
    

Implementation Patterns

Core Workflows

1. Basic Sorting

  • Insertion:
    // Insert at top
    $item->setPositionBefore($item->fresh());
    
    // Insert at bottom
    $item->setPositionAfter($item->fresh());
    
    // Insert between two items
    $item->setPositionBetween($item1, $item2);
    
  • Reordering:
    $item->moveUp();   // Move up one position
    $item->moveDown(); // Move down one position
    

2. Querying

  • Sorted queries:
    // Default order (asc)
    $items = Article::sorted()->get();
    
    // Custom order (desc)
    Article::sorted('desc')->get();
    
    // Scoped queries
    Article::where('published', true)->sorted()->get();
    
  • Pagination:
    Article::sorted()->paginate(10);
    

3. Grouped Sorting

For many-to-many or grouped relationships:

// Model: Article (belongsToMany Tags)
class Article extends Model
{
    use Sortable;

    protected $sortable = [
        'column' => 'position',
        'group'  => 'tag_id', // Group by tag_id
    ];
}

// Usage:
$tag = Tag::find(1);
$tag->articles()->sorted()->get(); // Sorted by tag_id

4. Bulk Operations

// Reorder multiple items
$items = Article::all();
$items->each(function ($item) {
    $item->setPositionBefore($item->fresh());
});

Integration Tips

API/JSON Responses

Leverage sorted() in API responses:

return Article::sorted()->get()->map(function ($item) {
    return [
        'id'    => $item->id,
        'title' => $item->title,
        'order' => $item->position, // Optional: include position in response
    ];
});

Admin Panels (e.g., Nova, Filament)

  • Use sorted() in resource queries:
    public function query()
    {
        return Article::sorted();
    }
    
  • Add drag-and-drop UI with JavaScript (e.g., SortableJS) and update positions via AJAX.

Observers/Events

Listen for position changes:

// app/Providers/EventServiceProvider.php
protected $listen = [
    'AlexCrawford\Sortable\Events\PositionUpdated' => [
        'App\Listeners\LogPositionChange',
    ],
];

Custom Column Names

Override default position column:

protected $sortable = [
    'column' => 'sort_order',
];

Gotchas and Tips

Pitfalls

  1. Lexorank Column Type:

    • Mistake: Using string instead of lexorank in migrations.
    • Fix: Always use $table->lexorank('column_name').
    • Fallback: If lexorank cast isn’t available, use string and manually handle lexorank logic.
  2. Grouping Without Foreign Key:

    • Mistake: Setting group without a foreign key column.
    • Fix: Ensure the group value matches a column in the pivot/table (e.g., tag_id for grouped sorting).
  3. Concurrent Updates:

    • Issue: Race conditions when multiple users reorder items simultaneously.
    • Solution: Use database transactions or optimistic locking:
      DB::transaction(function () use ($item) {
          $item->moveUp();
      });
      
  4. Performance with Large Datasets:

    • Problem: sorted() queries can be slow on tables with millions of rows.
    • Mitigation:
      • Add an index to the position column.
      • Use limit() or take() for pagination:
        Article::sorted()->take(50)->get();
        

Debugging

  1. Invalid Lexorank Values:

    • Symptom: SQLSTATE[22007]: Invalid datetime format.
    • Debug: Check for malformed lexorank strings (e.g., null or non-lexorank values).
    • Fix: Add validation:
      protected $casts = [
          'position' => 'lexorank',
      ];
      
  2. Grouping Not Working:

    • Symptom: Items aren’t grouped as expected.
    • Debug: Verify the group value in $sortable matches a column in the query scope.
    • Fix: Use toSql() on the query to inspect the generated SQL:
      $query = Article::where('tag_id', 1)->sorted();
      dd($query->toSql());
      
  3. Position Not Persisting:

    • Symptom: setPositionBefore() doesn’t update the database.
    • Debug: Check for:
      • Missing save() after setting position:
        $item->setPositionBefore($otherItem);
        $item->save(); // Required!
        
      • Model events interfering (e.g., saving observer).

Extension Points

  1. Custom Lexorank Logic: Override the lexorank generator:

    use AlexCrawford\Sortable\Lexorank;
    
    class CustomLexorank extends Lexorank
    {
        public function generate(): string
        {
            // Custom logic
            return parent::generate();
        }
    }
    
    // In your model:
    protected $sortable = [
        'lexorank' => CustomLexorank::class,
    ];
    
  2. Adding Sortable to Non-Eloquent Models: Use the standalone lexorank class:

    use AlexCrawford\Sortable\Lexorank;
    
    $lexorank = new Lexorank();
    $position = $lexorank->generate();
    
  3. Custom Query Scopes: Extend the sorted() scope:

    class Article extends Model
    {
        public function scopeCustomSorted($query)
        {
            return $query->sorted()->where('published', true);
        }
    }
    
  4. Testing: Use the Lexorank class directly in tests:

    public function testLexorankGeneration()
    {
        $lexorank = new Lexorank();
        $this->assertEquals('0', $lexorank->generate());
        $this->assertEquals('1', $lexorank->generate());
    }
    

Pro Tips

  1. Seed Data with Sorted Order:

    public function run()
    {
        $articles = collect(['A', 'B', 'C'])->map(fn ($title) => [
            'title' => $title,
            'position' => null, // Let lexorank handle it
        ]);
    
        Article::insert($articles->toArray());
    }
    
  2. Frontend Drag-and-Drop: Use a library like interact.js to capture drag events and update positions via AJAX:

    interact('.sortable-item').draggable({
        onmove: dragEvent => {
            const draggable = dragEvent.target;
            const rect = draggable.getBoundingClientRect();
            // Send position updates to Laravel
        }
    });
    
  3. Soft Deletes: Lexorank works with soft deletes, but ensure your sorted() queries include withTrashed() if needed:

    Article::withTrashed()->sorted()->get();
    
  4. Performance Optimization: Cache sorted queries if they rarely change:

    $sorted = Cache::remember('sorted_articles', now()->addHours(1), function () {
        return Article::sorted()->get();
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
milito/query-filter
apiboxsym/user-bundle
apiboxsym/health-check-bundle
jayeshmepani/jpl-moshier-ephemeris-php
elnasnato/laraliveui
labrodev/rest-sdk
sampaui/sampaui
babelqueue/php-sdk
facebook/capi-param-builder-php
babelqueue/symfony
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours