Search code examples
laravellaravel-5laravel-5.7data-retrieval

Laravel returns a Collection with duplicates of the first model


I'm developing a Laravel 5.7 (API) application with a PostgreSQL database behind it. The relevant Models are: User (customers and employees), Car, and Request.

An employee User creates a Request for a Car, that belongs to a customer User.

The relationships are:

  • Car (as customer) : User = n:m
  • Car : Request = 1:n
  • User : Request (as employee) = 1:n

enter image description here

(The data design is suboptimal, to put it mildly, but anyway, it's the given reality for now.)

Now to the actual issue. I want to display all Requests of a customer User:

Request::query()
    ->join('user_car', 'user_car.car_id', '=', 'request.car_id')
    ->join('user', 'user.id', '=', 'user_car.user_id')
    ->where('user.id', '=', $customer->id)
    ->select()
    ->get();

The customer with the given $customer->id has n Requests. And the length of the result Collection of the call above is correct. But all these n entries are duplicates of the first one. Means: I'm getting a list with n instances of Request#1.

Why does the first call return a list of references to the same Model object? Is it a (known) bug?


ADDITIONAL INFORMATION

Relationships:

class User extends \Illuminate\Foundation\Auth\User
{
    // ...
    public function cars()
    {
        return $this->belongsToMany('App\Car', 'user_car')->withTimestamps();
    }
    public function requests()
    {
        return $this->hasMany(Request::class, 'user_id');
    }
}

class Car extends Model
{
    // ...
    public function users()
    {
        return $this->belongsToMany('App\User', 'user_car')->withTimestamps();
    }
    public function requests()
    {
        return $this->hasMany(Request::class);
    }
}

class Request extends Model
{
    // ...
    public function car()
    {
        return $this->belongsTo(Car::class);
    }
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

The query is correct.

I logged the database requests, got the generated statement

SELECT *
FROM "request"
INNER JOIN "user_car" ON "user_car"."car_id" = "request"."car_id"
INNER JOIN "user" ON "user"."id" = "user_car"."user_id"
WHERE "user"."id" = 1;

..., and executed it manually. The result table contains as expected n different entries.

NOT just references

The result Collection's entries instances references to the different objects:

$test1 = $resultCollection->first();
$test2 = $resultCollection->last();
$test3 = spl_object_hash($test1);
$test4 = spl_object_hash($test2);

Xdebug output:

$test3 = "0000000077505ccd000000007964e0a8" <-- ccd0
$test4 = "0000000077505c33000000007964e0a8" <-- c330

Workaround

I found a workaround. This call

Request::whereIn('car_id', $customer->cars()->pluck('id')->toArray())->get();

... retrieves the correct/expected set of model.


Solution

  • First, note that your object hashes are not actually identical, and you're likely dealing with two separate instances.

    What you're likely experiencing is an issue with ambiguous column names. When you JOIN together multiple tables, any matching/duplicate column names will contain the value of the last matching column. Your SQL GUI/client usually separates these. Unfortunately Laravel doesn't have a prefixing mechanism, and just uses an associative array.

    Assuming all of your tables have a primary key column of id, every Request object in your result set will likely have the same ID - the User's ID you pass in the WHERE condition.

    You can fix this in your existing query by explicitly selecting the columns you need to prevent ambiguity. Use ->select(['request.*']) to limit the returned info to the Request object data.