Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Eloquent Laravel Package

petrknap/eloquent

A collection of enhancements and helper classes for Laravel Eloquent: UTC/local datetime casts, Optional wrappers that delegate missing-model handling, and repositories that provide a clean, expressive interface for persistence logic.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package via Composer:

composer require petrknap/eloquent

There is no service provider or configuration to publish — the package is a grab-bag of classes you reference directly from your models. Your first stop should be src/Casts/, because that's the only subpackage with a worked example in the README:

  • PetrKnap\Eloquent\Casts\AsUtc — a cast that normalizes DateTimeInterface values to UTC on write, and returns Carbon in UTC on read.
  • PetrKnap\Eloquent\Casts\AsPrivate — a cast class you reference by name for values you want handled as-private (likely opaque/hidden on serialization; verify against source).
  • src/Optionals/ and src/Repositories/ exist but are only listed in the table of contents — read the source before relying on them.

The canonical first use case is a timestamp problem: you want to persist UTC in the database but expose local wall-clock time to users without scattering Carbon::setTimezone() calls through your codebase. Start with a model like the README's:

use PetrKnap\Eloquent\Casts\AsUtc;

final class Event extends Model
{
    protected function casts(): array
    {
        return [
            'starts_at'              => AsUtc::dateTime(),
            'local_starts_at_utc'    => AsUtc::dateTime(readonly: true),
            'local_starts_at_tz'     => AsPrivate::class,
        ];
    }

    protected function localStartsAt(): Attribute
    {
        return AsUtc::withTimezone(
            'local_starts_at_utc',
            $this->getDateFormat(),
            'local_starts_at_tz',
        );
    }
}

Then verify with a round-trip: assign a timezone-aware Carbon, save(), refresh(), and print getTimezone()->getName() on both attributes. If both come back as UTC and Europe/Prague respectively, the package is behaving as advertised.

Implementation Patterns

Pairing cast + accessor for timezone-preserving columns. AsUtc::withTimezone() is a factory returning an Attribute accessor/mutator pair. It reads two physical columns (_utc and _tz) and synthesizes a single virtual attribute (localStartsAt). This is the pattern to copy whenever you want "one logical field, two storage columns" without a custom cast: store the canonically-normalized value plus its context (timezone, unit, currency code, locale) and reassemble in an accessor.

Use readonly: true for columns written only by the model. The AsUtc::dateTime(readonly: true) variant signals that the physical column is derived — you don't set it directly, you set the virtual attribute and let withTimezone compute it. Treat any cast you mark readonly the same way you'd treat a computed column in SQL: never assign it in seeders or tests directly.

Prefer model-level casts() over $casts property. The package's examples use the Laravel 10+ method form. If your app is still on the $casts property, AsUtc::dateTime() still returns a string class reference or cast instance compatible with the array; just be aware that the readonly: keyword argument requires the method form to stay readable.

Repositories. If src/Repositories/ does what the name suggests, it's a base class for query objects. The idiomatic integration is to inject the repository into a service/action class rather than reaching for Model::query() inside controllers. Since documentation is absent, inspect the constructor and abstract methods before adopting it — if it's a final class with a hard-coded Model type, it may be cheaper to write a thin where-builder yourself than to fight it.

Optionals. "Optionals" in Eloquent context usually means nullable-typed attributes that distinguish "unset" from "null." If src/Optionals/ provides a value object (e.g. Optional::of($x) / Optional::empty()), wire it as a cast on the column and unwrap it in your domain layer — never in Blade or JSON resources, where you'd leak the wrapper.

Multi-cast fields. Combining AsUtc::withTimezone with AsPrivate (as in the example) is fine — each physical column gets exactly one cast; the virtual attribute gets the accessor. Don't stack two casts on one column.

Gotchas and Tips

  • Treat this as an unvetted dependency. Zero stars, zero dependents, no documentation beyond three headings. Before production use, read composer.json for the PHP and Laravel version constraints, run composer why-not after install, and pin an exact version in composer.json rather than a caret constraint — there is no community pressure to keep BC.
  • The listed "last release" date (2026-10-28) is in the future relative to a normal review cycle. That usually means a typo in metadata, a back-dated tag, or a re-published package. Verify the actual tag on Packagist/GitHub and inspect the diff between it and HEAD before trusting it.
  • LGPL-3.0 is not MIT. LGPL is fine for linking from a proprietary app, but if you fork and redistribute the package itself you inherit copyleft obligations on that fork. Get sign-off from whoever owns your license compliance if this is going into a commercial product. If you only need the UTC-cast logic, a ~30-line custom CastsAttributes implementation is often cheaper than the license review.
  • withTimezone expects two columns to exist and to be typed correctly. The _utc column should be a datetime/timestamp; the _tz column must accept an IANA timezone identifier string (e.g. Europe/Prague) — a string(64) is the safe width; VARCHAR(32) will truncate America/Argentina/Buenos_Aires. If refresh() returns null for the accessor, check for null in either column before blaming the cast.
  • getDateFormat() is referenced in the accessor closure. If you override it in your model, the package will use your format string when concatenating — which is what you want — but a mismatch between your format and the _utc column's storage format will silently produce wrong datetimes rather than throwing. Add a feature test that asserts $model->localStartsAt->toIso8601String() for a fixed input.
  • Debugging casts: $model->getCasts(), $model->getAttributeValue('field'), and $model->getAttributes() (raw, post-cast? — post-cast for getAttributeValue, raw DB values for getAttributes after refresh) are your tools. If casting looks inverted, DB::listen(fn($q) => dump($q->bindings)) to confirm what actually hit the database.
  • Extension points. The example model is final, and the cast classes likely are too. If you need to tweak behavior (e.g. a different default timezone), don't subclass — wrap. Compose AsUtc::dateTime() with your own Attribute factory, or copy the ~class into app/Casts/ if the license situation is murky.
  • Serialization. AsPrivate presumably controls toArray()/toJson() output. If your API is returning a raw _tz column that you expected to be hidden, that's where to look. Conversely, if your localStartsAt virtual attribute is missing from JSON, add it to $appends.
  • Test with DST boundaries. The whole class of bugs this package exists to solve lives at DST transitions. A round-trip test on 2025-03-30 02:30 Europe/Prague (non-existent local time) and 2025-10-26 02:30 (ambiguous) will tell you quickly whether the cast is doing the right thing or just calling ->setTimezone('UTC').
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel