What is a way to create a relationship on a Laravel model that uses an OR
condition for the field used to join the table while loading an eloquent model and that will work with eager loading and be able to leverage all of the existing features of a Laravel relationship?
I can use accessor attributes and other workarounds, but I cannot figure out how to do this and have it work EXACTLY like any other Laravel relationship.
user_connections
Name | Type |
---|---|
id | bigInt|primary|auto_increment |
from_user_id | bigInt |
to_user_id | bigInt |
created_at | timestamp? |
updated_at | timestamp? |
I have a Laravel Model named UserConnection
that uses the above table schema.
How do I define a relationship on that model that can utilize either the from_user_id
field or the to_user_id
field?
The goal is to have a relationship named userConnections
on a User model that can be tied to either field.
Example of Desired Syntax:
class User {
...
// THE RELATIONSHIP METHOD NAME TO USE (DOESN'T HAVE TO BE `hasMany`)
public function userConnections(){
return $this->hasMany(
\App\Models\UserConnection::class, /* THE TARGET CLASS TO BE LOADED */
'from_user_id||to_user_id', /* JOIN ON EITHER OF THESE FIELDS */
'id' /* THE ID OF THE USER IN THIS MODEL */
);
}
I can do this with a query and load the models manually. But I want to stick to the Laravel format for relationships so that other people on this project can use the relationship naturally.
I need to make it work with this schema.
Finally figured out how to do this natively in Laravel without using a package.
All that was needed was to just define the relationship for both from and to then union them and it worked like a charm.
This effectively works like an OR
statement since the relationships get merged using union
I hope this helps someone else.
namespace App\Models;
use App\Models\UserConnection;
class User {
//GET ALL OF THE FROM USER CONNECTIONS
public function fromUserConnections(){
return $this->hasMany(UserConnection::class, 'from_user_id');
}
//GET ALL OF THE TO USER CONNECTIONS
public function toUserConnections(){
return $this->hasMany(UserConnection::class, 'to_user_id');
}
//MERGE THE RELATIONSHIP QUERIES
public function userConnections(){
return $this->fromUserConnections()->union($this->toUserConnections());
}
}
Using this method I am able to successfully call:
//EAGER LOADING
$users = User::with('userConnections')->get();
//LAZY LOADING
$userConnections = $user->userConnections;
Although the original solution below does work, I try not to clutter my projects with single use composer packages.
As much as I was hoping to do this without adding a package it doesnt appear possible. I was finally able to accomplish this with a package that allows for a relationship method called hasManyMerged
https://github.com/staudenmeir/laravel-merged-relations
This method allows for matching on either key AND works when using ->with() for eager loading relationships.
Example:
namespace App\Models;
use Korridor\LaravelHasManyMerged\HasManyMergedRelation;
class User
{
use HasManyMergedRelation;
public function userConnections() {
return $this->hasManyMerged(\App\Models\UserConnection::class, ['from_user_id', 'to_user_id']);
}
}
Using this method I am able to successfully call:
$users = \App\Models\User::with('userConnections')->get();
Additionally I was able to create the inverse of the relationship by adding a custom trait and leveraging some of the code from the package. You can see how I did that here: https://github.com/korridor/laravel-has-many-merged/issues/5#issuecomment-2029147963
Hopefully This helps someone else out. Really seems like this should be part of Laravel natively without a package, but at least this was a simple solution that works exactly like any other Laravel relationship.