mstdmstd's avatar

Laravel resources lost meta when I added mapping

Hello, In Laravel 6 I make pagination listing like

        $ads = Ad
            ::getByTitle($this->filter_title)
            ->getByStatus('A')
            ->leftJoin('users', 'users.id', '=', 'ads.creator_id')
            ->orderBy($this->order_by, $this->order_direction)

            ->select(
                'ads.*',
                'users.name as creator_username',
                'users.email as creator_email',
                'users.phone as creator_phone'
            )
            ->offset($limit_start)
            ->take($ads_per_page)
            ->distinct()
            ->paginate($ads_per_page);
            return (new AdCollection($ads));

and it works ok for me and in meta of returned data I got additive params including total :

meta":{"current_page":1,"from":1,"last_page":2,..."to":4,"total":5,"version":"1.0.3"}}

But I got a problem I added map to this request like:

        $ads = Ad
            ::getByTitle($this->filter_title)
            ->getByStatus('A')
            ->leftJoin('users', 'users.id', '=', 'ads.creator_id')
            ->orderBy($this->order_by, $this->order_direction)

            ->select(
                'ads.*',
                'users.name as creator_username',
                'users.email as creator_email',
                'users.phone as creator_phone'
            )

            ->offset($limit_start)
            ->take($ads_per_page)
            ->distinct()
            ->paginate($ads_per_page)
            ->map(function ($adItem) {
                $adImage= AdImage
                    ::getByAdId($adItem->id)
                    ->getByMain(true)
                    ->first();
                if ($adImage) {
                    $adItem->main_image= $adImage->image;
                    $adItem->main_image_info= $adImage->info;
                }

                $categories = AdCategory
                    ::getByAdId($adItem->id)
                    ->leftJoin('categories', 'categories.id', '=', 'ad_categories.category_id')
                    ->orderBy('category_name', 'desc')

                    ->select(
                        'ad_categories.category_id as category_id',
                        'categories.name as category_name',
                        'categories.slug as category_slug'
                    )
                    ->get()
                    ->toArray();
                $adItem->categories= $categories;

                return $adItem;
            })
            ->all();

as I lost all meta data. Why error and if there is a way to fix it?

In app/Http/Resources/AdCollection.php I have :

<?php

namespace App\Http\Resources;

use App\library\MyFuncsClass;
use Illuminate\Http\Resources\Json\ResourceCollection;

class AdCollection extends ResourceCollection
{

    public static $wrap = 'ads';

    public function toArray($request)
    {
        return $this->collection->transform(function($ad){
            return [
                'id' => $ad->id,
                'title' => $ad->title,
                'slug' => $ad->slug,
                'phone_display' => $ad->phone_display,
                'published' => $ad->published,
                'price' => $ad->price,
                'ad_type' => $ad->ad_type,
                'expire_date' => $ad->expire_date,
                'description' => $ad->description,
                'creator_id' => $ad->creator_id,
                'creator_username' => !empty($ad->creator_username) ? $ad->creator_username : null,
                'creator_email' => !empty($ad->creator_email) ? $ad->creator_email : null,
                'creator_phone' => !empty($ad->creator_phone) ? $ad->creator_phone : null,
                'categories' => !empty($ad->categories) ? $ad->categories : [],
                'main_image' => $ad->main_image,
                'main_image_info' => $ad->main_image_info,
                'created_at' => $ad->created_at,
                'updated_at' => $ad->updated_at,
            ];
        });
    }

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

}

Thanks!

0 likes
3 replies
bobbybouwmann's avatar

Because whenever you map over the results and return the models you get a regular collection. However, when you call pagination you get a different kind of object back. One with information about the pagination. This data is used for the metadata as well. Since you map over it, there is no pagination object anymore.

Now, resources are actually made for this. It seems that you have two relations you need to add.

Documentation: https://laravel.com/docs/6.x/eloquent-resources#conditional-relationships

Also, it's a good pattern to use the resource classes inside the collections

class AdCollection extends ResourceCollection
{
    public static $wrap = 'ads';

    public function toArray($request)
    {
        $this->collection->transform(function ($ad) {
            return new AdResource($ad);
        });

        return parent::toArray($request);
    }
}
2 likes
mstdmstd's avatar

Thank you for feedback! I try to make next : I remove mapping from request :

        $ads = Ad
            ::getByTitle($this->filter_title)
            ->getByStatus('A')
            ->leftJoin('users', 'users.id', '=', 'ads.creator_id')
            ->orderBy($order_by, $order_direction)

            ->select(
                'ads.*',
                'users.name as creator_username',
                'users.email as creator_email',
                'users.phone as creator_phone'
            )

            ->offset($limit_start)
            ->take($ads_per_page)
            ->distinct()
            ->paginate($ads_per_page)l

I remade app/Http/Resources/AdCollection.php :

<?php

namespace App\Http\Resources;

use App\library\MyFuncsClass;
use Illuminate\Http\Resources\Json\ResourceCollection;

class AdCollection extends ResourceCollection
{

    public static $wrap = 'ads';

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

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

}

I created app/Http/Resources/AdResource.php :

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class AdResource extends JsonResource
{
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'title' => $this->title,
            'slug' => $this->slug,
            'phone_display' => $this->phone_display,
            'published' => $this->published,
            'price' => $this->price,
            'ad_type' => $this->ad_type,
            'top' => $this->top,
            'expire_date' => $this->expire_date,
            'description' => $this->description,
            'creator_id' => $this->creator_id,
            'creator_username' => !empty($this->creator_username) ? $this->creator_username : null,
            'creator_email' => !empty($this->creator_email) ? $this->creator_email : null,
            'creator_phone' => !empty($this->creator_phone) ? $this->creator_phone : null,
            'categories' => !empty($this->categories) ? $this->categories : [],
            'main_image' => $this->main_image,
            'main_image_info' => $this->main_image_info,
            'created_at' => $this->created_at,
            'updated_at' => $this->updated_at,
        ];
    }
}

Now in results data I see all meta info, but I lost main_image, main_image_info and categories fields

I see how whenLoaded must be used inside of AdResource, but how have I to run requests to get values for main_image, main_image_info and categories fields? Just in resource like :

class AdResource extends JsonResource
{
    public function toArray($request)
    {
    
        $adImage= AdImage
           ::getByAdId($this->id)
           ->getByMain(true)
           ->first();

        return [
            'id' => $this->id,
            ...
            'adImage' => $adImage->image,

? That not seems as good way...

mstdmstd's avatar

I made some search and found this branch: https://stackoverflow.com/questions/49477682/conditional-relationship-not-appearing-in-laravel-resource-api

It was implemented with related model methods :

$games = Game::with('availableInterests')->get();

But I have a bit different case as as I can not get main_image and main_image_info using “:with”(or can ?) . I think I can read related categories fields with double relationship(seems eloquent can do it...). Any ideas ?

Please or to participate in this conversation.