PHPackages                             baril/orderly - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. baril/orderly

ActiveLibrary[Database &amp; ORM](/categories/database)

baril/orderly
=============

Orderable/sortable behavior for Eloquent models.

v3.3.1(10mo ago)296.7k↓27.6%1MITPHPCI passing

Since Sep 7Pushed 10mo ago1 watchersCompare

[ Source](https://github.com/michaelbaril/orderly)[ Packagist](https://packagist.org/packages/baril/orderly)[ RSS](/packages/baril-orderly/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (9)Dependencies (7)Versions (10)Used By (1)

Orderly 🗃️
==========

[](#orderly-card_file_box)

[![Version](https://camo.githubusercontent.com/ea70f14b4c99e9b474ac4f902c2b6eecbe1e6f5dd87def5d43f4120982538273/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f626172696c2f6f726465726c793f6c6162656c3d737461626c65)](https://packagist.org/packages/baril/orderly)[![License](https://camo.githubusercontent.com/8d4af2360ba6786ba8d5945b07c0c2e267ca70941afc7df3e5fa8384b9f165de/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f626172696c2f6f726465726c79)](https://packagist.org/packages/baril/orderly)[![Downloads](https://camo.githubusercontent.com/e19f0985e32db21ced15d736c80dbf9f67e4bcaaff8174fb51ab67eb9ede97f9/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f626172696c2f6f726465726c79)](https://packagist.org/packages/baril/orderly/stats)[![Tests](https://camo.githubusercontent.com/4f2529d85d715b3caa628801a05b13a1a85a38d409a296b5da42901504fe37dd/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f6d69636861656c626172696c2f6f726465726c792f72756e2d74657374732e796d6c3f6272616e63683d6d6173746572266c6162656c3d7465737473)](https://github.com/michaelbaril/orderly/actions/workflows/run-tests.yml?query=branch%3Amaster)[![Coverage](https://camo.githubusercontent.com/77fe454ae21571bb701259b3dd942915fe187b86fc19848f90f8fae85bcba6d5/68747470733a2f2f696d672e736869656c64732e696f2f656e64706f696e743f75726c3d68747470732533412532462532466d69636861656c626172696c2e6769746875622e696f2532466f726465726c79253246636f76657261676525324662616467652e6a736f6e)](https://michaelbaril.github.io/orderly/coverage/)

This package adds an orderable/sortable behavior to Eloquent models.

You can find the full API documentation [here](https://michaelbaril.github.io/orderly/api/).

Version compatibility
---------------------

[](#version-compatibility)

LaravelOrderly12.x3.3+11.x3.2+10.x3.1+9.x3.x8.x2.x / 3.x7.x1.x6.x1.xSetup
-----

[](#setup)

### New install

[](#new-install)

If you're not using package discovery, register the service provider in your `config/app.php` file:

```
return [
    // ...
    'providers' => [
        Baril\Orderly\OrderlyServiceProvider::class,
        // ...
    ],
];
```

Add a column to your table to store the position. The default name for this column is `position` but you can use another name if you want (see below).

```
public function up()
{
    Schema::create('articles', function (Blueprint $table) {
        // ... other fields ...
        $table->unsignedInteger('position');
    });
}
```

Then, use the `\Baril\Orderly\Concerns\Orderable` trait in your model. The `position` field should be guarded as it won't be filled manually.

```
class Article extends Model
{
    use \Baril\Orderly\Concerns\Orderable;

    protected $guarded = ['position'];
}
```

You also need to set the `$orderColumn` property if you want to use another name than `position`:

```
class Article extends Model
{
    use \Baril\Orderly\Concerns\Orderable;

    protected $orderColumn = 'order';
    protected $guarded = ['order'];
}
```

Basic usage
-----------

[](#basic-usage)

You can use one of the following methods to change the model's position (no need to save afterwards):

- `moveToOffset($offset)` (`$offset` starts at 0 and can be negative, ie. `$offset = -1` is the last position),
- `moveToStart()`,
- `moveToEnd()`,
- `moveToPosition($position)` (`$position` starts at 1 and must be a valid position),
- `moveUp($positions = 1, $strict = true)`: moves the model up by `$positions`positions (the `$strict` parameter controls what happens if you try to move the model "out of bounds": if set to `false`, the model will simply be moved to the first or last position, else it will throw a `PositionException`),
- `moveDown($positions = 1, $strict = true)`,
- `swapWith($anotherModel)`,
- `moveBefore($anotherModel)`,
- `moveAfter($anotherModel)`.

```
$model = Article::find(1);
$anotherModel = Article::find(10)
$model->moveAfter($anotherModel);
// $model is now positioned after $anotherModel, and both have been saved
```

Also, this trait:

- automatically defines the model position on the `create` event, so you don't need to set `position` manually,
- automatically decreases the position of subsequent models on the `delete`event so that there's no "gap".

```
$article = new Article();
$article->title = $request->input('title');
$article->body = $request->input('body');
$article->save();
```

This model will be positioned at `MAX(position) + 1`.

To get ordered models, use the `ordered` scope:

```
$articles = Article::ordered()->get();
$articles = Article::ordered('desc')->get();
```

(You can cancel the effect of this scope by calling the `unordered` scope.)

Previous and next models can be queried using the `previous` and `next`methods:

```
$entity = Article::find(10);
$entity->next(10); // returns a QueryBuilder on the next 10 entities, ordered
$entity->previous(5)->get(); // returns a collection with the previous 5 entities, in reverse order
$entity->next()->first(); // returns the next entity
```

Mass reordering
---------------

[](#mass-reordering)

The `move*` methods described above are not appropriate for mass reordering because:

- they would perform many unneeded queries,
- changing a model's position affects other model's positions as well, and can cause side effects if you're not careful.

Example:

```
$models = Article::orderBy('publication_date', 'desc')->get();
$models->map(function($model, $key) {
    return $model->moveToOffset($key);
});
```

The sample code above will corrupt the data because you need each model to be "fresh" before you change its position. The following code, on the other hand, will work properly:

```
$collection = Article::orderBy('publication_date', 'desc')->get();
$collection->map(function($model, $key) {
    return $model->fresh()->moveToOffset($key);
});
```

It's still not a good way to do it though, because it performs many unneeded queries. A better way to handle mass reordering is to use the `saveOrder`method on a collection:

```
$collection = Article::orderBy('publication_date', 'desc')->get();
// $collection is not a regular Eloquent collection object, it's a custom class
// with the following additional method:
$collection->saveOrder();
```

That's it! Now the items' order in the collection has been applied to the `position` column of the database.

You can also order a collection explicitely with the `setOrder` method. It takes an array of ids as a parameter:

```
$ordered = $collection->setOrder([4, 5, 2]);
```

The returned collection is ordered so that the items with ids 4, 5 and 2 are at the beginning of the collection. Also, the new order is saved to the database automatically (you don't need to call `saveOrder`).

⚠️ Note: Only the models within the collection are reordered / swapped between one another. The other rows in the table remain untouched.

You can also use the `setOrder` method, either statically on the model, or on a query builder.

```
// This will reorder all statuses (assuming there are 5 statuses in the table):
Status::setOrder([2, 1, 5, 3, 4]);

// This will put the status with id 4 at the beginning, and move the other
// statuses' positions accordingly:
Status::setOrder([4]);

// This will only swap the statuses 3, 4 and 5, and won't change the position
// of the other statuses:
Status::whereKey([3, 4, 5])->setOrder([4, 5, 3]);
```

When used like this, the `setOrder` method returns the number of affected rows.

Orderable groups / one-to-many relationships
--------------------------------------------

[](#orderable-groups--one-to-many-relationships)

Sometimes, the table's data is "grouped" by some column, and you need to order each group individually instead of having a global order. To achieve this, you just need to set the `$groupColumn` property:

```
class Article extends Model
{
    use \Baril\Orderly\Concerns\Orderable;

    protected $guarded = ['position'];
    protected $groupColumn = 'section_id';
}
```

If the group is defined by multiple columns, you can use an array:

```
protected $groupColumn = ['field_name1', 'field_name2'];
```

Orderable groups can be used to handle orderable one-to-many relationships:

```
class Section extends Model
{
    public function articles()
    {
        return $this->hasMany(Article::class)->ordered();
        // Chaining the ->ordered() method is optional here, but you can do
        // it if you want the relation ordered by default.
    }
}

class Article extends Model
{
    protected $groupColumn = 'section_id';
}
```

Orderable many-to-many relationships
------------------------------------

[](#orderable-many-to-many-relationships)

If you need to order a many-to-many relationship, you will need a `position`column (or some other name) in the pivot table.

Have your model use the `\Baril\Orderly\Concerns\HasOrderableRelationships`trait:

```
class Post extends Model
{
    use \Baril\Orderly\Concerns\HasOrderableRelationships;

    public function tags()
    {
        return $this->belongsToManyOrderable(Tag::class);
    }
}
```

The prototype of the `belongsToManyOrderable` method is similar as `belongsToMany` with an added 2nd parameter `$orderColumn`:

```
public function belongsToManyOrderable(
        $related,
        $orderColumn = 'position',
        $table = null,
        $foreignPivotKey = null,
        $relatedPivotKey = null,
        $parentKey = null,
        $relatedKey = null,
        $relation = null)
```

Now all the usual methods from the `BelongsToMany` class will set the proper position to attached models:

```
$post->tags()->attach($tag->id); // will attach $tag and give it the last position
$post->tags()->sync([$tag1->id, $tag2->id, $tag3->id]) // will keep the provided order
$post->tags()->detach($tag->id); // will decrement the position of subsequent $tags
```

You can order the results of the relation by chaining the `ordered` method:

```
$orderedTags = $post->tags()->ordered()->get();
$tagsInReverseOrder = $post->tags()->ordered('desc')->get();
```

If you want the relation ordered by default, you can use the `belongsToManyOrdered` method in the relation definition, instead of `belongsToManyOrderable`.

```
class Post extends Model
{
    use \Baril\Orderly\Concerns\HasOrderableRelationships;

    public function tags()
    {
        return $this->belongsToManyOrdered(Tag::class);
        // the line above is actually just a shortcut to:
        // return $this->belongsToManyOrderable(Tag::class)->ordered();
    }
}
```

In this case, if you occasionally want to order the related models by some other field, you will need to use the `unordered` scope first, or use `forceOrderBy`:

```
$post->tags; // ordered by position, because of the definition above
$post->tags()->ordered('desc')->get(); // reverse order
$post->tags()->unordered()->get(); // unordered

// Note that orderBy has no effect here since the tags are already ordered by position:
$post->tags()->orderBy('id')->get();

// This is the proper way to do it:
$post->tags()->unordered()->orderBy('id')->get();
// or:
$post->tags()->forceOrderBy('id')->get();
```

The `BelongsToManyOrderable` class has all the same methods as the `Orderable`trait, except that you will need to pass them a related $model to work with:

- `moveToOffset($model, $offset)`,
- `moveToStart($model)`,
- `moveToEnd($model)`,
- `moveToPosition($model, $position)`,
- `moveUp($model, $positions = 1, $strict = true)`,
- `moveDown($model, $positions = 1, $strict = true)`,
- `swap($model, $anotherModel)`,
- `moveBefore($model, $anotherModel)` (`$model` will be moved before `$anotherModel`),
- `moveAfter($model, $anotherModel)` (`$model` will be moved after `$anotherModel`),
- `before($model)` (similar as the `previous` method from the `Orderable` trait),
- `after($model)` (similar as `next`).

```
$tag1 = $article->tags()->ordered()->first();
$tag2 = $article->tags()->ordered()->last();
$article->tags()->moveBefore($tag1, $tag2);
// now $tag1 is at the second to last position
```

Note that if `$model` doesn't belong to the relationship, any of these methods will throw a `Baril\Orderly\GroupException`.

There's also a method for mass reordering:

```
$article->tags()->setOrder([$id1, $id2, $id3]);
```

In the example above, tags with ids `$id1`, `$id2`, `$id3` will now be at the beginning of the article's `tags` collection. Any other tags attached to the article will come after, in the same order as before calling `setOrder`.

Orderable morph-to-many relationships
-------------------------------------

[](#orderable-morph-to-many-relationships)

Similarly, the package defines a `MorphToManyOrderable` type of relationship. The 3rd parameter of the `morphToManyOrderable` method is the name of the order column (defaults to `position`):

```
class Post extends Model
{
    use \Baril\Orderly\Concerns\HasOrderableRelationships;

    public function tags()
    {
        return $this->morphToManyOrderable('App\Tag', 'taggable', 'tag_order');
    }
}
```

Same thing with the `morphedByManyOrderable` method:

```
class Tag extends Model
{
    use \Baril\Orderable\Concerns\HasOrderableRelationships;

    public function posts()
    {
        return $this->morphedByManyOrderable('App\Post', 'taggable', 'order');
    }

    public function videos()
    {
        return $this->morphedByManyOrderable('App\Video', 'taggable', 'order');
    }
}
```

Artisan command
---------------

[](#artisan-command)

The `orderly:fix-positions` command will recalculate the data in the `position` column (eg. in case you've manually deleted rows and have "gaps").

For an orderable model:

```
php artisan orderly:fix-positions "App\\YourModel"
```

For an orderable many-to-many relation:

```
php artisan orderly:fix-positions "App\\YourModel" relationName
```

###  Health Score

43

—

FairBetter than 91% of packages

Maintenance54

Moderate activity, may be stable

Popularity33

Limited adoption so far

Community13

Small or concentrated contributor base

Maturity60

Established project with proven stability

 Bus Factor1

Top contributor holds 54.5% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~220 days

Total

9

Last Release

319d ago

Major Versions

v1.0.0 → v2.0.02021-07-17

v2.0.1 → v3.0.02023-02-04

### Community

Maintainers

![](https://www.gravatar.com/avatar/95cbe396c7a3fe89d006b143506ee5e5645317318f2cf8cf00faf5d327ef6114?d=identicon)[michaelbaril](/maintainers/michaelbaril)

---

Top Contributors

[![michaelbaril](https://avatars.githubusercontent.com/u/11444287?v=4)](https://github.com/michaelbaril "michaelbaril (24 commits)")[![mbwbd](https://avatars.githubusercontent.com/u/231652605?v=4)](https://github.com/mbwbd "mbwbd (20 commits)")

---

Tags

laraveldatabaseeloquentsortableorderable

###  Code Quality

TestsPHPUnit

Code StylePHP\_CodeSniffer

### Embed Badge

![Health badge](/badges/baril-orderly/health.svg)

```
[![Health](https://phpackages.com/badges/baril-orderly/health.svg)](https://phpackages.com/packages/baril-orderly)
```

###  Alternatives

[mongodb/laravel-mongodb

A MongoDB based Eloquent model and Query builder for Laravel

7.1k7.2M71](/packages/mongodb-laravel-mongodb)[tucker-eric/eloquentfilter

An Eloquent way to filter Eloquent Models

1.8k4.8M26](/packages/tucker-eric-eloquentfilter)[spiritix/lada-cache

A Redis based, automated and scalable database caching layer for Laravel

591444.8k2](/packages/spiritix-lada-cache)[pdphilip/elasticsearch

An Elasticsearch implementation of Laravel's Eloquent ORM

145360.2k4](/packages/pdphilip-elasticsearch)[toponepercent/baum

Baum is an implementation of the Nested Set pattern for Eloquent models.

3154.7k](/packages/toponepercent-baum)[baril/bonsai

An implementation of the Closure Tables pattern for Eloquent.

3593.5k](/packages/baril-bonsai)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
