Has Many Through

Has One Through and Has Many Through

A through relationship crosses an intermediate table in one JOIN. Category::reviews() is hasManyThrough(Review::class, Product::class) with every key guessed, and $this->through( 'products')->has('reviews') builds the same from existing relationships. Six arguments cover the rest, such as an order's address, reached through the customer the order belongs to:

app/Models/Order.php: has-one-through across a belongsToPHP
public function shippingAddress(): HasOneThrough
{
    return $this->hasOneThrough(Address::class, Customer::class,
        'id',          // customers column that orders.customer_id matches
        'customer_id', // addresses column that points at customers
        'customer_id', // orders column holding the customer
        'id');         // customers column that addresses point at
}
Two through relationships and the SQL they sendSQL
use App\Models\{Category, Order};
echo Category::find(3)->reviews->pluck('rating')->join(', '), "\n";
echo Order::find(4)->shippingAddress->city, "\n";
Output
-- select * from `categories` where `categories`.`id` = 3 limit 1
-- select `reviews`.*, `products`.`category_id` as `laravel_through_key` from `reviews` inner
    join `products` on `products`.`id` = `reviews`.`product_id` where `products`.`category_id`
    = 3
4, 3
...
Sao Paulo

laravel_through_key carries the parent key back through the join so eager loading can match rows. For chains longer than one hop, eloquent-has-many-deep 2,873 (https://github.com/staudenmeir/eloquent-has-many-deep 2,873 ) (1.22 for Laravel 13 2,157 ) extends the idea to any depth.