rackbeat/laravel-morph-where-has
Installation:
composer require rackbeat/laravel-morph-where-has
No additional configuration is required—the package auto-registers its service provider.
First Use Case:
Suppose you have a polymorphic morphTo relation (e.g., owner()) that can belong to multiple models (Customer, Vendor). To query records where the morph relation matches a condition (e.g., Customer with active = true), use:
Invoice::whereHasMorph('owner', [App\Customer::class], function ($query) {
$query->where('active', true);
})->get();
Replace owner with your morph relation name and [App\Customer::class] with the allowed morph classes.
Define Morph Classes:
Explicitly declare all possible morph classes in your model’s whereHasMorph calls. Example:
// In Invoice model
public function customer() {
return $this->morphTo('owner')->forClass(App\Customer::class);
}
Use this for type safety and clarity.
Querying with Constraints:
Invoice::whereHasMorph('owner', [App\Customer::class, App\Vendor::class])
->get();
Invoice::whereHasMorph('owner', [App\Customer::class], function ($query) {
$query->where('tier', 'premium')->where('balance', '>', 1000);
})->get();
Integration with Eloquent:
Invoice::whereHasMorph('owner', [App\Customer::class])
->where('status', 'paid')
->orderBy('created_at', 'desc')
->get();
Dynamic Morph Classes: Pass an array of classes dynamically (e.g., from a request):
$allowedClasses = request()->input('owner_types', [App\Customer::class]);
Invoice::whereHasMorph('owner', $allowedClasses)->get();
Missing Morph Classes:
SQLSTATE[42S22]: Column not found if a class in whereHasMorph isn’t registered in the morphTo relation.// Correct: Only include classes that can morph to 'owner'
Invoice::whereHasMorph('owner', [App\Customer::class]); // ✅
Invoice::whereHasMorph('owner', [App\InvalidClass::class]); // ❌
Performance:
with() or load():
Invoice::with(['owner' => function ($query) {
$query->whereHas('activeCustomers');
}])->whereHasMorph('owner', [App\Customer::class])->get();
Case Sensitivity:
whereHasMorph must match exactly (including namespace) with the morphTo relation’s forClass definitions.App\Models\Customer::class) to avoid ambiguity.Query Logs: Enable Laravel’s query logging to inspect generated SQL:
DB::enableQueryLog();
Invoice::whereHasMorph('owner', [App\Customer::class])->get();
dd(DB::getQueryLog());
Validation: Verify morph relations with:
$invoice = new Invoice();
dd($invoice->owner()->getMorphClass()); // Check expected class
Fallback:
If whereHasMorph fails, revert to raw queries or manual joins:
Invoice::join('customers as owner', function ($join) {
$join->on('invoices.owner_id', '=', 'owner.id')
->where('owner.ownerable_type', Invoice::class)
->where('owner.active', true);
})->get();
Custom Constraints: Extend the package by creating a macro for reusable conditions:
use Illuminate\Database\Eloquent\Builder;
Builder::macro('whereActiveOwner', function () {
return $this->whereHasMorph('owner', [App\Customer::class], fn ($q) => $q->where('active', true));
});
// Usage:
Invoice::whereActiveOwner()->get();
Dynamic Class Resolution:
Override the package’s MorphWhereHas class to add logic for resolving morph classes dynamically (e.g., from a config file).
Testing: Mock morph relations in tests:
$invoice = Invoice::factory()->create();
$invoice->owner()->associate(Customer::factory()->create(['active' => true]));
$invoice->save();
$this->assertCount(1, Invoice::whereHasMorph('owner', [App\Customer::class])->get());
How can I help you explore Laravel packages today?