Installation:
composer require highsolutions/eloquent-sequence
No publisher required—just install and use.
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')
];
}
}
First Use Case:
$section = new Section(['article_id' => 1, 'seq' => 0]);
$section->save(); // Automatically assigns the next sequence value (e.g., 1)
$section->moveUp(); // Decrements `seq` for this group
$section->moveDown(); // Increments `seq` for this group
Where to Look First:
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',
];
}
Dynamic Grouping:
Override getSequenceGroup() to compute the group dynamically:
protected function getSequenceGroup()
{
return $this->article->slug; // Group by slug instead of ID
}
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...)
}
Bulk Reordering:
Use reorder() to reset sequences for a group:
Section::where('article_id', 1)->reorder(); // Reassigns seq = 1, 2, 3...
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(),
];
}
withTrashed() if needed:
Section::withTrashed()->where('article_id', 1)->reorder();
DB::transaction(function () {
$section->moveUp();
$section->save();
});
class SectionObserver
{
public function saved(Section $section)
{
if ($section->wasRecentlyCreated) {
$section->reorderGroup(); // Reorder siblings after creation
}
}
}
Race Conditions:
moveUp()/moveDown() calls can corrupt sequences.DB::beginTransaction();
try {
$section->moveUp();
DB::commit();
} catch (\Exception $e) {
DB::rollBack();
throw $e;
}
selectForUpdate() in the package’s getNextSequenceValue():
$query->lockForUpdate();
Field Name Conflicts:
fieldName matches a reserved keyword (e.g., order), queries may fail.protected function getSequenceField()
{
return "`seq`"; // or use a non-reserved name like `sort_order`
}
Group Ambiguity:
group is nullable or dynamic, sequences may behave unexpectedly.getSequenceGroup():
protected function getSequenceGroup()
{
$group = $this->article_id;
if (is_null($group)) {
throw new \InvalidArgumentException("Group cannot be null");
}
return $group;
}
Performance with Large Groups:
reorder() can be slow for groups with thousands of records.Section::where('article_id', 1)->orderBy('seq')->chunk(100, function ($items) {
$items->each(function ($item, $index) {
$item->update(['seq' => $index + 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();
}
Check for Silent Failures:
fieldName exists in the database (add migrations if missing).group column is indexed for performance:
Schema::table('sections', function (Blueprint $table) {
$table->index('article_id');
});
Test Edge Cases:
Section::where('article_id', 999)->reorder().process facade to simulate:
Process::daemonize()->run(function () {
for ($i = 0; $i < 100; $i++) {
Section::find(1)->moveUp();
}
});
Custom Sequence Generators:
Replace the default getNextSequenceValue() with a custom logic (e.g., UUID-based):
protected function getNextSequenceValue()
{
return Str::uuid()->getHex();
}
Hooks for Validation: Add validation before sequence updates:
protected function beforeSequenceUpdate()
{
if ($this->seq < 0) {
throw new \InvalidArgumentException("Sequence cannot be negative");
}
}
Event Dispatching: Trigger events for sequence changes:
protected function afterSequenceUpdate()
{
event(new SequenceUpdated($this));
}
Fallback for Missing Groups: Handle cases where the group doesn’t exist:
protected function getSequenceGroup()
{
return $this->group ?? 'default_group';
}
How can I help you explore Laravel packages today?