Get instance of subtype of a model with Eloquent

Stefan Bogdanescu

Founder & Senior Architect · 2026-06-29

Laravel Company

Achieving Polymorphic Model Retrieval in Eloquent: The Developer's Guide

As we build complex applications using Eloquent, one of the most common design challenges arises when dealing with inheritance or polymorphism. You have a base model (Animal) and several specific subclasses (Dog, Cat), linked by a discriminator field (like the type column). The goal is to fetch an object from the database and have it seamlessly resolve to the correct subclass instance, allowing you to call specific methods like $animal->bark() or $animal->meow().

This post dives deep into why standard Eloquent methods fall short for this scenario and presents a robust, performant solution that avoids unnecessary database hits.

The Challenge: Polymorphism in Eloquent

You have defined your models like this:

class Animal extends Model { /* ... */ }
class Dog extends Animal { /* ... */ }
class Cat extends Animal { /* ... */ }

And your database table looks like this:

id type name breed
1 dog Fido Labrador
2 cat Whiskers Siamese

When you execute $animal = Animal::find($id);, Eloquent correctly fetches the Animal record. However, $animal is strictly an instance of Animal. While you could check its type (instanceof Dog), it doesn't automatically grant access to the specialized methods defined in the subclass unless you perform manual casting or reflection, which often leads to performance bottlenecks if done improperly.

Attempting to use Dog::find($id) is tempting but incorrect because it locks you into fetching only the Dog record and ignores the flexibility of the base relationship. The core requirement is to retrieve a specific type of object based on runtime data stored in the database, all in a single, efficient query.

Solution: Dynamic Model Resolution via Scope or Query Builder

Since Eloquent's built-in relationships are designed for explicit one-to-one or many-to-many links, handling arbitrary runtime class resolution requires a slightly more manual, yet highly optimized, approach that leverages the power of the underlying database structure. We want to avoid the N+1 problem entirely.

The most efficient way to solve this without relying on complex, heavy polymorphism solutions (like table inheritance) is to use conditional querying based on the discriminator field. This allows us to fetch the exact model we need in a single SQL query.

Step 1: Implementing a Custom Finder Method

Instead of trying to force Eloquent into dynamic instantiation magic, we implement a custom method on the base model or a dedicated service that handles the lookup logic directly. This ensures performance is prioritized.

We can modify the Animal model to include a static method capable of finding any subtype based on the type field:

// app/Models/Animal.php

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;

class Animal extends Model
{
    // ... existing code

    /**
     * Find an Animal instance of a specific subtype.
     *
     * @param int $id
     * @param string $type The model class name (e.g., 'Dog', 'Cat')
     * @return \Illuminate\Database\Eloquent\Model|null
     */
    public static function findType(int $id, string $type)
    {
        // Use where clause to filter by both ID and the type discriminator
        $animal = self::where('id', $id)
                     ->where('type', $type) // Crucial filtering step
                     ->first();

        return $animal;
    }
}

Step 2: Usage Example

Now, when you need an animal, you specify both the ID and the desired type. This single query hits the database once, retrieving exactly what you asked for.

// Fetching a Dog instance efficiently
$dog = Animal::findType($id, 'Dog');

if ($dog) {
    // $dog is now an instance of Dog, ready to use specialized methods.
    echo $dog->bark(); 
} else {
    echo "Dog not found.";
}

// Fetching a Cat instance efficiently
$cat = Animal::findType($id, 'Cat');

if ($cat) {
    // $cat is now an instance of Cat.
    echo $cat->meow();
} else {
    echo "Cat not found.";
}

Conclusion: Practical Application and Best Practices

This approach provides a clean separation between the base model definition and the runtime fetching logic. By implementing findType(), we satisfy the requirement of dynamic object retrieval while adhering to Eloquent's focus on efficient database interaction. This pattern is highly effective for scenarios involving flexible data structures, which aligns perfectly with modern Laravel development principles, as promoted by resources like Laravel Company.

While more complex solutions exist (like using dedicated polymorphic relationship packages), this custom query method offers superior control and performance when dealing with simple discriminator fields across a shared table. Always favor explicit, optimized queries over runtime reflection when performance is critical.