kobykorman/eloquentify

Easily transform complex custom query results into fully hydrated hierarchical Eloquent models.
222 20
Install
composer require kobykorman/eloquentify
Latest Version:v2.1.0
PHP:^8.2
License:MIT
Last Updated:Jun 13, 2026
Links: GitHub  ·  Packagist
Maintainer: kobykorman

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)
  • morphTo relations 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 ->pivot models
  • Silent failures became exceptions

License

This library is open-sourced software licensed under the MIT license.