thunderwolf / sortable
基于 Propel sortable 行为的 Laravel Eloquent 排序组件
Requires
- php: >=8.1
- illuminate/database: ^7.0|^8.0|^9.0|^10.0
- illuminate/events: ^7.0|^8.0|^9.0|^10.0
- illuminate/support: ^7.0|^8.0|^9.0|^10.0
Requires (Dev)
- phpunit/phpunit: ^9.5
This package is auto-updated.
Last update: 2024-09-25 20:46:37 UTC
README
排序行为允许模型成为有序列表,并提供多种方法高效地遍历此列表。
要使用它,需要在模型中使用 Sortable
特性 并配置,最简单的配置如下所示
public static function sortable(): array
{
return [];
}
Sortable
特性 将覆盖默认的 Builder,如下所示
/**
* Override -> Create a new Eloquent query builder for the model.
* If you have more Behaviors using this kind on Override create own and use Trait SortableBuilderTrait
*
* @param Builder $query
* @return SortableBuilder
*/
public function newEloquentBuilder($query): SortableBuilder
{
return new SortableBuilder($query);
}
如果您使用多个特性或自定义覆盖,请只使用 SortableBuilderTrait
特性,因为 SortableBuilder
类看起来如下
<?php
namespace Thunderwolf\EloquentSortable;
use Illuminate\Database\Eloquent\Builder;
class SortableBuilder extends Builder
{
use SortableBuilderTrait;
}
基本用法
使用此包最基本的方法是创建一个使用 Sortable
特性 的模型,如下所示
<?php
use Illuminate\Database\Eloquent\Model;
use Thunderwolf\EloquentSortable\Sortable;
class Task extends Model
{
use Sortable;
protected $table = 'tasks';
protected $fillable = ['title'];
public $timestamps = false;
public static function sortable(): array
{
return [];
}
}
注册 SortableServiceProvider
之后,您也可以使用蓝图通过 createSortable
辅助方法创建表,如下所示
$schema->create('tasks', function (Blueprint $table1) {
$table1->increments('id');
$table1->string('title');
$table1->createSortable([]);
});
以类似的方式处理迁移。
现在,模型具有被插入到有序列表中的能力,如下所示
<?php
$t1 = new Task();
$t1->setAttribute('title', 'Wash the dishes');
$t1->save();
echo $t1->getSortableRank(); // 1, the first rank to be given (not 0)
$t2 = new Task();
$t2->setAttribute('title', 'Do the laundry');
$t2->save();
echo $t2->getSortableRank(); // 2
$t3 = new Task();
$t3->setAttribute('title', 'Rest a little');
$t3->save();
echo $t3->getSortableRank(); // 3
只要您保存新对象,Propel 就会自动将它们分配到列表中的第一个可用排名。
这些方法返回的通常是带有属性和相关模型的常规 Propel 模型对象。排序行为还向对象添加了检查方法
<?php
echo $t2->isFirst(); // false
echo $t2->isLast(); // false
echo $t2->getSortableRank(); // 2
一旦您构建了有序列表,您可以使用排序行为添加的任何方法遍历它。例如
<?php
$firstTask = Task::query()->findOneByRank(1); // $t1
$secondTask = $firstTask->getNext(); // $t2
$lastTask = $secondTask->getNext(); // $t3
$secondTask = $lastTask->getPrevious(); // $t2
$allTasks = Task::query()->findList(); // => collection($t1, $t2, $t3)
$allTasksInReverseOrder = Task::query()->orderByRank('desc')->get(); // => collection($t3, $t2, $t2)
在列表中操作对象
您可以使用 moveUp()、moveDown()、moveToTop()、moveToBottom()、moveToRank() 和 swapWith() 方法中的任何一种移动列表中的对象。这些操作是即时的,不需要保存模型
<?php
$t1 = Task::query()->findOneByRank(1);
$t2 = Task::query()->findOneByRank(2);
// Initial list is: 1 - Wash the dishes, 2 - Do the laundry, 3 - Rest a little
$t2->moveToTop(); // will end with this order: 1 - Do the laundry, 2 - Wash the dishes, 3 - Rest a little
$t2->moveToBottom(); // will end with this order: 1 - Wash the dishes, 2 - Rest a little, 3 - Do the laundry
$t2->moveUp(); // will end with this order: 1 - Wash the dishes, 2 - Do the laundry, 3 - Rest a little
$t2->swapWith($t1); // will end with this order: 1 - Do the laundry, 2 - Wash the dishes, 3 - Rest a little
$t2->moveToRank(3); // will end with this order: 1 - Wash the dishes, 2 - Rest a little, 3 - Do the laundry
$t2->moveToRank(2); // will end with this order: 1 - Wash the dishes, 2 - Do the laundry, 3 - Rest a little
默认情况下,新对象被添加到列表的底部。但是,您也可以使用 insertAtTop()、insertAtBottom() 和 insertAtRank() 方法将它们插入到特定位置。请注意,insertAtXXX 方法不会保存对象
<?php
// Initial list is: 1 - Wash the dishes, 2 - Do the laundry, 3 - Rest a little
$t4 = new Task();
$t4->setAttribute('title', 'Clean windows');
$t4->insertAtRank(2);
$t4->save(); // The list is now 1 - Wash the dishes, 2 - Clean Windows, 3 - Do the laundry, 4 - Rest a little
每次您删除() 对象时,排名都会重新排列以填补空缺
<?php
$t4->delete();
// The list is now 1 - Wash the dishes, 2 - Do the laundry, 3 - Rest a little
提示
您可以通过调用
removeFromList()
从列表中删除对象而无需删除它。不要忘记之后调用save()
以便重新排列列表中的其他对象以填补空缺。
多个列表
当您需要为单个模型存储多个列表时(例如,每个用户一个任务列表) - 使用每个列表的 Scope。这需要在行为定义中启用 Scope 支持,将 use_scope 参数设置为 true 并设置 scope_columns,如下面的示例所示。创建具有 Sortable
特性 和如下配置的模型
<?php
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Thunderwolf\EloquentSortable\Sortable;
class SingleScopedTask extends Model
{
use Sortable;
protected $table = 'single-scoped-tasks';
protected $fillable = ['title'];
public $timestamps = false;
public static function sortable(): array
{
return ['use_scope' => true, 'scope_columns' => ['single_scoped_user_id']];
}
/**
* When planning to use `Inserting Related Models` way of set user id we need to override setter - we are using 8.x
*
* @param $value
* @return void
*/
public function setSingleScopedUserIdAttribute($value)
{
$this->oldScope['single_scoped_user_id'] = $this->attributes['single_scoped_user_id']??null;
$this->attributes['single_scoped_user_id'] = $value;
}
public function user(): BelongsTo
{
return $this->belongsTo(SingleScopedUser::class, 'single_scoped_user_id');
}
}
在这个示例中,我们还在使用 SingleScopedUser
模型,如下所示
<?php
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class SingleScopedUser extends Model
{
protected $table = 'single-scoped-users';
protected $fillable = ['username'];
public $timestamps = false;
public function tasks(): HasMany
{
return $this->hasMany(SingleScopedTask::class);
}
}
注册 SortableServiceProvider
之后,您也可以使用蓝图通过 createSortable
辅助方法创建表,如下所示
$schema->create('single-scoped-tasks', function (Blueprint $table2) {
$table2->increments('id');
$table2->unsignedInteger('single_scoped_user_id');
$table2->string('title');
$table2->createSortable(['use_scope' => true, 'scope_columns' => ['single_scoped_user_id']]);
});
$schema->create('single-scoped-users', function (Blueprint $table3) {
$table3->increments('id');
$table3->string('username');
});
以类似的方式处理迁移。
在上面的示例中,您可以有您需要的任意多个列表。
您可以直接设置 Scope,如下所示
<?php
// We are assuming this table have those 2 records:
$paul = SingleScopedUser::query()->find(1); // paul
$john = SingleScopedUser::query()->find(2); // john
$t1 = new SingleScopedTask();
$t1->setAttribute('title', 'Wash the dishes');
$t1->setSortableScope('single_scoped_user_id', $paul->getKey());
$t1->save();
echo $t1->getSortableRank(); // 1
$t2 = new SingleScopedTask();
$t2->setAttribute('title', 'Do the laundry');
$t2->setSortableScope('single_scoped_user_id', $paul->getKey());
$t2->save();
echo $t2->getSortableRank(); // 2
$t3 = new SingleScopedTask();
$t3->setAttribute('title', 'Rest a little');
$t3->setSortableScope('single_scoped_user_id', $john->getKey());
$t3->save();
echo $t3->getSortableRank(); // 1, because John has his own task list
或者您可以使用关系,如下所示
// We are assuming this table have those 2 records:
$paul = SingleScopedUser::query()->find(1); // paul
$john = SingleScopedUser::query()->find(2); // john
$t1 = new SingleScopedTask(['title' => 'Wash the dishes']);
$paul->tasks()->save($t1);
echo $t1->getSortableRank(); // 1
$t2 = new SingleScopedTask(['title' => 'Do the laundry']);
$paul->tasks()->save($t2);
echo $t2->getSortableRank(); // 2
$t3 = new SingleScopedTask(['title' => 'Rest a little']);
$john->tasks()->save($t3);
echo $t3->getSortableRank(); // 1, because John has his own task list
现在生成的方法接受一个 $scope 参数,以将查询限制在给定的 Scope 中
<?php
$firstPaulTask = SingleScopedTask::query()->findOneByRank(1, ['single_scoped_user_id' => $paul->getKey()]); // $t1
$lastPaulTask = $firstPaulTask->getNext(); // $t2
$firstJohnTask = SingleScopedTask::query()->findOneByRank(1, ['single_scoped_user_id' => $john->getKey()]); // $t3
使用排序行为和 Scope 的模型还受益于一个名为 inList()
的额外 Builder
方法
<?php
$allPaulsTasks = SingleScopedTask::query()->inList(['single_scoped_user_id' => $paul->getKey()])->get();
// => collection($t1, $t2)
多列 Scope
我们可以使用多个列作为 Scope。
要实现这一点,只需创建具有 Sortable
特性 和如下配置的模型
<?php
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Thunderwolf\EloquentSortable\Sortable;
class MultiScopedTask extends Model
{
use Sortable;
protected $table = 'multi-scoped-tasks';
protected $fillable = ['title'];
public $timestamps = false;
public static function sortable(): array
{
return ['use_scope' => true, 'scope_columns' => ['multi_scoped_user_id', 'multi_scoped_group_id']];
}
/**
* When planning to use `Inserting Related Models` way of set user id we need to override setter - we are using 8.x
*
* @param $value
* @return void
*/
public function setMultiScopedUserIdAttribute($value)
{
$this->oldScope['multi_scoped_user_id'] = $this->attributes['multi_scoped_user_id']??null;
$this->attributes['multi_scoped_user_id'] = $value;
}
/**
* When planning to use `Inserting Related Models` way of set user id we need to override setter - we are using 8.x
*
* @param $value
* @return void
*/
public function setMultiScopedGroupIdAttribute($value)
{
$this->oldScope['multi_scoped_group_id'] = $this->attributes['multi_scoped_group_id']??null;
$this->attributes['multi_scoped_group_id'] = $value;
}
public function user(): BelongsTo
{
return $this->belongsTo(MultiScopedUser::class, 'multi_scoped_user_id');
}
public function group(): BelongsTo
{
return $this->belongsTo(MultiScopedGroup::class, 'multi_scoped_group_id');
}
}
在这个示例中,我们还在使用 MultiScopedUser
模型,如下所示
<?php
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class MultiScopedUser extends Model
{
protected $table = 'multi-scoped-users';
protected $fillable = ['username'];
public $timestamps = false;
public function tasks(): HasMany
{
return $this->hasMany(MultiScopedTask::class);
}
}
和一个 MultiScopedGroup
模型,如下所示
<?php
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class MultiScopedGroup extends Model
{
protected $table = 'multi-scoped-groups';
protected $fillable = ['name'];
public $timestamps = false;
public function tasks(): HasMany
{
return $this->hasMany(MultiScopedTask::class);
}
}
注册 SortableServiceProvider
之后,您也可以使用蓝图通过 createSortable
辅助方法创建表,如下所示
$schema->create('multi-scoped-tasks', function (Blueprint $table4) {
$table4->increments('id');
$table4->unsignedInteger('multi_scoped_user_id');
$table4->unsignedInteger('multi_scoped_group_id');
$table4->string('title');
$table4->createSortable(['use_scope' => true, 'scope_columns' => ['multi_scoped_user_id', 'multi_scoped_group_id']]);
});
$schema->create('multi-scoped-users', function (Blueprint $table5) {
$table5->increments('id');
$table5->string('username');
});
$schema->create('multi-scoped-groups', function (Blueprint $table6) {
$table6->increments('id');
$table6->string('name');
});
以类似的方式处理迁移。
通过上述配置,Trait可以为每个用户和每个组管理一个可排序的任务列表。
您可以直接设置如下所示的“用户-组”范围。
<?php
// We are assuming this table have those 2 records:
$paul = MultiScopedUser::query()->find(1); // paul
$john = MultiScopedUser::query()->find(2); // john
// We are assuming this table have those 2 records:
$adminGroup = MultiScopedGroup::query()->find(1); // admin
$userGroup = MultiScopedGroup::query()->find(2); // user
// now onto the tasks
$t1 = new MultiScopedTask();
$t1->setAttribute('title', 'Create permissions');
$t1->setSortableScope('multi_scoped_user_id', $paul->getKey());
$t1->setSortableScope('multi_scoped_group_id', $adminGroup->getKey());
$t1->save();
echo $t1->getSortableRank(); // 1
$t2 = new MultiScopedTask();
$t2->setAttribute('title', 'Grant permissions to users');
$t2->setSortableScope('multi_scoped_user_id', $paul->getKey());
$t2->setSortableScope('multi_scoped_group_id', $adminGroup->getKey());
$t2->save();
echo $t2->getSortableRank(); // 2
$t3 = new MultiScopedTask();
$t3->setAttribute('title', 'Install servers');
$t3->setSortableScope('multi_scoped_user_id', $john->getKey());
$t3->setSortableScope('multi_scoped_group_id', $adminGroup->getKey());
$t3->save();
echo $t3->getSortableRank(); // 1, because John has his own task list inside the admin-group
$t4 = new MultiScopedTask();
$t4->setAttribute('title', 'Manage content');
$t4->setSortableScope('multi_scoped_user_id', $john->getKey());
$t4->setSortableScope('multi_scoped_group_id', $userGroup->getKey());
$t4->save();
echo $t4->getSortableRank(); // 1, because John has his own task list inside the user-group
或者您可以使用关系,如下所示
// We are assuming this table have those 2 records:
$paul = MultiScopedUser::query()->find(1); // paul
$john = MultiScopedUser::query()->find(2); // john
// We are assuming this table have those 2 records:
$adminGroup = MultiScopedGroup::query()->find(1); // admin
$userGroup = MultiScopedGroup::query()->find(2); // user
// now onto the tasks
$t1 = new MultiScopedTask(['title' => 'Create permissions']);
$t1->user()->associate($paul);
$t1->group()->associate($adminGroup);
$t1->save();
echo $t1->getSortableRank(); // 1
$t2 = new MultiScopedTask(['title' => 'Grant permissions to users']);
$t2->user()->associate($paul);
$t2->group()->associate($adminGroup);
$t2->save();
echo $t2->getSortableRank(); // 2
$t3 = new MultiScopedTask(['title' => 'Install servers']);
$t3->user()->associate($john);
$t3->group()->associate($adminGroup);
$t3->save();
echo $t3->getSortableRank(); // 1, because John has his own task list inside the admin-group
$t4 = new MultiScopedTask(['title' => 'Manage content']);
$t4->user()->associate($john);
$t4->group()->associate($userGroup);
$t4->save();
echo $t4->getSortableRank(); // 1, because John has his own task list inside the user-group
现在生成的函数接受每个作用域列的一个参数,以将查询限制在给定的作用域内。
<?php
// $t1
$firstPaulAdminTask = MultiScopedTask::query()->findOneByRank(
1,
['multi_scoped_user_id' => $paul->getKey(), 'multi_scoped_group_id' => $adminGroup->getKey()]
);
// $t2
$lastPaulTask = $firstPaulAdminTask->getNext();
// $t4
$firstJohnUserTask = MultiScopedTask::query()->findOneByRank(
1,
['multi_scoped_user_id' => $john->getKey(), 'multi_scoped_group_id' => $userGroup->getKey()]
);
使用排序行为和 Scope 的模型还受益于一个名为 inList()
的额外 Builder
方法
<?php
$allJohnsUserTasks = MultiScopedTask::query()
->inList(['multi_scoped_user_id' => $john->getKey(), 'multi_scoped_group_id' => $userGroup->getKey()])
->get();
// => collection($t4)
配置
默认情况下,使用以下配置
public static function sortable(): array
{
return [];
}
模型中会添加列sortable_rank
。您可以使用如下配置进行配置
public static function sortable(): array
{
return ['rank_column' => 'my_rank_column'];
}
如上章节所述,您还可以使用作用域,它可以是一个或多个列。例如配置如下
public static function sortable(): array
{
return ['use_scope' => true, 'scope_columns' => ['multi_scoped_user_id', 'multi_scoped_group_id']];
}
要使用作用域,您必须将use_scope
设置为true
。
无论您为您的列指定什么名称,可排序行为总是添加以下代理方法,这些方法映射到正确的列
<?php
$task->getSortableRankName(); // returns name of the rank column
$task->getSortableRank(); // returns value of the rank column
$task->setSortableRank($rank); // allows set rank value
$task->isSortableScopeUsed(); // returns use_scope configuration value
$task->getSortableScopeNames(); // returns scope_columns configuration value
$task->getSortableScope($key); // returns value of the scope column by the scope column name
$task->getSortableScopes(); // returns array of scope values where key is scope column name and value scope column value
$task->setSortableScope($key, $scope); // allows set scope column value by the scope column name as a key
提示
如果您计划使用关系和作用域,请检查上面的示例,因为您需要覆盖每个作用域列的设置器。
完整API
以下是行为添加到模型对象的函数列表
<?php
// storage columns accessors
public function getSortableRankName(): string
public function getSortableRank(): int
public function setSortableRank(int $rank): void
// only for behavior with use_scope
public function isSortableScopeUsed(): bool
public function getSortableScopeNames(): array
public function getSortableScope(string $key): ?int
public function getSortableScopes(): array
public function setSortableScope(string $key, int $scope): void
// inspection methods
public function isFirst(): bool
public function isLast(): bool
// list traversal methods
public function getNext(): Model
public function getPrevious(): Model
// methods to insert an object in the list (require calling save() afterwards)
public function insertAtRank(int $rank): Model
public function insertAtBottom(): Model
public function insertAtTop(): Model
// methods to move an object in the list (immediate, no need to save() afterwards)
public function moveToRank(int $newRank): Model
public function swapWith(Model $object): Model
public function moveUp(): Model
public function moveDown(): Model
public function moveToTop(): Model
public function moveToBottom(): Model
// method to remove an object from the list (requires calling save() afterwards)
public function removeFromList(): Model
以下是行为添加到构建器的函数列表
<?php
public function filterByRank(int $rank, array $scopes = []): self
public function orderByRank(string $order = 'asc'): self
public function findOneByRank(int $rank, array $scopes = []): Model
public function findList(array $scopes = []): ?Collection
public function countList(array $scopes = []): int
public function deleteList(array $scopes = []): int
public function getMaxRank(array $scopes = []): ?int
public function reorder(array $order): bool
// only for behavior with use_scope
public function inList(array $scopes): self