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

Has Source Model Laravel Package

brokeyourbike/has-source-model

A lightweight Laravel trait that gives any Eloquent model a polymorphic "source" relationship, letting you track and retrieve the record or model a row originated from with minimal setup.

View on GitHub
Deep Wiki
Context7

Getting Started

This package gives an Eloquent model a polymorphic "source" relation — the record it was derived from (an Invoice sourced from an Order, a Refund sourced from a Payment). Day-to-day it is a thin trait plus a couple of columns, so onboarding is quick.

  1. Install and pin deliberately — v3.0.0 is a major with an undocumented breaking change, so start on ^2.x unless you can read the diff:
    composer require brokeyourbike/has-source-model-laravel
    
  2. Publish and run any shipped migration (the trait expects source_id / source_type morph columns; add them yourself with $table->morphs('source') if the package doesn't):
    php artisan vendor:publish --tag="has-source-model-migrations"
    php artisan migrate
    
  3. Add the trait to the models that should know their origin:
    use BrokeYourBike\HasSourceModel\HasSourceModel;
    
    class Invoice extends Model
    {
        use HasSourceModel;
    }
    
  4. First use case — record provenance at creation time instead of passing IDs around:
    $invoice = Invoice::create([...]);
    $invoice->source()->associate($order)->save();
    
    $invoice->source;        // Order instance
    $invoice->sourceModel(); // same, via the trait helper (verify name in the source)
    

Where to look first: the trait itself (src/HasSourceModel.php) and its migration stub. The surface area is small, and the docblock/README will tell you the exact relation and helper names — the v3 release notes do not.

Implementation Patterns

Treat source as write-once, read-often. Associate it in the same transaction as the model's creation, then never mutate it. This keeps provenance trustworthy and avoids needing to reason about re-pointing a record at a different origin.

DB::transaction(function () use ($order) {
    $invoice = Invoice::create([...]);
    $invoice->source()->associate($order)->save();
});

Prefer associate() over mass assignment. Accepting source_type / source_id from request input lets a client dictate the morph class — a classic injection vector. Resolve the source model server-side and associate it explicitly.

Enforce a morph map before you have data. Polymorphic columns store FQCNs by default, which bloats rows and couples your schema to namespaces:

// AppServiceProvider::boot()
Relation::enforceMorphMap([
    'order'   => \App\Models\Order::class,
    'payment' => \App\Models\Payment::class,
]);

Do this before the first production write, or plan a backfill.

Eager load, always. source is a morphTo, so looping over a collection without with('source') produces one query per distinct type per row:

Invoice::with('source')->whereDate('created_at', today())->get();

For heterogeneous sources, constrain per type so you don't hydrate more than you need:

Invoice::with(['source' => function (MorphTo $morphTo) {
    $morphTo->morphWith([
        Order::class   => ['customer'],
        Payment::class => ['gateway'],
    ]);
}])->get();

Walk the provenance chain. Derived-from relations compose naturally into an audit trail. Wrap the traversal so callers don't reimplement the loop:

// app/Models/Concerns/... or a small service
function lineage(Model $model): array
{
    $chain = [$model];
    while ($model->source) {
        $chain[] = $model = $model->source;
    }
    return $chain; // refund -> payment -> order
}

Filter by source in list/report queries. If v3's "reference" feature ships a scope (e.g. whereSource()), use it rather than hand-writing where('source_type', ...)->where('source_id', ...). Verify the generated SQL once — morph scopes are easy to get subtly wrong when a morph map is active.

Factories and tests. ->for($order, 'source') works out of the box for a morph relation and is the cleanest way to build fixtures; write one test asserting both that the relation resolves and that the stored source_type is your morph alias, not the FQCN. That single test catches the most common regression in this area.

Use it as an integration seam. Provenance is genuinely useful in events and listeners: on InvoicePaid, resolve $invoice->source and dispatch downstream work to the originating aggregate rather than duplicating foreign keys on every table.

Gotchas and Tips

  • v3.0.0's breaking change is undocumented. The notes say only "braking", with a feature titled "reference" and a fix also titled "braking". Do not upgrade blind. Read commits cb25541, 25e561f, and 4580864, diff the trait and any migration stub against your installed 2.x, and check whether stored source_type values or the relation/helper names changed. Treat it as a schema-affecting release until proven otherwise.
  • A morph-type change is a data migration, not just a code change. If v3 normalized types or moved to aliases, existing rows keep the old values and silently return null from source. Backfill in a transaction and verify with a count query before and after.
  • source may already be taken. Check for an existing source column, accessor, or relation on the model — Laravel will not warn you, and the trait will lose to a local attribute. Rename via the package's config if it exposes one.
  • Nullable source means nullable everywhere. Older records, imports, and manually created rows will have no source. Guard $model->source?->... and decide explicitly whether source is required for new records (a creating hook or a validation rule beats hoping).
  • Orphaned sources. There is no cascade for morphTo — deleting an Order leaves invoices pointing at a dead ID. Either forbid deleting sourced parents, or accept nulls and handle them in the UI ($invoice->source()->exists() before display).
  • Debugging a null relation. In order: confirm source_id is non-null, confirm source_type matches a morph-map alias (not the FQCN or the reverse), then confirm the class still exists. Relation::getMorphedModel($type) in tinker resolves this in seconds.
  • Extension points. The useful ones are the morph map, the package config (column names, if published), and overriding the trait's helper methods in your model. If you find yourself fighting the trait for anything beyond naming, a plain morphTo plus your own scope is usually cheaper than patching around it.
  • Keep the constraint loose-but-pinned. "brokeyourbike/has-source-model-laravel": "^3.0" with a composer.lock committed; this package moves in majors without detailed notes, so let CI — not a fresh composer update — decide when you cross a version 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.
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
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk