ldc-boutique-cnc-sable/app/Actions/Common/ChangeModelOrderPosition.php

103 lines
3.2 KiB
PHP
Raw Permalink Normal View History

<?php
declare(strict_types=1);
namespace App\Actions\Common;
use App\Contracts\Action;
use App\Contracts\ActionInput;
use App\Interfaces\ModelWithOrder;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Webmozart\Assert\Assert;
/**
* @implements \App\Contracts\Action<\App\Actions\Common\ChangeModelOrderPositionInput, bool>
*/
final class ChangeModelOrderPosition implements Action
{
private const int MAX_ORDER_VALUE = 4294967295;
/**
* @param \App\Actions\Common\ChangeModelOrderPositionInput $input
*/
#[\Override]
public function __invoke(ActionInput $input): bool
{
Assert::isInstanceOf($input, ChangeModelOrderPositionInput::class);
$model = $input->model;
$currentOrder = $model->getOrder();
$targetOrder = $input->targetOrder;
if ($currentOrder === $targetOrder) {
return true;
}
$usesSoftDeletes = in_array(SoftDeletes::class, class_uses_recursive($model), true)
|| method_exists($model, 'trashed');
/** @var Builder<Model&ModelWithOrder> $baseQuery */
$baseQuery = $usesSoftDeletes
// @phpstan-ignore method.notFound (withTrashed)
? $model::query()->withTrashed()
: $model::query();
/** @var int|null $maxOrder */
$maxOrder = (clone $baseQuery)->max('order');
if ($maxOrder !== null && $targetOrder > $maxOrder) {
$targetOrder = (int) $maxOrder;
}
if ($currentOrder === $targetOrder) {
return true;
}
// Deactivate timestamps
$model->timestamps = false;
// Step 1: Temporarily park the moving model at MAX_ORDER_VALUE
$model->setOrder(self::MAX_ORDER_VALUE);
$model->saveQuietly();
if ($targetOrder < $currentOrder) {
// Moving UP
/** @var \Illuminate\Database\Eloquent\Collection<int, Model&ModelWithOrder> $itemsToShift */
$itemsToShift = (clone $baseQuery)
->where('order', '>=', $targetOrder)
->where('order', '<', $currentOrder)
->whereKeyNot($model->getKey())
->orderBy('order', 'desc')
->get();
foreach ($itemsToShift as $item) {
$item->timestamps = false;
$item->setOrder($item->getOrder() + 1);
$item->saveQuietly();
}
} else {
// Moving DOWN
/** @var \Illuminate\Database\Eloquent\Collection<int, Model&ModelWithOrder> $itemsToShift */
$itemsToShift = (clone $baseQuery)
->where('order', '>', $currentOrder)
->where('order', '<=', $targetOrder)
->whereKeyNot($model->getKey())
->orderBy('order', 'asc')
->get();
foreach ($itemsToShift as $item) {
$item->timestamps = false;
$item->setOrder($item->getOrder() - 1);
$item->saveQuietly();
}
}
// Step 2: Set the moving model to the target order
$model->setOrder($targetOrder);
$model->saveQuietly();
return true;
}
}