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

Eloquent Sequence Laravel Package

highsolutions/eloquent-sequence

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require highsolutions/eloquent-sequence
    

    No publisher required—just install and use.

  2. Basic Model Integration: Add the Sequence trait and define a sequence() method in your Eloquent model:

    use HighSolutions\EloquentSequence\Sequence;
    
    class Section extends Model
    {
        use Sequence;
    
        public function sequence()
        {
            return [
                'group' => 'article_id', // Grouping column (e.g., foreign key)
                'fieldName' => 'seq',    // Sequence column (e.g., 'order', 'priority')
            ];
        }
    }
    
  3. First Use Case:

    • Auto-incrementing sequences:
      $section = new Section(['article_id' => 1, 'seq' => 0]);
      $section->save(); // Automatically assigns the next sequence value (e.g., 1)
      
    • Manual updates:
      $section->moveUp(); // Decrements `seq` for this group
      $section->moveDown(); // Increments `seq` for this group
      
  4. Where to Look First:

    • README for configuration options.
    • Tests for edge cases (e.g., concurrent updates).
    • Sequence trait source (here) for method signatures.

Implementation Patterns

Core Workflows

  1. Grouped Sequencing: Use group to define a scope (e.g., article_id, parent_id). Sequences are unique per group.

    public function sequence()
    {
        return [
            'group' => 'category_id',
            'fieldName' => 'display_order',
        ];
    }
    
  2. Dynamic Grouping: Override getSequenceGroup() to compute the group dynamically:

    protected function getSequenceGroup()
    {
        return $this->article->slug; // Group by slug instead of ID
    }
    
  3. Custom Sequence Logic: Extend the trait or override methods like getNextSequenceValue():

    protected function getNextSequenceValue()
    {
        return parent::getNextSequenceValue() * 10; // Custom step (e.g., 0, 10, 20...)
    }
    
  4. Bulk Reordering: Use reorder() to reset sequences for a group:

    Section::where('article_id', 1)->reorder(); // Reassigns seq = 1, 2, 3...
    
  5. Integration with API Resources: Expose sequence methods in API responses:

    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'order' => $this->seq,
            'can_move_up' => $this->canMoveUp(),
        ];
    }
    

Common Patterns

  • Soft Deletes: Sequences persist after soft deletion. Use withTrashed() if needed:
    Section::withTrashed()->where('article_id', 1)->reorder();
    
  • Transactions: Wrap sequence updates in transactions to avoid race conditions:
    DB::transaction(function () {
        $section->moveUp();
        $section->save();
    });
    
  • Observers: Trigger sequence updates via model events:
    class SectionObserver
    {
        public function saved(Section $section)
        {
            if ($section->wasRecentlyCreated) {
                $section->reorderGroup(); // Reorder siblings after creation
            }
        }
    }
    

Gotchas and Tips

Pitfalls

  1. Race Conditions:

    • Issue: Concurrent moveUp()/moveDown() calls can corrupt sequences.
    • Fix: Use database locks or transactions:
      DB::beginTransaction();
      try {
          $section->moveUp();
          DB::commit();
      } catch (\Exception $e) {
          DB::rollBack();
          throw $e;
      }
      
    • Alternative: Use selectForUpdate() in the package’s getNextSequenceValue():
      $query->lockForUpdate();
      
  2. Field Name Conflicts:

    • Issue: If fieldName matches a reserved keyword (e.g., order), queries may fail.
    • Fix: Use backticks or override the query builder:
      protected function getSequenceField()
      {
          return "`seq`"; // or use a non-reserved name like `sort_order`
      }
      
  3. Group Ambiguity:

    • Issue: If group is nullable or dynamic, sequences may behave unexpectedly.
    • Fix: Validate the group in getSequenceGroup():
      protected function getSequenceGroup()
      {
          $group = $this->article_id;
          if (is_null($group)) {
              throw new \InvalidArgumentException("Group cannot be null");
          }
          return $group;
      }
      
  4. Performance with Large Groups:

    • Issue: reorder() can be slow for groups with thousands of records.
    • Fix: Use raw SQL or chunk the update:
      Section::where('article_id', 1)->orderBy('seq')->chunk(100, function ($items) {
          $items->each(function ($item, $index) {
              $item->update(['seq' => $index + 1]);
          });
      });
      

Debugging Tips

  1. Log Sequence Values: Override getNextSequenceValue() to log:

    protected function getNextSequenceValue()
    {
        \Log::debug('Next seq for group ' . $this->getSequenceGroup(), [
            'current_max' => $this->getMaxSequenceValue(),
        ]);
        return parent::getNextSequenceValue();
    }
    
  2. Check for Silent Failures:

    • Ensure fieldName exists in the database (add migrations if missing).
    • Verify group column is indexed for performance:
      Schema::table('sections', function (Blueprint $table) {
          $table->index('article_id');
      });
      
  3. Test Edge Cases:

    • Empty groups: Section::where('article_id', 999)->reorder().
    • Concurrent updates: Use Laravel’s process facade to simulate:
      Process::daemonize()->run(function () {
          for ($i = 0; $i < 100; $i++) {
              Section::find(1)->moveUp();
          }
      });
      

Extension Points

  1. Custom Sequence Generators: Replace the default getNextSequenceValue() with a custom logic (e.g., UUID-based):

    protected function getNextSequenceValue()
    {
        return Str::uuid()->getHex();
    }
    
  2. Hooks for Validation: Add validation before sequence updates:

    protected function beforeSequenceUpdate()
    {
        if ($this->seq < 0) {
            throw new \InvalidArgumentException("Sequence cannot be negative");
        }
    }
    
  3. Event Dispatching: Trigger events for sequence changes:

    protected function afterSequenceUpdate()
    {
        event(new SequenceUpdated($this));
    }
    
  4. Fallback for Missing Groups: Handle cases where the group doesn’t exist:

    protected function getSequenceGroup()
    {
        return $this->group ?? 'default_group';
    }
    
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.
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
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle
atriumphp/atrium