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 When On Relation Laravel Package

mashirou1234/laravel-when-on-relation

Adds a whenOnRelation helper to Laravel Eloquent so you can run callbacks or apply constraints only when a relationship is loaded or present. It keeps relation-aware models, queries, and API resources concise and free of repetitive checks.

View on GitHub
Deep Wiki
Context7

Getting Started

This package adds a single opt-in macro, whenOnRelation(), to Eloquent relationship instances. Laravel's built-in when() (from the Conditionable trait) fires whenever the condition is truthy, regardless of whether you are chaining off a model or a relation. whenOnRelation() narrows that: the callback only executes when the receiver is a relation instance, which is exactly the situation where relation-specific constraints (nested eager loads, wherePivot, withCount tweaks) actually make sense.

Minimum steps:

  1. composer require the package and confirm it is auto-discovered (php artisan about or check vendor/composer/installed.json if the provider is not listed).
  2. Open the package's src/ directory and read the macro registration first — that single file tells you the exact receiver type (Relation vs. BelongsTo/HasMany specifically) and the closure signature. Everything else in the assessment depends on that detail.
  3. Verify registration in php artisan tinker:
>>> User::first()->posts()::class
=> "Illuminate\Database\Eloquent\Relations\HasMany"
>>> method_exists(User::first()->posts(), 'whenOnRelation')
=> true

Your first real use case is almost always a controller or scope that is reused by both a top-level query and a relation query. For example, a PostRepository that applies ->with('author') when called directly, but must not re-apply it when the caller already constrained the relation:

$user->posts()
    ->whenOnRelation(fn ($query) => $query->withCount('comments'))
    ->get();

Start here rather than retrofitting an existing global scope — you get the behavior change in one call site and can diff the SQL before widening adoption.

Implementation Patterns

Guard shared scopes. The highest-value pattern is a local scope that must behave differently depending on how it was reached. Keep the top-level path untouched and wrap the relation-only branch:

public function scopeForListing(Builder $query): Builder
{
    return $query->whenOnRelation(
        fn ($q) => $q->withCount('comments')->orderByDesc('comments_count')
    );
}

Nested resource controllers. In UserPostController@index the builder is $user->posts(), while PostController@index gets Post::query(). One form request / one filter class can serve both without an if ($builder instanceof Relation) branch.

Pivot-aware constraints. Pivot operations only exist on relation instances, so this is a natural fit — the callback is where wherePivot, withPivotValue, and using() belong. It removes the "am I definitely on a relation here?" doubt that makes pivot code fragile.

Compose with when() rather than replacing it. These are orthogonal: when() gates on your domain condition, whenOnRelation() gates on the call context. Chain them in that order for readable intent:

$query->when($request->boolean('with_stats'), fn ($q) => $q
    ->whenOnRelation(fn ($r) => $r->withCount('comments')));

Testing. Because the callback silently does nothing when the receiver is not a relation, assert both branches explicitly — one test hitting User::find(1)->posts(), one hitting Post::query(). Use DB::listen() or ->toSql() snapshots; a passing test that asserts only the result set will not catch a callback that never fired.

Gotchas and Tips

  • Silent no-ops are the main risk. The whole point of the macro is to skip the callback, which means a misplaced call produces correct-looking output with a missing eager load. Assert on $query->getEagerLoads() or the emitted SQL in tests rather than on hydrated models.
  • whenOnRelation() is not a substitute for when(). If your condition is a request flag or a config value, use when(). Reaching for the relation helper to express business rules will make call sites hard to reason about.
  • Check what counts as a "relation instance." Confirm whether the macro is registered on the base Relation class or only on concrete relation types — a MorphTo or BelongsToMany may or may not be covered, and the failure mode is a BadMethodCallException rather than a quiet skip. Verify against every relation type you actually use before rolling it out.
  • Existing when() behavior is unchanged. The release notes are explicit about this, so no upgrade work is required for current code. Do not expect a codebase-wide behavior shift after composer update.
  • Macro registration order matters. If your app (or another package) registers a whenOnRelation macro in a service provider, last registration wins. If behavior looks wrong after adding another dependency, dump Relation::getMacro('whenOnRelation') or check provider ordering in bootstrap/providers.php.
  • Return the builder from the closure. Eloquent's conditionable helpers pass the relation in and use the closure's return value; a closure that mutates and returns nothing is a common source of "my constraint didn't apply." Match whatever the package's own docblock specifies rather than copying when() idioms blindly.
  • Debugging tip: ->dd() and ->dump() terminate or print at the point of call, so place them inside the callback to confirm it fired at all. If nothing prints, the receiver was not a relation — trace back one call in the chain to find where the relation was replaced by a plain Builder (e.g. after ->getQuery() or ->toBase()).
  • Extension point: if you need the same "only when on a relation" guard for non-Eloquent builders, do not overload this macro — write a small dedicated macro on your own builder class so the semantics stay honest.
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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
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