i want to change my API response to become array of array object in laravel

Viewed 152

i have a problem for the response, i want to change the response API because i need for my mobile APP, the feature have filter object based on date. So i hope you all can help me to solve the problem

i wanna change the response for my API

before:

{
    "tasks": [
        {
            "id": 7,
            "user_id": 1,
            "title": "gh",
            "date": "2022-02-10 13:05:00",
            "deskripsi": "gfh",
            "created_at": "2022-02-09T06:05:56.000000Z",
            "updated_at": "2022-02-09T06:05:56.000000Z"
        },
        {
            "id": 5,
            "user_id": 1,
            "title": "ghf",
            "date": "2022-02-17 16:05:00",
            "deskripsi": "fghf",
            "created_at": "2022-02-09T06:05:12.000000Z",
            "updated_at": "2022-02-09T06:05:12.000000Z"
        },
        {
            "id": 6,
            "user_id": 1,
            "title": "fgh",
            "date": "2022-02-17 18:05:00",
            "deskripsi": "gh",
            "created_at": "2022-02-09T06:05:40.000000Z",
            "updated_at": "2022-02-09T06:05:40.000000Z"
        }
    ]
}

here is the code for the response API above

return response([
            'tasks' => Task::where('user_id', auth()->user()->id)->where('date','>',NOW())->orderBy('date','asc')->get(),
        ],200);

and i want to change it my response API into this response

{
    "tasks": [
        {
            "date": "2022-02-10",
            "task": [
               {
                  "id": 7,
                  "user_id": 1,
                  "title": "gh",
                  "date": "2022-02-10 13:05:00",
                  "deskripsi": "gfh",
                  "created_at": "2022-02-09T06:05:56.000000Z",
                  "updated_at": "2022-02-09T06:05:56.000000Z"
               },
               {
                  "id": 7,
                  "user_id": 1,
                  "title": "gh",
                  "date": "2022-02-10 15:05:00",
                  "deskripsi": "gfh",
                  "created_at": "2022-02-09T06:05:56.000000Z",
                  "updated_at": "2022-02-09T06:05:56.000000Z"
               }
            ]
        },
        {
            "date": "2022-02-12",
            "task": [
               {
                  "id": 7,
                  "user_id": 1,
                  "title": "gh",
                  "date": "2022-02-12 13:05:00",
                  "deskripsi": "gfh",
                  "created_at": "2022-02-09T06:05:56.000000Z",
                  "updated_at": "2022-02-09T06:05:56.000000Z"
               }
            ]
        },
    ]
}
3 Answers

It sounds like you want to create a human friendly date field based on the date column, then group by it.

While solutions do exists to accomplish this at the database level, I believe you'd still need to loop around it again afterwards to get the hierarchy structure you're looking for. I don't think it's too complicated for PHP to loop through it.

My suggestion is as follows:

Before:

return response([
            'tasks' => Task::where('user_id', auth()->user()->id)
                ->where('date','>',NOW())->orderBy('date','asc')->get(),
        ],200);

After:

$out = [];
$tasks = Task::where('user_id', auth()->user()->id)
        ->where('date','>',NOW())->orderBy('date','asc')->get();

foreach($tasks as $task) {
    $date = strtok((string)$task->date, ' ');
            if (empty($out[$date])) {
                $out[$date] = (object)['date' => $date, 'task' => []];
            }
            $out[$date]->task[] = $task;
}

$out = array_values($out);

return response(['tasks' => $out], 200);

Note in the above I'm using the function strtok. This function might look new even to the most senior of php developers.... It's a lot like explode, except it can be used to grab only the first part before the token you're splitting on. While I could have used explode, since the latter part after the token isn't needed, strtok is better suited for the job here.

Do groupBy on the resulting Collection from the query (see docs: https://laravel.com/docs/9.x/collections#method-groupby)

For example, you could do:

$tasksGroupedByDate = Task::where(.......)
    ->get()
    ->groupBy(fn (Task $task) => $task->date->format('Y-m-d'));

(Note: above uses PHP 7.4 arrow functions. Also, add a date cast on the date column in your Task model to be able to use ->format( directly on the date field)

The above code results to:

{
  '2022-01-01' => [
    { Task object },
    { Task object },
    { Task object },
  ],
  '2022-01-02' => [
    { Task object },
    { Task object },
    { Task object },
  ],
}

(used Task object for brevity, but that will be ['id' => 1, 'title' => 'Task name', .....])

To morph that to the structure you want, you can use map and then values to remove the keys and turn it back to an ordered array:

$tasksGroupedByDate->map(fn ($tasksInGroup, $date) => [
    'date' => $date,
    'task' => $tasksInGroup
])->values();

If you want to combine everything into one method chain:

return [
    'tasks' => Task::where(......)
        ->get()
        ->groupBy(fn (Task $task) => $task->date->format('Y-m-d'))
        ->map(fn ($tasksInGroup, $date) => [
            'date' => $date,
            'task' => $tasksInGroup
        ])
        ->values(),
];
$task = Task::where('user_id', auth()->user()->id)->where('date','>',NOW())->orderBy('date','asc')->get();

foreach($task as $item){
   $date[] = item->date;
   $result = Task::where('user_id', auth()->user()->id)->where('date','=', $date)->get();
}

return response([
   'tasks' => 
      ['date' => $date,
      'task' => $task]
],200);

maybe something like this

Related