Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

kokoshneta's avatar

How do you achieve a ‘hasManyMorphsThrough’ relationship?

I’m setting up a web-shop system. There are Orders, each of which has one or more OrderItems, each of which has one associated Product. The product relationship is polymorphic, since some different product types are stored in separate tables; let’s say as an example there are Books and Movies (both classes extending a base Product class). So the structure is:

orders
- id
- other_stuff

order_items
- id
- order_id
- product_id
- product_type
- other_stuff

books
- id
- title
- other_stuff

movies
- id
- title
- other_stuff

The models all have the appropriate relationships to retrieve directly related models:

// Product.php
public function orderItems() {
	return $this->hasMany(OrderItem::class, 'product_id');
}
// OrderItem.php
public function product() {
	return $this->morphTo();
}

public function order() {
	return $this->belongsTo(Order::class);
}
// Order.php
public function items() {
	return $this->hasMany(OrderItem::class);
}

public function products() {
	// Here’s the issue
	return $this->hasManyThroughAMorphedRelationship();
}

The last method there shows the issue. I would like to be able to list products directly from an order, but I cannot figure out how. As far as my foggy brain can work out, all the variations of hasXThrough() require the class of the target model as a parameter, but that’s exactly the model that’s polymorphic, so there isn’t a target class, or a target database table – there are several.

Further compounding the issue is that the relationship needs to be used in a Filament table column. I can get the product IDs listed in a table column very easily like this:

// OrderResource.php
public static function table(Table $table) : Table {
	return $table->columns([
		TextColumn::make('items.product_id')->label('Products')
	]);
}

But product IDs are useless for the user. Product names are what’s needed, but until Filament 3 comes out, nested relationships are not possible in table columns, so it needs to be a relationship on the Order model.

Is this possible at all?

0 likes
1 reply
kokoshneta's avatar
kokoshneta
OP
Best Answer
Level 27

Haven’t managed to figure out a way to actually create a relationship of the type I was hoping for, but I did at least manage a workaround specifically for Filament admin panels, using getStateUsing(). I had tried this earlier, based on the example in the docs:

Tables\Columns\TextColumn::make('amount_including_vat')
    ->getStateUsing(function (Model $record): float {
        return $record->amount * (1 + $record->vat_rate);
    })

– but I couldn’t get it to work. I kept getting obscure “Argument #1 ($haystack) must be a string” errors and couldn’t figure out what was causing them. Eventually I stumbled on the closure customisation section in the Filament docs and noticed this phrasing:

If you have defined a form or component Eloquent model instance, define a $record parameter

Turns out the reason it hadn’t worked was that I had named my parameter semantically, as Order $order instead of Model $record. Once I fixed that, it worked:

TextColumn::make('products')
	->getStateUsing(function (Model $record) : string {
		return $record->items->implode(fn ($i) => $i->product->name(), '<br />');
	})
	->html()
	->label('Products')

Please or to participate in this conversation.