[SOLVED] Does Eloquent's createMany method preserves the input array order?

Issue

I have Choice model which belongTo Question model which belongTo Quiz model. To be exact:

  • A Quiz has exactly 4 questions.
  • A Question has exactly 4 choices.

The mentioned relationships are well received in a the Request (showed below).


I need to do all the inserts in three transactions.

I have the following two successful transactions.

DB::transaction(function () use ($request) {
    $quiz = Quiz::create(['start_at' => $request->start_at, 'duration' => $request->duration]);
    $questions = $quiz->questions()->createMany($request->questions);
// ...

Now I need the confirmation that $questions will be similar to $request->questions in the order of the data under all circumstances, to rely on that fact and map id from the returned collection to the choices from the Request as foreign ids and do one more bulk insert.

// ...
    $choices = [];
    foreach ($request->questions as $i => $question_data) {
        foreach ($question_data['choices'] as $j => $choice_content) {
            $choices[] = [
                'question_id' => $questions[$i - 1]->id, // Append foreign id
                'content' => $choice_content,
                'choice_number' => $j,
                'is_correct' => $j == $question_data['is_correct'],
                'created_at' => date('Y-m-d H:i:s'),
            ];
        }
    }
    Choice::insert($choices);
});

{
    "start_at": "2022-07-17T19:54",
    "duration": "191",
    "questions": {
        "1": {
            "content": "Sed asperiores eaque voluptatem id saepe.",
            "choices": {
                "1": "Ad quia impedit libero voluptatem qui.",
                "2": "Voluptates quis consequuntur natus illum laborum tempore.",
                "3": "Corrupti dolorum optio quam qui.",
                "4": "Numquam quidem voluptatem nisi."
            },
            "is_correct": "4"
        },
        "2": {
            "content": "Illum ut tempora.",
            "choices": {
                "1": "Consequatur minima tempora qui amet.",
                "2": "Voluptate sint sapiente illum delectus possimus enim.",
                "3": "A magni aut aperiam aliquam laboriosam.",
                "4": "Faustino MacGyver"
            },
            "is_correct": "4"
        },
        "3": {
            // ...
        },
        "4": {
            // ...
        }
    }
}

Solution

Laravel’s createMany does a foreach on the array you provide it and returns it in the order that you passed it in. It should retain the order.
This is the source:

   public function createMany(iterable $records)
    {
        $instances = $this->related->newCollection();

        foreach ($records as $record) {
            $instances->push($this->create($record));
        }

        return $instances;
    }

This does not necessarily apply to subsequent queries of that data. That is up to the database how it decides to order it in the absence of an order by clause.

That having been said, you can avoid the entire issue by setting the choices as you create each question. Since you have a relationship, you can allow Laravel to figure out how to set the proper ID’s.

foreach ($request->questions as $question_data) {

    $question = $quiz->questions()->create([
        // question data here
    ]);

    foreach ($question_data['choices'] as $j => $choice_content) {

        $question->choices()->create(
            [
                // choice data here
            ]
        );
      }
    }
    

Answered By – Ray Dabbah

Answer Checked By – Katrina (BugsFixing Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *