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

Laravel Hasmanywithinverse Laravel Package

stancl/laravel-hasmanywithinverse

Laravel's hasMany leaves the inverse relation unset, so accessing the parent from each child triggers extra queries. This package adds hasManyWithinverse, which hydrates the inverse automatically—so $child->parent is already loaded without N+1.

View on GitHub
Deep Wiki
Context7

Getting Started

archtechx/laravel-hasmanywithinverse adds a hasMany variant that also hydrates the child model's inverse relation in memory. Instead of:

$post = $user->posts()->create(['title' => 'Hello']);
$post->author;      // triggers a fresh SELECT

you get a Post whose author relation is already set to the $user instance — no extra query, and the two objects share identity (mutating $user->name after the fact is visible through $post->author).

Install

composer require archtechx/laravel-hasmanywithinverse

Version notes for this release (v1.4.2)

  • Laravel 9 and Laravel 10 are supported. Laravel 10 support was added in this release.
  • Laravel 8 support was dropped. If you are still on L8, pin ^1.4.1 and hold off on the upgrade until you move to L9+.
  • Requires PHP 8.0+ (inherited from the Laravel 9/10 floor).

Where to look first

  1. The README — the surface area is tiny, essentially one trait method.
  2. The relation class (ArchTech\HasManyWithInverse\HasManyWithInverse) — it extends Illuminate\Database\Eloquent\Relations\HasMany, so everything you know about hasMany still applies; the only difference is the constructor argument for the inverse name and the overridden create/save paths.
  3. Whatever trait ships the hasManyWithInverse() builder method on your model.

First use case

Replace the hasMany you reach for most often in write paths — typically a user/author producing content in a controller or action:

use ArchTech\HasManyWithInverse\HasManyWithInverse;

class User extends Model
{
    public function posts(): HasManyWithInverse
    {
        return $this->hasManyWithInverse(Post::class, 'author');
    }
}

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

The second argument is the name of the inverse relationship method on the child model, not the foreign key column. Get that name wrong and you will silently set a relation nobody reads.


Implementation Patterns

Controller / action writes

The highest-value use is the "create through the authenticated user, then render" flow:

public function store(StorePostRequest $request): PostResource
{
    $post = $request->user()->posts()->create($request->validated());

    // $post->author is already the authenticated user — no query,
    // and PostResource can embed 'author' without a lazy-load hit.
    return PostResource::make($post);
}

This pairs especially well with Model::preventLazyLoading() in AppServiceProvider, because a resource that references $post->author would otherwise blow up in non-production environments.

Seeding and factories

Factory has() / for() helpers route through saveMany/createMany on the relation, so the inverse is populated during seeding too:

User::factory()->hasPosts(3)->create();

If you have model observers or booted() hooks that read $post->author, this avoids the N+1 you would otherwise introduce in the seeder.

Chained writes

hasManyWithInverse composes normally with saveMany, createMany, save, make, and create — every child produced through the relation object gets its inverse set:

$user->posts()->createMany([
    ['title' => 'A'],
    ['title' => 'B'],
]);
// each Post has ->author loaded

Nested relations work, but each level needs its own hasManyWithInverse declaration — a plain hasMany in the middle of the chain stops the propagation there.

Testing

Assert on the relation being loaded, not just equal:

$post = $user->posts()->create(['title' => 'Hello']);

$this->assertTrue($post->relationLoaded('author'));
$this->assertTrue($user->is($post->author));
$this->assertSame(0, DB::connection()->getQueryLog() ? ... : 0);

The identity assertion is the point: it is the same PHP object, so it is a cheap and precise regression test if you later swap the relation back to a plain hasMany.

Mixing with eager loading

hasManyWithInverse solves the write side only. Listing endpoints still need with('author') / load('author') — do not let the package lull you into dropping eager loads on index queries.

Rollout strategy

Because the relation class is a HasMany subclass, declaration swaps are behaviourally additive. Migrate a couple of hot relations first, run your test suite with preventLazyLoading enabled in a non-production environment, and treat any new lazy-load exceptions as a signal you found a code path that was previously paying for a silent extra query.


Gotchas and Tips

Laravel 8 is gone. v1.4.2 raises the floor. Composer will happily resolve ^1.4.2 in a project that still has an L8 app in a monorepo sibling — check composer why-not if you see resolution failures on a shared lockfile. L8 users should pin 1.4.1.

firstOrCreate / updateOrCreate / firstOrNew bypass the relation's create(). These go through the query builder and save() on a fresh model instance rather than the relation's create(), so the inverse may not be set. If you depend on the in-memory inverse, route through create() explicitly, or re-check the relation class in your installed version before assuming coverage. Same caution applies to forceCreate.

Direct instantiation is invisible to the package. Post::create([...]) outside a relation object has nothing to infer an inverse from. The package only helps when the parent is known at call time.

The inverse name must match the child's relationship method exactly. A typo does not throw — setRelation() accepts any key. You get a model with a loaded relation nobody asks for, and the real inverse still lazy-loads. Add the assertion from the testing section to at least one test per migration.

refresh(), fresh(), and withoutRelations() discard it. All three rebuild or strip relation state. If a job or listener calls $post->refresh() before reading author, you are back to a query.

Queue serialisation does not carry it. Models are re-fetched by ID in the worker, so the inverse is not restored. Only rely on this within a single request/command execution.

Watch for chaperone() overlap. Recent Laravel versions ship a native chaperone() on HasMany that solves the same problem. If you are planning an upgrade past L10, audit these call sites — you may be able to delete the trait usage and drop the dependency entirely, which is usually preferable to keeping a third-party shim for a framework feature.

Extension point. If you need to handle more creation paths (firstOrCreate and friends), the cleanest hook is your own subclass of HasManyWithInverse overriding those methods and returning it from your trait's hasManyWithInverse() — do not patch the vendor directory, and do not try to bolt this onto a global model observer, which loses the parent context that makes the trick work.

Debugging checklist when the inverse is unexpectedly missing: (1) was the child created through the relation object, (2) does the inverse method name match the second argument, (3) did anything call refresh()/fresh() in between, (4) is the model crossing a queue boundary.

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.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core