pion/laravel-support-eloquent
pion/laravel-support-eloquent offers reusable Eloquent support tools for Laravel, including helpful model traits and utilities that simplify common database and model tasks. It integrates smoothly with existing models to help you write cleaner, more maintainable application code.
Install and inspect before you import.
composer require pion/laravel-support-eloquent
composer show pion/laravel-support-eloquent # confirm the tag you actually got
Small utility packages like this one have no service provider to register and no config to publish in the common case — the whole value is in src/. Read it first, in this order:
composer.json — PHP and illuminate/* constraints. This is the single most important file: a package aimed at "small traits" is usually tolerant across Laravel versions, but the constraint tells you whether it was written against Laravel 8-era or 11-era conventions.src/ — each trait/class is short enough to read in one sitting. Look for boot{TraitName}() (runs on model boot), initialize{TraitName}() (runs per instance), and any scope* methods.README.md / CHANGELOG.md — the changelog is often the only place that documents breaking changes for a 3-star package.First use case — adopt exactly one trait on exactly one model.
Do not refactor your base model on day one. Pick the trait that solves a problem you already have (a UUID/primary-key concern, an active/inactive scope, an ordering concern, an attribute-casting helper — whichever exists in src/), apply it to a throwaway model, and run:
$model = MyModel::factory()->create();
dump(class_uses_recursive($model)); // confirm the trait is actually on the instance
dump($model->toArray()); // confirm casts/attributes didn't change shape
If toArray() is unchanged and your existing tests are green, you have a safe adoption. That one-model check is the whole "getting started" path — this package is a toolbox, not a framework, and the correct first step is a surgical trial, not a migration.
Trait composition per model (the intended pattern). The package is designed to be used à la carte. Prefer explicit use statements on each model over blanket inheritance, because it keeps class_uses_recursive() readable and makes it obvious in code review which models depend on the package:
class Article extends Model
{
use HasFactory;
use SoftDeletes;
use \Pion\Support\Eloquent\Traits\...; // name from src/, no base-class coupling
}
Centralize in an abstract base model if you adopt more than ~3 traits. Once several models share the same set, promote them to App\Models\Concerns\BaseModel (your own class) rather than repeating the use block. Critically, make your abstract class the seam, not the vendor's — that way a future decision to drop the package is a one-file change:
abstract class BaseModel extends \Illuminate\Database\Eloquent\Model
{
use HasFactory;
// vendor traits here, isolated to one place
}
Chain the scopes like any other Eloquent scope. Scope methods in support packages are plain query builders, so they compose with your own:
Article::query()
->active() // package scope, if present
->where('published_at', '<=', now())
->latest()
->paginate();
The thing to verify in your test suite is that the package scope returns the same builder instance (assertSame($query, $query->active())) so it doesn't accidentally execute or clone — that's the classic defect in hand-rolled scope traits.
Use the boot hooks, don't fight them. If a trait defines boot{TraitName}(), it is registering model events (creating, saving, deleting) on your behalf. Do not register the same event a second time in the model's booted() method — you'll get double-fires (two UUIDs generated, two slugs appended). Audit booted() in any model you retrofit.
Integration with factories and seeders. Traits that mutate attributes on creating will run inside factory()->create() and inside Model::create(), but not inside factory()->make() or new Model([...]). If your tests assert on generated values, use create(); if you're asserting on raw input, use make(). This distinction bites people constantly.
Testing pattern. Add one focused test per trait application rather than trusting the package:
it('applies the trait behaviour', function () {
$model = MyModel::factory()->create();
expect($model->{column})->not->toBeNull();
});
Because the package has zero dependents and three stars, your test suite is the compatibility guarantee. Treat every trait adoption as a piece of code you now own.
Trait method collisions are the number one failure mode. Laravel's use TraitA, TraitB { TraitA::method insteadof TraitB; } resolution is on you. The most common collisions are with Laravel's own SoftDeletes, HasUuids (Laravel 9+), HasSlug-style packages, and any booted()/initialize() pair. Before adding a trait, grep src/ for method names and diff them against what the model already inherits.
"Small support traits" often duplicate framework features now shipped in core. If the package provides UUID keys, HasUuids, enum casting, or AsArrayObject-style helpers, check the Laravel version you're on first — the built-in may be better maintained and better documented. Prefer core when the behaviour is identical; keep the package only where it does something core doesn't.
Migrations are not shipped. Traits that assume a column shape (UUID primary key, slug column, sort-order integer, nullable timestamp) will fail at runtime, not at install time, with SQLSTATE errors on create(). Whenever you adopt a trait, write the migration in the same PR and add a Schema::hasColumn() assertion in a test if the trait is central to the model.
$fillable / $guarded are unaffected. Traits do not make attributes mass-assignable. If a generated column looks "not saved", it is almost always the guard, not the trait — check $model->getAttributes() after fill() to distinguish.
Static/boot caching. Anything registered in boot{Trait}() runs once per model class per process. In long-running workers (Octane, queue daemons) a change to the trait's static state will not re-register. If you see behaviour that "works in tests, wrong in the worker", that's the cause.
Debugging checklist, in order:
class_uses_recursive($model) — is the trait actually there? (Autoload issues after adding a use statement usually mean a stale composer dump-autoload.)$model->getAttributes() vs $model->toArray() — did the value get set, or only cast?Model::getEventDispatcher()->getListeners('eloquent.creating: App\Models\X') — how many listeners are registered? (More than one means a duplicate boot registration.)DB::listen(fn ($q) => dump($q->sql, $q->bindings)) — is the scope emitting what you expect?Pin the version and isolate the seam. With three stars and a single maintainer, treat the dependency as a vendored convenience:
composer.lock.App\Models\Concerns\* trait (use VendorTrait;) or in a single BaseModel, so replacing it later is mechanical rather than a repo-wide find-and-replace.app/ and own it. That's not a slight on the package — it's the correct calculus for a dependency with zero dependents.Extension points. Model methods always win over trait methods, so you can override any helper without editing vendor code — but call parent::method() deliberately, and remember that insteadof/as aliasing is the only way to resolve two traits fighting over one name. For relation helpers, prefer Laravel's Model::resolveRelationUsing() from a service provider over monkey-patching.
Config quirks. If the package does ship a config file, publish with php artisan vendor:publish --provider="..." and commit it — otherwise defaults shift silently on upgrade. If it ships none, don't invent one; the package's behaviour is then entirely determined by your model definitions, which is the point.
How can I help you explore Laravel packages today?