Search code examples
laraveleloquentmany-to-manyeloquent-relationship

Laravel Many-to-Many Relationship giving wrong result


I am developing a database for giveaways. I have two main tables: participants and giveaways with an N:N relationship since a participant can participate to one or more giveaways and a giveaway can have one or more participants. My (simplified) table structure is the following:

giveaways 
----------------------
uuid    name


participant_giveaway
----------------------
id    giveaway_uuid    fiscal_code 


participants
----------------------
fiscal_code    first_name    last_name

Now, I have followed the docs for Laravel 11 but the relationship gives me wrong results. For instance, I have 4 participants for a giveaway and when I try to retrieve them with Giveaway::find($uuid)->participants, it returns 2 random participants that are linked to another giveaway.

This is my Giveaway model.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Collection;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;

class Giveaway extends Model implements HasMedia
{
    use HasFactory, InteractsWithMedia, HasUuids;

    protected $primaryKey = 'uuid';

    protected $fillable = [
        'name',
        'terms_and_conditions',
        'privacy_policy',
        'date_start',
        'date_end'
    ];

    
    protected $casts = [
        'date_start' => 'datetime',
        'date_end' => 'datetime',
    ];

    public function participants(): BelongsToMany
    {
        return $this->belongsToMany(Participant::class, 'participant_giveaway', 'giveaway_uuid', 'fiscal_code')
            ->withPivot('drawn', 'code', 'expiration_date', 'status', 'created_at')->withTimestamps();
    }

    public function drawableParticipants(): Collection
    {
        return $this->participants()->where('drawn', 0)->get()->pluck('participant');
    }
}

This is my Participant model.

<?php

namespace App\Models;

use Database\Factories\ParticipantFactory;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Support\Collection;

class Participant extends Model
{
    use HasFactory;

    protected $fillable = [
        'name',
        'date_start',
        'date_end'
    ];

    public function giveaways(): BelongsToMany
    {
        return $this->BelongsToMany(Giveaway::class, 'participant_giveaway', 'fiscal_code', 'giveaway_uuid')
            ->withPivot('drawn', 'code', 'expiration_date', 'status', 'created_at')->withTimestamps();
    }
}

Edit: I made a dump of the query being run when accessing the giveaway's participants through the relationship. I get: "select * from participants inner join participant_giveaway on participants.id = participant_giveaway.fiscal_code where participant_giveaway.giveaway_uuid = ?". The problem is that Laravel is joining the participants table on the id instead of the fiscal_code. I correctly set up the fields on which to join in the model so I do get why it does not join on them.


Solution

  • Since I could not figure out why Laravel still persists to join on the id column instead of the fiscal_code, I opted for the easiest road and changed the participants primary key from id to fiscal_code.

    Furthrmore, I also had a typo in my Participant giveaways() method. Instead of using this->belongsToMany() I was using this->BelongsToMany (with a capital B).

    Edit: I opened an issue on their repo and told me I had to pass another argument on the the belongsToMany method (from either side) to customize the joining column on the participants table, like so:

    public function participants(): BelongsToMany
    {
        return $this->belongsToMany(Participant::class, 'participant_giveaway', 'giveaway_uuid', 'fiscal_code',
                relatedKey: 'fiscal_code')
            ->withPivot('drawn', 'created_at')
            ->withTimestamps();
    }