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

Orm Laravel Package

analogue/orm

Analogue is a flexible data-mapper ORM for PHP and Laravel. It separates domain models from mappers and repositories, offering fluent query building, eager-loaded relations, collections and clean persistence without the active-record constraints of Eloquent.

View on GitHub
Deep Wiki
Context7

Getting Started

Install

composer require analogue/orm

Laravel auto-discovery registers Analogue\ORM\AnalogueServiceProvider and the Analogue facade. On older Laravel versions, add both to config/app.php manually. Publish config:

php artisan vendor:publish --provider="Analogue\ORM\AnalogueServiceProvider"

config/analogue.php is where the DBAL connection name, custom mapper bindings, and cache settings live. Read it before anything else — it is the main integration surface.

First file to open — the config, then the README's Entity/Mapper/Relationships sections. The mental model to adopt immediately: an Entity is a plain-ish PHP object, a Mapper is the table gateway, and store() is an upsert. There is no ActiveRecord and no $model->save().

Minimal end-to-end

use Analogue\ORM\Entity;

class User extends Entity
{
    protected $attributes = ['id', 'name', 'email'];
    protected $casts = ['id' => 'integer'];
}
$mapper = Analogue::mapper(User::class);

$user = new User;
$user->name = 'Ada';
$user->email = '[email protected]';
$mapper->store($user);        // INSERT — $user->id populated from the identity map

$same = $mapper->find($user->id);

That is the whole loop: construct/assign, store(), find()/get(). First real use case: replacing an Eloquent model with a domain object that does not extend a framework base class, wrapped in a repository.

Implementation Patterns

Repositories around mappers. The natural Analogue workflow is one repository per aggregate, injecting the manager (or resolving Analogue) and exposing intent-revealing methods:

class UserRepository
{
    public function find(int $id): ?User
    {
        return Analogue::mapper(User::class)->find($id);
    }

    public function active(): Collection
    {
        return Analogue::mapper(User::class)
            ->where('active', true)
            ->orderBy('created_at', 'desc')
            ->get();
    }

    public function save(User $user): User
    {
        Analogue::mapper(User::class)->store($user);
        return $user;
    }
}

Relationships as methods on the entity. Declare the graph once, then use it from the mapper:

class Post extends Entity
{
    protected $attributes = ['id', 'user_id', 'title'];

    public function author()
    {
        return $this->belongsTo(User::class, 'user_id');
    }

    public function comments()
    {
        return $this->hasMany(Comment::class);
    }
}

Accessing $post->author returns a lazy Proxy; the parent row is loaded on first property access.

Eager loading and nested graphs. This is how you keep query counts sane:

Analogue::mapper(Post::class)
    ->with('author', 'comments.author')
    ->where('status', 'published')
    ->get();

Storing graphs. Assign the relation and let store() cascade:

$user->posts = [$draft, $published];
$mapper->store($user);          // writes user, then the related posts

Store children individually if you need finer control over write ordering.

Query building is fluent, execution is lazy. Everything returns a query builder until you call get(), first(), find(), or all(). Reuse a scoped builder across repository methods rather than rebuilding it.

Transactions. Wrap multi-entity writes:

Analogue::transaction(function () use ($mapper, $user, $posts) {
    $mapper->store($user);
    foreach ($posts as $post) {
        $mapper->store($post);
    }
});

Custom mappers as extension points. Bind a mapper subclass per entity in config/analogue.php ('mappers' => [User::class => UserMapper::class]) to add default scopes, soft-delete filtering, or a shared query API. This is the Analogue equivalent of Eloquent's local scopes.

Lifecycle events. Analogue fires creating, created, updating, updated, saving, saved, deleting, deleted. Use saving to stamp created_at/updated_at — they are not automatic.

Gotchas and Tips

Not a unit of work. Despite the Data Mapper framing, store() writes immediately; there is no flush() batching changes for you. Analogue::transaction() is your safety net, not hydration tracking.

Identity map surprises. Within a request, $mapper->find(1) returns the same instance every time. If you update a row via raw DBAL, the in-memory entity stays stale — detach it from the manager or fetch it through a fresh resolution path. Likewise, two find() calls returning "different" objects will actually be the same reference, which breaks naive assertNotSame tests.

Lazy proxies cause N+1. Any loop touching ->relation without with() is a query per row. Check with() on every collection query in code review; treat a bare relation access inside foreach as a smell.

List every column in $attributes. It defines what the entity will populate and mass-assign. Omit a column and you get silent nulls, not exceptions. Casts (integer, boolean, datetime, json/array) go in $casts; without a cast, DBAL hands you raw strings.

No timestamps, no soft deletes. Both are conventions you implement yourself (events or custom mapper). Don't assume Eloquent parity — it is the most common source of "why isn't updated_at changing".

Collections are not arrays. get() returns Analogue\ORM\System\Collection; use toArray() or iterate. count($collection) and empty($collection) behave differently than on an array in some cases.

Version pinning is the real blocker. Last release was April 2020 and it targets doctrine/dbal 2.x, which conflicts with Laravel 9+ (DBAL 3). Practically: PHP 7.4–8.0 and Laravel 5–8. If you're on a modern stack, budget for a fork or vendor patch before designing anything around it. With zero dependents and no releases in years, treat it as a legacy or educational choice, not a foundation for new build.

Schema tooling from metadata is a bootstrap aid, not a migration system. Analogue can derive tables from entity definitions, but it won't handle index tuning, data migrations, or safe rollbacks. Own your migrations in Laravel.

Debugging. Inspect what a mapper will run with $mapper->getQuery() before executing, and enable DBAL SQL logging on the underlying connection. Because relationships resolve lazily through proxies, stack traces point at the proxy, not at your foreach — when a query looks unexplained, grep for the relation name rather than the property.

Config cache. php artisan config:cache freezes analogue.connection; changing the DB name in .env without clearing config produces confusing "table not found" errors against the wrong database.

Extension points to remember: custom mapper classes (behaviour, scopes), a custom Entity base class (shared casts/events), and manager bindings in the container (swap in a fake manager or a test double for repository tests). Entities being plain objects means domain tests need no database at all — keep that separation, it's the main payoff of the package.

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.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource