illegal/laravel-utils
A small collection of helper utilities for Laravel applications, bundling convenient functions and traits for everyday development tasks. Lightweight, easy to install, and designed to drop into any Laravel project without extra configuration.
Re-read the repository before trusting the release note. A single bullet ("Added Sortable trait") tells you almost nothing about what landed, and it changes the shape of the package: an archived, dependency-free helper collection now ships a UI-layer trait. Before installing anything:
composer.json and check both the require and require-dev blocks for livewire/livewire. If Livewire is a hard require, installing this package for one string helper drags Livewire into a non-Livewire app. If it's absent entirely, the trait is written against Livewire's public API and will break silently on version drift — that's actually the better outcome, but only if you pin Livewire yourself.boot/initialize trait hooks, $this->emit() vs $this->dispatch(), and any wire:sort / wire:sortable Blade assumptions.src/ for the trait's actual surface: what methods it adds, what properties it expects you to define ($sortColumn, $sortable, etc.), and whether it registers any JS assets or expects a published view. git log/CHANGELOG on v1.2 tells you whether this was a one-commit addition.config/ file or publishes() call arrived with v1.2. New UI features often bring new config; a config file means mergeConfigFrom and a publish step you didn't have before.If you're already a consumer, this is a straightforward composer update — but pin it, since the tag cadence has now proven non-zero:
composer require illegal/laravel-utils:1.2.* --no-update
composer update illegal/laravel-utils --with-dependencies
First use case: build a throwaway Livewire component with a position-ordered list and apply the trait to it in php artisan tinker-adjacent fashion — i.e. in a scratch component you delete afterwards. Drag one row, assert the database order changed, and check what the trait did to your model (single update, N updates, transaction?). Do not adopt it into an existing sortable UI until you've seen that write path; if the package's idea of persistence doesn't match yours, the trait is 20 lines you can write yourself.
If you are not a Livewire shop, v1.2 changes nothing for you. The first session remains: prove the one or two helpers you actually want work on your Laravel and PHP versions, and check whether Str, Arr, Number, once(), and Collection now cover them.
Wrap, don't sprinkle. Unchanged, and now more important: global functions and a Livewire trait in one package means app code can end up depending on it from two directions. Keep a thin app/Support/Utils.php for the helpers, and a app/Livewire/Concerns/SortsList.php trait that uses the package trait, so both replace points are single files.
Apply Sortable per component, never on a base class. The trait is the newest and least-exercised code in the package. Put it on one component, run that component's tests, then decide. If it works, re-export it through your own SortsList concern so you control the method names your views call.
Read the persistence contract before wiring the view. Whatever the trait does on drop — update per row, a bulk CASE WHEN upsert, a position swap — is now your ordering logic. If it does sequential single-row updates, wrap the whole reorder in DB::transaction() in an overridden handler; the trait almost certainly doesn't.
Override rather than modify. Traits in Livewire are resolved on the component instance, so a method defined directly on the component wins over the trait's. Redefine the sort handler on your component to add authorization ($this->authorize('reorder', $model)), logging, or a $this->dispatch('list-reordered') event, and leave the package trait untouched. This is your only clean extension point short of forking.
Fork-and-path-repository pattern. Still the right long-term integration for an unmaintained package, and now the realistic way to fix the trait for Livewire 3:
"repositories": [{ "type": "path", "url": "packages/laravel-utils" }],
"require": { "illegal/laravel-utils": "*" }
Commit the fork under packages/, bump illuminate/support and adapt the trait to your Livewire major there. Because v1.2 landed at all, "type": "vcs" against your own fork is also fine — just never depend on the upstream tag for something you've patched.
Test the trait with Livewire::test(), not browser clicks. Drag-and-drop is the easy part to get wrong and the hard part to test:
Livewire::test(TaskList::class)
->call('sortItem', $task->id, 3) // use the trait's actual method name
->assertDispatched('list-reordered');
$this->assertSame([1,2,3,4], Task::orderBy('position')->pluck('position')->all());
The characterization test is your contract, because upstream will not tell you if the trait's behavior changes.
Fork the ordering logic when the requirements grow. Drag-sort features tend to accumulate: per-user ordering, grouped lists, optimistic UI, keyboard reordering. Any of those outgrow a one-trait helper fast. Plan the extraction: your app/Livewire/Concerns/SortsList.php becomes a hand-written implementation, and the package trait is deleted.
Migration pattern, revised. Inventory usage with a grep for the trait name (it's now a class-level dependency, so a string grep of use Sortable finds every site — easier than the helper functions). Remove trait by trait: drop the use line, paste an equivalent method onto the component, delete the dependency when the last usage is gone.
emit → dispatch, hook renames, script injection). A trait that works today can break on your next Livewire minor. Pin Livewire explicitly and add the sort component to your upgrade checklist.composer.json. wire:sort/wire:sortable typically needs the Sortable JS plugin registered in your Livewire script config (@livewireScripts / Livewire::scripts) or Alpine import. If drag does nothing but a call() from tests works, it's JS wiring, not PHP. Check the package for a published view or asset before you debug your component.bootSortable(), initializeSortable(), or mountSortable(), that code executes on every request touching the component — including ones that never reorder. A missing property or a null model there is a hard failure at mount time.integer vs unsignedInteger), whether it's nullable, and whether you need a composite index like (user_id, position) before you have rows with duplicate positions.position values. Add a unique index if your data can tolerate it, or a periodic re-numbering job. The trait will not handle this for you.DB::listen in a local test) before shipping to a list that can grow.autoload.files collide with your own app/Support/helpers.php and symfony/polyfill-*. Grep for name overlap.php artisan package:discover -v and bootstrap/cache/packages.php. Note that a trait doesn't need discovery — it's class-autoloaded — so a "the trait works but the helpers don't" symptom points straight at discovery, not the package.vendor/.../config against your copy after the update rather than blindly re-publishing.Str::of(), Arr::get(), Number::format(), once(), throw_if()/throw_unless(), rescue(). If v1.2's trait is the only thing you want, extract the 20–30 lines and drop the dependency; you'll be ahead on both Livewire-version agility and upgrade review.illuminate/support constraint, the absence of a security path, or the zero-dependents debugging tax. The play is unchanged: vendor it, add tests for both the helpers and the sort behavior, bump constraints in your fork, and tag your own release. Treat v1.2 as a starting point to fork from, not as evidence the upstream is now supported.How can I help you explore Laravel packages today?