Search code examples
laravelmodelforeign-keysrelationshiphas-many

Laravel relationship : one foreignKey for several localKey relationship


I have to develop an api with Laravel and i'm stuck with this problem: I need to get all matchs and for each match i need to get the users who belongs to this match (there is 2 users per match)

I've search answer for this problem and i found this https://github.com/topclaudy/compoships but it didn't worked for me.

So far, i have this :

class Match extends Model
{
    protected $table = 'matchs';

    public $primaryKey = 'id';

    public function playerOne() {
    return $this->hasMany('App\User', 'id', 'playerOne_id');
    }

    public function playerTwo() {
        return $this->hasMany('App\User', 'id', 'playerTwo_id');
    }
}

Controller Method:

public function index()
{
    $matchs = Match::with('playerOne', 'playerTwo')->orderBy('created_at', 'asc')->get();

    return $matchs; 
    //return new MatchsCollection($matchs);
}

and my database look like this : (Matchs table)

id    | playerOne_id   | playertwo_id | winner_id | status 
------------------------------------------------------------
1     | 1              | 3            | 0         | PENDING
2     | 2              | 1            | 0         | PENDING
3     | 3              | 2            | 0         | PENDING

and users table :

id    | name      | email           
-----------------------------------
1     | John      | [email protected] 
2     | Mark      | [email protected]
3     | Harry     | [email protected]

and when i call my api i get this result:

[
  {
    id: 1,
    playerOne_id: 1,
    playerTwo_id: 3,
    winner_id: 0,
    status: "PENDING",
    created_at: "2019-01-10 00:00:00",
    updated_at: null,
    users: [
      {
        id: 1,
        name: "John",
        email: "[email protected]",
        email_verified_at: null,
        created_at: "2019-01-11 10:38:26",
        updated_at: "2019-01-11 10:38:26"
      }
    ]
  },
  {
    id: 2,
    playerOne_id: 2,
    playerTwo_id: 1,
    winner_id: 0,
    status: "PENDING",
    created_at: "2019-01-11 08:26:28",
    updated_at: "2019-01-11 08:26:28",
    users: [
      {
        id: 2,
        name: "Mark",
        email: "[email protected]",
        email_verified_at: null,
        created_at: "2019-01-11 10:40:13",
        updated_at: "2019-01-11 10:40:13"
      }
    ]
  },
  {
    id: 3,
    playerOne_id: 3,
    playerTwo_id: 2,
    winner_id: 0,
    status: "PENDING",
    created_at: "2019-01-11 08:45:22",
    updated_at: "2019-01-11 08:45:22",
    users: [
      {
        id: 3,
        name: "harry",
        email: "[email protected]",
        email_verified_at: null,
        created_at: "2019-01-11 10:40:13",
        updated_at: "2019-01-11 10:40:13"
      }
    ]
  }
]

What i wan't to get is this result (i just show you the first match)

[
  {
    id: 1,
    playerOne_id: 1,
    playerTwo_id: 3,
    winner_id: 0,
    status: "PENDING",
    created_at: "2019-01-10 00:00:00",
    updated_at: null,
    users: [
      {
        id: 1,
        name: "John",
        email: "[email protected]",
        email_verified_at: null,
        created_at: "2019-01-11 10:38:26",
        updated_at: "2019-01-11 10:38:26"
      },
      {
        id: 3,
        name: "Harry",
        email: "[email protected]",
        email_verified_at: null,
        created_at: "2019-01-11 10:38:26",
        updated_at: "2019-01-11 10:38:26"
      }
    ]
  }
]

is this possible in Laravel ? thanks :)


Solution

  • To be able to return all the players as part of the user array you could either change the relation between Match and User to be a belongsToMany (many-to-many) or simply manipulate the match data before returning it from the controller.

    Changing it to a BelongsToMany would be my recommendation since it semantically makes more sense, however, I understand that (depending on how much work you've done so far) it may not be practical.

    You would need to:

    1. rename your matchs table to be matches (this will also mean you can remove
      protected $table = 'matchs'; from your Match model class ).
    2. Create a table called match_user. If your project is using migrations then you can run:
      php artisan make:migration create_match_user_table --create=match_user
      In that migration you would need to change the contents of the up() method to be:

      public function up()
      {
          Schema::create('match_user', function (Blueprint $table) {
              $table->integer('match_id')->unsigned();
              $table->integer('user_id')->unsigned();
              $table->timestamps();
      
              $table->primary(['match_id', 'user_id']);
          });
      }
      
    3. Run php artisan migrate

    4. In your Match model change the users() relationship to be:

      public function users()
      {
          return $this->belongsToMany(User::class)->withTimestamps();
      }
      
    5. If the data you have in your tables is dummy data you can skip this step. Otherwise, you'll need to move the playerOne_id and playertwo_id information to the new pivot table. You could do that will the following:

      Match::all()->each(function ($match) {
          $match->users()->attach([$match->playerOne_id, $match->playertwo_id]);
      });
      

      It doesn't really matter where you run this as it only has to be run once so after you've run it you can delete it. I would usually run this in a route or in tinker.

    6. Then comes the clean up. Again, if the data you have is just dummy data, then I would just remove the playerOne_id and playertwo_id the original migration, otherwise create a new migration that removes those field Dropping columns docs


    Once the above has been done you would just need to do the following in your controller to get the results:

    $matches = Match::with('users')->latest()->get();