'Laravel relationship with whereColumn condition
Appears I'm trying to do something uncommonly complex or going about this the wrong way. I can't seem to find anything online similar to what I am doing here. I am trying to query a relationship where a column in the related table is equal to a column in the table of the model.
Given the following tables
| entity
============
id
...
| foo
============
id
entity_id
type_id
...
| baz
============
id
entity_id
type_id
...
| type
============
id
...
and Models
class Entity extends Model
{
public function baz()
{
$this->hasMany( Baz::class );
}
public function foo()
{
$this->hasMany( Foo::class );
}
//...
}
class Foo extends Model
{
public function entity()
{
$this->belongsTo( Entity::class );
}
public function type()
{
$this->belongsTo( Type::class );
}
//...
}
class Baz extends Model {
public function entity()
{
$this->belongsTo( Entity::class );
}
public function type()
{
$this->belongsTo( Type::class );
}
//...
}
class Type extends Model
{
//...
}
I have the following code working, but in a different context Both Foo and Baz are filtered using $type_id
public function controllerActionWorking( Request $request, $type_id )
{
// I'm using this in another place to get rows of Foo with type_id == $type_id. Including Related Baz which also have type_id == $type_id
$results = Foo::with(
[
'entity.baz' =>
function( $query ) use ($type )
{
return $query->where( 'type_id', $type->id ))
}
])
->where( 'type_id', $type_id )
->get()
// ....
}
What I need is all Foo records, The Entity that goes with Foo, and Entities related Baz records where the Baz.type_id is equal to the type_id of the Foo record. this is non working code, but something like this is what I started trying first.
public function controllerActionHelpWanted( Request $request )
{
$results = Foo::with(
[
'entity.baz' =>
function( $query )
{
// Here I need to verify the entity.baz.type_id = foo.type_id
// the following does NOT work
// I get errros that 'foo'.'type_id' does not exist
return $query->whereColumn( 'foo.type_id', 'baz.type_id' )
})
])
->get()
// .....
}
I can use a join() something like this
$results = Foo::with( 'entity' )
->join(
'baz',
function($join)
{
return $join->on( 'foo.entity_id', 'baz.entity_id' )
->on( 'foo.type_id', 'baz.type_id' );
})
->get()
That returns an instance of Foo with combined attributes of both tables. It also looses the 'entity.baz' nested relationship. which will matter in downstream code.
I could $results->map() to filter the 'entity.baz' relationship after the query. I'd prefer to avoid the impact on performance if possible.
just seems like I'm missing something here. Any help/advice is appreciated.
Solution 1:[1]
Hard to read, when names are foo/baz and no logic reference is applied. Looks like many to many relation, where you can apply pivot.
public function products()
{
return $this->belongsToMany(Product::class)
->withPivot('quantity', 'price'); // joins data from intermediate table
}
Pivot tables are queried by: $obj->pivot->quantity;
Solution 2:[2]
As far you wrap your question into faz/baz/entity it becomes quiet abstract to me, still :)
Let's try to make your query more model-side:
class Foo extends Model
{
use HasFactory;
public function entity()
{
return $this->belongsTo(Entity::class, 'entity_id', 'id');
}
public function scopeTypes($query, $type_id)
{
return $query->where('type_id', $type_id);
}
public function scopeWithBazes($query, $type_id)
{
/* @var $query Builder */
return $query->join('bazs', function($type_id) {
return self::where('type_id', $type_id);
});
}
}
Inside controller I can do:
class TestController extends Controller
{
public function index(Request $request)
{
$foos = Foo::query()->withBazes(1)->get();
foreach($foos as $foo)
dd($foo->baz_name); // i placed baz_name in Baz table for test
}
}
Well, drawback is that the table is joined. It is joined because you don't have any intermediate tables, so you need use joins etc.
Foo::types($type) will return collection of foos with type given. Foo::bazTypes($type) will return joined table.
Still, these kind of problems appear when you omit intermediate tables, queries become more complex but database is a bit less robust.
If you were able to make that naming abstraction into 'real' problem, maybe people would play with it more. Just make foo => invoice, baz => address, etc, so perhaps you'll find better database schema for your scenario. Sometimes you will end up with multiple queries no matter what, but it's always worth asking :)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Elboyler |
| Solution 2 | Elboyler |
