*/ 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 $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 $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 $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; } }