nevadskiy/laravel-position

Arrange Laravel models in a given order.

0.8.1 2023-10-26 11:50 UTC

This package is auto-updated.

Last update: 2024-03-26 12:42:50 UTC


README

Stand With Ukraine

🔢 Arrange Laravel models in a given order

PHPUnit Code Coverage Latest Stable Version License

✅ Requirements

  • Laravel 7.0 or newer
  • PHP 7.2 or newer

🔌 Installation

Install the package via Composer:

composer require nevadskiy/laravel-position

🔨 Add positions to models

Add the HasPosition trait to the models that should have positions:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Nevadskiy\Position\HasPosition;

class Category extends Model
{
    use HasPosition;
}

You also need to add a position column to the model's table using a migration:

Schema::create('categories', function (Blueprint $table) {
    $table->integer('position')->unsigned()->index();
});

And that's it!

📄 Documentation

How it works

Models with positions have an integer attribute named position, which indicates their position in the sequence. This attribute is automatically calculated upon insertion and is utilized for sorting the models during query operations.

Creating models

The position attribute is a kind of array index and is automatically inserted when a new model is created. For example:

$category = Category::create();
echo $category->position; // 0

$category = Category::create();
echo $category->position; // 1

$category = Category::create();
echo $category->position; // 2

Starting position

By default, the first record in the sequence is assigned a position value of 0. If you want to specify a custom number to start counting models, you can override the getStartPosition method in your model:

public function getStartPosition(): int
{
    return 1;
}

In this example, the first record will be assigned a position value of 1.

Ordering

By default, the newly created model is assigned the position at the end of the sequence.

For example, if you want to create models at the beginning of the sequence, you can override the getNextPosition method in your model:

public function getNextPosition(): int
{
    return $this->getStartPosition();
}

In this example, each new model will be assigned the starting position and will be positioned at the beginning of the sequence. The positions of other models in the sequence will be automatically updated:

$first = Category::create();
echo $first->position; // 0

$second = Category::create();
echo $second->position; // 0
echo $first->position; // 1 (automatically updated)

$third = Category::create();
echo $third->position; // 0
echo $second->position; // 1 (automatically updated)
echo $first->position; // 2 (automatically updated again)

You can also use negative positions. For example, the -1 position indicates that the model will be positioned at the end of the sequence. It is almost identical to Model::count() - 1. This is the default behavior.

Deleting models

When a model is deleted, the positions of other records in the sequence are automatically updated.

Querying models

To select models in the arranged sequence, you can use the orderByPosition scope. For example:

Category::orderByPosition()->get();

Automatic ordering when querying models

The orderByPosition scope is not applied by default because the corresponding SQL statement will be added to all queries, even where it is not required.

It is recommended to manually add the scope in all places where you need it.

However, if you want to enable auto-ordering for all query operations, you can override the alwaysOrderByPosition method in your model as following:

public function alwaysOrderByPosition(): bool
{
    return true;
}

Moving items

Update

To move a model to an arbitrary position in the sequence, you can simply update its position. For example:

$category->update([
    'position' => 3
]);

The positions of other models will be automatically recalculated as well.

Move

You can also use the move method, which sets a new position value and updates the model immediately. For example:

$category->move(3);

If you want to move the model to the end of the sequence, you can use a negative position value. For example:

$category->move(-1); // Move to the end

Swap

The swap method swaps the position of two models. For example:

$category->swap($anotherCategory);

Arrange models

It is also possible to arrange models by their IDs. The position of each model will be recalculated according to the index of its ID in the given array. You can also provide a second argument as a starting position. For example:

Category::arrangeByKeys([3, 5, 7]);

Grouping models

Position grouping is particularly useful when you want to maintain position sequences separately for different groups of records within the same table.

To enable position grouping, you can specify one or more database columns that act as the grouping criteria for positions using the groupPositionBy method in your model:

public function groupPositionBy(): array
{
    return [
        'category_id',
    ];
}

Position lock

By default, when you change the position or group of a model, the PositionObserver automatically updates the positions of other models in the sequence by performing additional database queries. If you need to disable this behavior for any reason, you can do it like so:

use Nevadskiy\Position\PositionObserver;

PositionObserver::lockFor(Category::class);

$category->update(['position' => 1]);

PositionObserver::unlockFor(Category::class);

📑 Changelog

Please see CHANGELOG for more information what has changed recently.

☕ Contributing

Please see CONTRIBUTING for more information.

🔓 Security

If you discover any security related issues, please e-mail me instead of using the issue tracker.

📜 License

The MIT License (MIT). Please see LICENSE for more information.

🛠️ Todo List

  • support swap for models from different groups