renoki-co/laravel-useful-casts
renoki-co/laravel-useful-casts provides a collection of handy custom Eloquent casts for Laravel models, reducing boilerplate when converting database attributes into common PHP types and objects. Drop-in casts keep model code cleaner and more expressive.
renoki-co/laravel-useful-casts is a grab-bag of ready-made Eloquent cast classes — small implementations of Illuminate\Contracts\Database\Eloquent\CastsAttributes that cover value shapes Laravel doesn't ship (and that you'd otherwise re-write as an accessor + mutator pair in every project). The 2.2.0 release is a support-matrix bump, not an API change: it adds Laravel 10.x and PHP 8.2, and drops Laravel 8.x and PHP 8.0.
Install and pin the version deliberately. 2.2.0 requires PHP 8.1+ and Laravel 9.x or 10.x. If any app or CI job is still on PHP 8.0 or Laravel 8, composer require will resolve you back to the 2.1.x line — check composer.lock rather than assuming you got 2.2.
composer require renoki-co/laravel-useful-casts
Where to look first: the README's cast list, then the corresponding class under src/. Each cast is a single self-contained class with a get() and a set() — reading one is the fastest way to understand what it will and won't do to your column. There is no service provider, no config file to publish, and no migration to run; Eloquent instantiates the cast classes directly.
First use case: pick one column where you currently have both an accessor and a mutator doing symmetric work (a date format, a serialized blob, a normalised value). Swap both for a single cast declaration, then confirm the round-trip — write a value, save, refresh(), and toArray() it. That last step catches most of the real problems (see Gotchas).
Declare casts on the model. The package's job is done at declaration time; usage is just Eloquent:
protected $casts = [
'published_at' => SomeCast::class,
'settings' => SomeCast::class.':optionA,optionB', // ctor args after the colon
];
Colon-separated arguments are passed to the cast's constructor in order, comma-delimited. That is the extension point for per-column configuration — prefer it over subclassing a cast, because a subclass is invisible in the model and easy to forget when reading the schema.
Treat casts as the single source of truth for a column's type. The most common failure mode in codebases that half-adopt this package is a cast plus an accessor on the same attribute — the accessor wins on read, the cast's set() wins on write, and the two drift. When you adopt a cast, delete the accessor and mutator it replaces in the same commit.
Compose with API resources and form requests, not instead of them. A cast normalises storage; a resource controls output shape. Keeping both means toArray()/toJson() stays predictable and you don't have to make your value objects JsonSerializable just to satisfy a serialisation bug (see Gotchas).
Verify with the raw side of the model in tests. assertDatabaseHas and DB::table() see the pre-cast, storage representation; $model->attribute sees the cast value. Write assertions against both so a silently broken set() (which stores the wrong thing but reads back fine in the same request) can't pass CI.
Migrate one column at a time. Because a cast changes both directions at once, a bad cast is a write-path bug on existing rows. Land them individually, with a refresh()-based test, rather than sprinkling ten casts across a model in one PR.
Version the constraint, not the lockfile. In a monorepo or on shared CI, express the floor explicitly ("renoki-co/laravel-useful-casts": "^2.2") so the Laravel 10 requirement is visible at review time rather than discovered in a failed deploy.
composer update, not at runtime — which is the good outcome. Verify your php platform config and CI images before bumping.casts() method rather than the $casts property — don't assume that path is covered by the package's own tests until a release says so.null will make a nullable column non-nullable from the model's perspective, and set() returning a non-null value for a null input will quietly write it back. Read each cast's set() before pointing it at a nullable column.set() must return an array. Return [$key => $value] to write, or [] to signal "no change". Returning a bare scalar is a confusing failure, not an error.toArray(). If a cast returns a plain object, $model->toArray() can throw or emit garbage. Either pick casts whose return types serialise cleanly, or implement Arrayable/JsonSerializable on your value object.Model::where(...), DB::table()->update(), insert(), upsert(), and chunked bulk operations all bypass set(). You must write the storage representation yourself in those calls — a very common source of "the cast works but the data is wrong".isDirty() report true on every save, causing spurious updated_at churn and noisy update listeners. Confirm your casts round-trip stably.getOriginal() is casted; getRawOriginal() is not. Reach for the raw variant when debugging storage.$model->getRawOriginal('column') before suspecting the cast. Nine times out of ten the cast is correct and the write path bypassed it.CastsAttributes implementation (using the package's classes as a reference) or by passing constructor arguments. If you find yourself wanting to subclass, a project-local cast class is usually the cleaner answer.How can I help you explore Laravel packages today?