Search code examples
laravellaravel-resource

Reading data from in vue/cli from Laravel ResourceCollection


In Laravel 6 backend rest api app I use ResourceCollection and Resourcem defintion like :

<?php

namespace App\Http\Resources;
use App\Facades\MyFuncsClass;

use Illuminate\Http\Resources\Json\ResourceCollection;

class TaskCollection extends ResourceCollection
{
    public function toArray($task)
    {

        return [
            $this->collection->transform(function($task){
                return [
                    'id' => $task->id,
                    'name' => $task->name,
                    'slug' => $task->slug,
                    ...
                    'events' => !empty($task->events) ? $task->events : [],
                    'events_count' => !empty($task->events_count) ? $task->events_count : 0,
                    'created_at' => $task->created_at,
                    'updated_at' => $task->updated_at,
                ];
            }),
        ];

    }

    public function with($task)
    {
        return [
            'meta' => [
                'version'=>MyFuncsClass::getAppVersion()
            ]
        ];
    }

}

I found this decision at Laravel 5.5 API resources for collections (standalone data)

and it works for me, but I dislike the way I got data on client part, so for listing of data defined in control :

return (new TaskCollection($tasks));

I have to write in vue/cli app :

axios.post(this.apiUrl + '/adminarea/tasks-filter', filters, this.credentialsConfig)
    .then((response) => {
        this.tasks = response.data.data[0]
        this.tasks_total_count = response.data.meta.total
        this.tasks_per_page = response.data.meta.per_page

and when I need to get 1 item I define in laravel's control :

return (new TaskCollection([$task])); // I have to wrap it as array

and I have to write in vue/cli app :

axios.get(this.apiUrl + '/adminarea/tasks/' + this.task_id, this.credentialsConfig)
    .then((response) => {
        // console.log('response::')
        // console.log(response)
        //
        this.taskRow = response.data.data[0][0]

I dislike syntax like data.data[0] and data.data[0][0] and even that works for me

In my app/Providers/AppServiceProvider.php I have lines :

<?php

namespace App\Providers;

use Auth;
use Validator;
use Illuminate\Http\Resources\Json\Resource;

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {

        Resource::withoutWrapping(); // looks like that does not work for ResourceCollection!

<?php

namespace App\Providers;

use Auth;
use Validator;
use Illuminate\Http\Resources\Json\Resource;

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Resource::withoutWrapping();
        ...

If there is a way to get rid of data.data[0] and data.data[0][0] in vue/cli part ?

MODIFIED 2: I created new resource with few columns as app/Http/Resources/Skill.php :

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class Skill extends JsonResource
{
    public static $wrap = 'skills';
    public function toArray($request)
    {
        return [
            'id' => $request->id,
            'name' => $request->name,
            'user_id' => $request->user_id,
            'user_name' => $request->user_name,
            'skill_id' => $request->skill_id,
            'skill_name' => $request->skill_name,
            'rating' => $request->rating,
            'created_at' => $request->created_at,
        ];
    }
}

and app/Http/Resources/SkillCollection.php :

<?php

namespace App\Http\Resources;

use App\Facades\MyFuncsClass;
use App\Http\Resources\Skill;
use Illuminate\Http\Resources\Json\ResourceCollection;

class SkillCollection extends ResourceCollection
{
    public static $wrap = 'skills';
    public function toArray($request)
    {

        return $this->collection->transform(function($request){
            parent::toArray($request);
            });
    }

and resulting I have empty "skills":[null,null,null,null] results. What is wrong ?

Thanks!


Solution

    1. The first thing you can do is to use destructuring assignment to get the response data from your Axios request:
    axios.get(this.apiUrl + '/adminarea/tasks/' + this.task_id, this.credentialsConfig)
        .then(({ data }) => {
            this.taskRow = data.data[0][0]
    

    So now response.data.data[0][0] is shortened to data.data[0][0].

    1. The second thing you can do is, in the toArray function of your TaskCollection class, to return the transformed collection directly instead of wrapping it in an array:
        public function toArray($task)
        {
                return $this->collection->transform(function($task){
                    return [
                        'id' => $task->i
                        ...
                    ];
                });
        }
    

    Now you don't need to use [0] everywhere. So instead of data.data[0][0], you can access a single task with data.data[0].

    1. To avoid having to use [0] for singular responses, you should return a JsonResource instead of a ResourceCollection. For this you would need to move your code from inside $this->collection->transform to a new JsonResource class. For example:

    app/Http/Resources/TaskResource.php:

    <?php
    
    namespace App\Http\Resources;
    
    use App\Facades\MyFuncsClass;
    use Illuminate\Http\Resources\Json\JsonResource;
    
    class TaskResource extends JsonResource
    {
        public function toArray($task)
        {
             return [
                        'id' => $this->id,
                        'name' => $this->name,
                        'slug' => $this->slug,
                        ...
                    ];
        }
    
        public function with($task)
        {
            return [
                'meta' => [
                    'version'=>MyFuncsClass::getAppVersion()
                ]
            ];
        }
    
    }
    

    Then you can return a single resource with

    return new TaskResource($task);
    

    In Javascript you will be able to get a single task with data.data now:

    axios.get(this.apiUrl + '/adminarea/tasks/' + this.task_id, this.credentialsConfig)
        .then(({ data }) => {
            this.taskRow = data.data;
    

    You can refactor your TaskCollection class to implicitly use your new TaskResource class for each task:

    <?php
    
    namespace App\Http\Resources;
    use App\Facades\MyFuncsClass;
    
    use Illuminate\Http\Resources\Json\ResourceCollection;
    
    class TaskCollection extends ResourceCollection
    {
        public function toArray($request)
        {
            return parent::toArray($request);
        }
    
        public function with($task)
        {
            return [
                'meta' => [
                    'version'=>MyFuncsClass::getAppVersion()
                ]
            ];
        }
    }
    
    1. Like I tried to explain in the comments, you won't be able to get away from having to wrap your results in a key (like 'data') while returning non-null values from the with function. If you don't believe me, temporarily remove your with function and you'll see that you'll be able to access your tasks simply with data (if you have disabled wrapping with Resource::withoutWrapping();).

    However, one thing you can do is to customise the "wrap" key. For example, for a collection of tasks you might want it to be tasks and for a single task, just task. You can simply add a $wrap property to your TaskCollection and TaskResource classes:

    class TaskCollection extends ResourceCollection
    {
        public static $wrap = 'tasks';
        ...
    
    class TaskResource extends JsonResource
    {
        public static $wrap = 'task';
    

    Now you will be able to access a list of tasks from Javascript with data.tasks and a singular task with data.task:

    axios.post(this.apiUrl + '/adminarea/tasks-filter', filters, this.credentialsConfig)
        .then(({ data }) => {
            this.tasks = data.tasks;
    
    axios.get(this.apiUrl + '/adminarea/tasks/' + this.task_id, this.credentialsConfig)
        .then(({ data }) => {
            this.taskRow = data.task;