kobykorman/eloquentify
| Install | |
|---|---|
composer require kobykorman/eloquentify |
|
| Latest Version: | v2.1.0 |
| PHP: | ^8.2 |
| License: | MIT |
| Last Updated: | Jun 13, 2026 |
| Links: | GitHub · Packagist |
Eloquentify for Laravel
👎 Lazy Loading (N+1 queries)
😑 Eager Loading (R+1 queries)
😎 Greedy Loading (1 query)
Why Eloquentify?
⚡ Single Query: N+1/R+1 round trips to the DB become one
💯 Eloquent API: custom SQL in, fully hydrated Eloquent models out
✨ Zero Config: your existing relation methods are all the wiring
Using Eloquent can be costly in terms of how many queries are fired behind the scenes when a model has many relationships. What if we could leverage the database for what it was meant for while retaining the Eloquent experience?
Eloquentify easily transforms the result of a single custom query into nested Eloquent models, so you can continue enjoying the Eloquent API and all of its benefits. And since every one of those replaced queries was a sequential round trip to your database, collapsing them into one is a difference you can see.
Installation
composer require kobykorman/eloquentify
Quick Start
1. Add the trait:
use Illuminate\Database\Eloquent\Model as BaseModel;
use KobyKorman\Eloquentify\EloquentifiesQueries;
class Model extends BaseModel
{
use EloquentifiesQueries;
}
2. Write one query, aggregating each relation into a JSON column named after its relation method:
// PostgreSQL
$result = DB::select("
SELECT users.id, users.name, users.email,
(SELECT json_build_object('id', profiles.id, 'bio', profiles.bio)
FROM profiles WHERE profiles.user_id = users.id) AS profile,
(SELECT json_agg(json_build_object(
'id', roles.id, 'name', roles.name,
'permissions', (SELECT json_agg(json_build_object('id', permissions.id, 'name', permissions.name))
FROM permissions
JOIN permission_role ON permission_role.permission_id = permissions.id
WHERE permission_role.role_id = roles.id)
))
FROM roles
JOIN role_user ON role_user.role_id = roles.id
WHERE role_user.user_id = users.id) AS roles
FROM users
WHERE users.id = ?
", [$id]);
3. Hydrate:
$user = User::eloquentify($result)->first();
4. Enjoy real Eloquent models:
$user->profile->bio; // hydrated HasOne
$user->roles->first()->permissions; // nested collections, any depth
$user->roles->first()->name = 'Admin';
$user->roles->first()->save(); // they are real models
How it works
For each column in your result, Eloquentify checks whether the model has a relation method with the same name (it must declare a relation return type, e.g. : HasMany). If it does, the column is decoded as JSON and hydrated recursively: a JSON object for to-one relations, a JSON array for to-many. Every other column becomes a plain attribute. That's the whole contract: column alias = relation method name.
Mistakes fail fast: invalid JSON, shape mismatches (an array where a to-one belongs), and ambiguous columns throw clear exceptions instead of guessing. Empty relations behave as you'd expect: NULL hydrates a to-one relation as null and a to-many as an empty collection, including the [null] arrays that aggregating over a left join produces.
Pivot data (BelongsToMany)
Include a pivot key (or your custom ->as() accessor name) inside each related object and it hydrates into the real pivot model, custom pivot classes included:
json_build_object('id', tags.id, 'name', tags.name,
'pivot', json_build_object('created_at', post_tag.created_at))
$post->tags->first()->pivot->created_at; // works exactly like native eager loading
JSON functions per database
| to-many (JSON array) | to-one (JSON object) | |
|---|---|---|
| PostgreSQL 9.4+ | json_agg(...) |
json_build_object(...) |
| MySQL 5.7.22+ | JSON_ARRAYAGG(...) |
JSON_OBJECT(...) |
| SQLite 3.38+ (or 3.9+ compiled with JSON1) | json_group_array(...) |
json_object(...) |
Requirements
- PHP 8.2+, Laravel 11+
- A database with JSON aggregation: PostgreSQL 9.4+, MySQL 5.7.22+, or SQLite 3.38+ (see table above)
- Relation methods must declare relation return types (e.g.
: HasMany), standard modern Laravel style - One result row per root model, which is what JSON aggregation naturally produces
Limitations
- Relation method names are reserved: a scalar column aliased like a relation (
AS posts) throws, so alias it differently (AS posts_count) morphTorelations can't be hydrated (the related class varies per row)- Relation classes from third-party packages (e.g. belongs-to-through) are assumed to-many unless they extend Eloquent's to-one relations; a mismatch throws rather than misclassifies silently
- Binary columns can't ride JSON: encode them (e.g. base64) and cast back
- For high-precision decimals, emit text (
amount::text) to avoid float precision loss in JSON - Database JSON encoders reformat temporal values (PostgreSQL: ISO 8601
2026-06-12T10:00:00; MySQL: microseconds appended). Date casts parse these identically to native loading; only an uncast date column read as a raw string sees the format difference
New in v2 (for v1 users)
v2 replaced v1's column prefixes with JSON aggregation (json_agg & friends). In one move:
- No row multiplication: sibling "many" relations of any size are fine
- No prefixes: no collisions, no aliasing rules, no naming conventions
- Use any method name for the relation: alias the column after the method, including two relations to the same model and self-references
- Pivot data hydrates into real
->pivotmodels - Silent failures became exceptions
License
This library is open-sourced software licensed under the MIT license.