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

daniel.schreij's avatar

Creating dynamic relations between two models.

I am struggling with a scenario in which I can have multiple relations between the same two models, and I would like to dynamically construct these relationships depending on the contents of another table.

I have a User model and a DiverRole model which are connected through a belongsToMany relationship, which of course means that a user can have one or more diver roles (and vice versa). The diver_roles table simply contains the possible diver roles that can be assigned to a user (e.g. student, photographer, dive leader, etc.).

The User model is also connected to a Dive model through a belongsToMany relationship using a pivot table named dive_user. This pivot table contains an extra field diver_role_id which specifies the specific diver role that the relationship represents.

I would now like to dynamically specify relationships with User in the Dive model, depending on the contents of the diver_roles table. Given that my diver_roles table has two entries:

id: name
-----------
1: Student
2: Photographer

I could manually specify these like:

// App\Dive

...

public function students(){
       return $this->belongsToMany(User::class)->wherePivot('diver_role_id', 1);
}

public function photographers(){
       return $this->belongsToMany(User::class)->wherePivot('diver_role_id', 2);
}

...

but this way these relationships are hardcoded, while I would like them to depend on the contents of the DiverRole table. I was hoping there is a way I could do something like:

// App\Dive

/** Call in constructor of Model **/
function establishRelationships(){
    // Get the diver roles
    $diverRoles = DiverRole::pluck('name' ,'id');

    foreach($diverRoles as $id => $name){
        // Generate the relationship name by pluralizing the entry and make the first
        // letter lowercase (Student --> students)
        $relationship_name = lcfirst(str_plural($name));
        // Add the relationship to the model
        $this->$relationship_name = function() use($id, $name){
            return $this->belongsToMany(User::class)->wherePivot('diver_role_id', $id);
        }
    }
}

but this just adds the closures directly to the Model's attributes (or at least, it doesn't work as intended).

I have also tried to use the object's __call() method, but since Laravel itself heavily relies on this, I did more harm than good by doing down that road.

Does anybody have any good leads on how to approach this problem? Thanks!

0 likes
2 replies
jbloomstrom's avatar

Why store the diver roles in the db? Do you expect to have more than just a few diver roles? If so, I would probably reach for polymorphism and make each type of role its own class.


class Diver {

  public function role() {
    return $this->morphsMany(Diver::class,'diver');
 }
}

class Student {

  public function diver() {
    return $this->morphTo();
  }
}

class Photographer {

  public function diver() {
    return $this->morphTo();
  }
}

class OtherRole {

  public function diver() {
    return $this->morphTo();
  }
}

Otherwise, if you only have a few roles and you don't want to use polymorphism, consider using a helper class to store information about the roles. The roles don't really need to be stored in the db, since you would have to add additional relationships to App\Dive if more roles were added to the db.

// App\DiverRole

class DiverRole {
  const STUDENT = 1;
  const PHOTOGRAPHER = 2;
  const INSTRUCTOR = 3;
}

// App\Dive

...

public function students(){
  return $this->belongsToMany(User::class)
    ->wherePivot('diver_role_id', DiverRole::STUDENT);
}

public function photographers(){
  return $this->belongsToMany(User::class)
    ->wherePivot('diver_role_id', DiverRole::PHOTOGRAPHER);
}

public function instructors(){
  return $this->belongsToMany(User::class)
    ->wherePivot('diver_role_id', DiverRole::INSTRUCTOR);
}

...

Hope this helps.

1 like
daniel.schreij's avatar

Thanks for the suggestion! I had been looking into polymorphism, but found it still too restrictive (as in, you need to create a separate class for each entity/ diver role).

The idea is indeed that the administrator of the system can add new roles when necessary (or change the name of old ones), and as far as I know, the best way to handle this is by using a separate reference table.

I'm not really a fan of hard-coding lists that are intended to be dynamic (for the same reasons I don't use ENUM fields, but use reference tables ), so I hope it is possible to somehow specify relationships dynamically in Eloquent's models. I have already got all other parts covered of what I'm trying to achieve on the view and edit sides (I already compose a list of possible diver roles and use it to traverse the relations that I have currently hard-coded in the model); this problem is the only bump in the road I'm still facing.

I worked with CakePHP before this, and I used to solve problems like these by using bindModel() (which has been deprecated in CakePHP3 in favor of defining relationships in the model's initialize() function). I can't imagine Laravel isn't capable of the same.

Please or to participate in this conversation.