Search code examples
laravellaravel-artisanfakerlaravel-collection

Laravel collection pluck method not working as expected


I've entered the fantastic world of Laravel and I am currently looking into seeding a database with fake data for testing.

I have a couple of tables I want to work with; projects and stories.

The stories table has the columns; id, name and project_id (which is a fk to the projects table).

My projects table is already populated with a list of 10 projects. Now I need to populate 100 stories with random projects associated. I have the approach below.

public function run()
{
    DB::table('stories')->delete();
    DB::statement('ALTER TABLE stories AUTO_INCREMENT = 1');

    $faker = Faker::create();

    foreach(range(1, 100) as $index)
    {
        Story::create([
            'reference' => $faker->numberBetween(1, 9999),
            'name' => $faker->sentence(6),
            'project_id' => Project::orderBy(\DB::raw('RAND()'))->get()->first()->pluck('id')
        ]);
    }
}

I don't know if this is the best way of doing what I need. However, when performing this code every story's project_id is set to 1; the first project's id.

When I perform the following command in tinker... It always returns 1 as the id.

Project::orderBy(\DB::raw('RAND()'))->get()->first()->pluck('id')

But when I perform the next command in tinker...

Project::orderBy(\DB::raw('RAND()'))->get()->first()

It returns a random project every time. Which is strange. Because if everything up to ->pluck() is working then pluck() should fetch that collected items id... Right? This is what the above command returns.

<App\Project #000000000c385908000000000de30942> {
   id: 6,
   name: "New Bernadetteton",
   cover_photo_url: "/uploads/covers/horizon-grass.png",
   created_at: "2015-07-08 16:32:15",
   updated_at: "2015-07-08 16:32:15" }

See below screenshot for my terminal window to illustrate what I mean.

enter image description here


Solution

  • Here's what's happening:

    1. With ->first() you get the actual project model

    2. Then you call pluck('id') on it. BUT the Model class doesn't have that method.

    3. So with, as with every method the Model doesn't know, it redirects the call to a new query builder instance of the model.

    4. In the end, that call ends up here:

    Illuminate\Database\Eloquent\Builder@value

    public function value($column)
    {
        $result = $this->first(array($column));
    
        if ($result) return $result->{$column};
    }
    

    As you can see, that method runs a new query, using first() and then returns the desired row.


    Now what you actually want is either:

    1. Don't use pluck at all

    There isn't really a need to use that method, you can just access the model property:

    'project_id' => Project::orderBy(\DB::raw('RAND()'))->first()->id
    

    2. Use pluck, but do it right

    'project_id' => Project::orderBy(\DB::raw('RAND()'))->pluck('id')
    

    And btw, the main method is called value(). pluck() is just an alias. I recommend using value() in new code. It's possible that the alias will be removed some day. (Obviously just in a new release and with a note in the upgrade guide, so don't panic ;))