0

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!

mstdmstd
  • 2,195
  • 17
  • 63
  • 140
  • Is there a reason why you define a `ResourceCollection` and not using a class for returning singular resources, e.g. defining `TaskResource` that extends `JsonResource` and then returning `TaskResource::collection($tasks)`? – D Malan Jan 14 '20 at 08:30
  • I think your resource is being wrapped in `data` regardless of your using `withoutWrapping()` because your `meta` function returns a non-empty object. Check out the [source code](https://github.com/laravel/framework/blob/11f410a3439f47efb60f19cf100e719e461fd75b/src/Illuminate/Http/Resources/Json/ResourceResponse.php#L67) here where Laravel wraps your response if you have `with` data. – D Malan Jan 14 '20 at 08:40
  • I tried JsonResource at first, but as I need method public function with($task) ... as in my code, I tried it and it did not work for me. I found that this “with” method is not accessible with JsonResource, but only for ResourceCollection. Is it so ? – mstdmstd Jan 14 '20 at 08:46
  • I tried several variants with wrapping, but failed. And sure I need to have 1 Collection item both for listing of elements and 1 element – mstdmstd Jan 14 '20 at 08:48
  • How do you expect your response to look if you want it to return `with` data? Laravel wraps your response in a `data` array if you have `with` data because otherwise it will be mixed up with your `with` data. For example, it will try to return an array like `[$task1, $task2, 'meta' => ['version' => 1]]]`. – D Malan Jan 14 '20 at 08:57
  • I search how to reduce data.data[0] and data.data[0][0] if that is possible. Keeping the functionality. Also very interesting who used ResourceCollection which syntax they use both on server/vlient... – mstdmstd Jan 14 '20 at 11:03

1 Answers1

1
  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;
D Malan
  • 10,272
  • 3
  • 25
  • 50
  • Great, thanks! The only thing I would like to ask is it possible to avoid using of 2 files : based on ResourceCollection and JsonResource ? Or maybe 1 of them as wrapper of the second? I mean listing of fields could be rather long – mstdmstd Jan 15 '20 at 16:52
  • 1
    Yes, if you return `return parent::toArray($request);` from `TaskCollection` it will automatically use the `toArray` function of `TaskResource` for each task. So you won't have to list the fields in both of those classes. – D Malan Jan 16 '20 at 06:52
  • Pls look at MODIFIED 2: – mstdmstd Jan 16 '20 at 12:04
  • 1
    The items in `Skill` should be `'id' => $this->id,` and not `'id' => $request->id,` – D Malan Jan 16 '20 at 12:10