Search code examples
phplaravelapilaravel-8laravel-api

How to properly decode response paginated resource from an API in laravel 8?


I'm trying to develop a laravel application that must have an internal API to get always the data and the frontend controllers to consume this API to rendering views. This API will be consumed by mobile apps, so all requests will be handled by the API.

This is my API index action, which is working fine:

public function index(Request $request)
    {
        $filters = $request->all();

        $query = Place::query()->with('user');

        if(!isset($filters['filterType']) || !in_array(Str::lower($filters['filterType']), ['and', 'or']) ){
            $filters['filterType'] = 'or';
        }

        //apply filters
        foreach($filters as $filter => $value){
            if(Place::hasProperty($filter, app(Place::class)->getTable())){
                if($filters['filterType'] == 'and'){
                    $query->where($filter, $value); 
                }
                else{
                    $query->orWhere($filter, $value);                    
                }
            }
        }

        //sorting
        if(!isset($filters['sortOrder']) || !in_array($filters['sortOrder'], ['asc', 'desc'])){
            $sortOrder = 'desc';
        }
        else{
            $sortOrder = $filters['sortOrder'];
        }

        if(isset($filters['sortBy'])){
            $sortBy = $filters['sortBy'];
            foreach(explode(',', $sortBy) as $sortField){
                if(Place::hasProperty($sortField, app(Place::class)->getTable())){
                    $query->orderBy($sortField, $sortOrder);
                }
            }
        }
        
        //default pagination
        if(!isset($filters['maxResults'])){
            $filters['maxResults'] = 5;
        }
        if(!isset($filters['page'])){
            $filters['page'] = 1;
        }

        //apply pagination
        $results = $query->paginate($filters['maxResults'], ['*'], 'page', $filters['page']);
        $resultsCollectionResource = PlaceResource::collection($results);

        return $resultsCollectionResource;
    }

If I do this request by postman http://api.site.test/places?fields=id,name,user_id

{
    "maxResults": 2
}

I get the expected results, with meta and links properties:

{
    "data": [
        {
            "id": 1,
            "name": "Lubowitz Group (Customer-focused real-time complexity)",
            "user_id": 3
        },
        {
            "id": 2,
            "name": "Heaney, Dietrich and Spencer (Fully-configurable multi-state processimprovement)",
            "user_id": 10
        }
    ],
    "links": {
        "first": "http://api.ourplaces.test/places?page=1",
        "last": "http://api.ourplaces.test/places?page=5",
        "prev": null,
        "next": "http://api.ourplaces.test/places?page=2"
    },
    "meta": {
        "current_page": 1,
        "from": 1,
        "last_page": 5,
        "links": [
            {
                "url": null,
                "label": "« Previous",
                "active": false
            },
            {
                "url": "http://api.ourplaces.test/places?page=1",
                "label": "1",
                "active": true
            },
            {
                "url": "http://api.ourplaces.test/places?page=2",
                "label": "2",
                "active": false
            },
            {
                "url": "http://api.ourplaces.test/places?page=3",
                "label": "3",
                "active": false
            },
            {
                "url": "http://api.ourplaces.test/places?page=4",
                "label": "4",
                "active": false
            },
            {
                "url": "http://api.ourplaces.test/places?page=5",
                "label": "5",
                "active": false
            },
            {
                "url": "http://api.ourplaces.test/places?page=2",
                "label": "Next »",
                "active": false
            }
        ],
        "path": "http://api.ourplaces.test/places",
        "per_page": 2,
        "to": 2,
        "total": 10
    }
}

Then I have the frontend controller, with this action:

    public function index()
    {
        $request = Request::create(env('API_URL').'/places', 'GET');

        $response = Route::dispatch($request);
        $responseContent =  $response->content();
        $places = json_decode($responseContent);
        return view('places.index', ['places' => $places]);
    }

And this view:

<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            {{ __('Places') }}
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-xl sm:rounded-lg">
                @foreach($places->data as $place)
                    <div class="row">
                        <div class="col-md-12 text-center">
                            <h1 class="post-title">{{ $place->name }}</h1>
                            <p>{{ $place->streetAddress }}!</p>
                            <p><a href="{{ route('web.places.show', [ 'place' => $place->id ]) }}">Ver Sitio...</a></p>
                        </div>
                    </div>
                    <hr>
                @endforeach

                <div class="row">
                    <div class="col-md-12 text-center">
                        {{ $places->links() }}
                    </div>
                </div>

            </div>
        </div>
    </div>
</x-app-layout>

But I get the error:

Error Call to undefined method stdClass::links() (View: C:\xampp\htdocs\ourplaces\resources\views\places\index.blade.php)

I think the problem is in the frontend controller, I think I'm doing a bad request to API, because after making json_decode, I get stdClass objects, not the original objects that the API controller generated.

API object:

https://i.sstatic.net/lEe50.png

Decoded object in frontend: https://i.sstatic.net/Jc85R.png

What I'm doing wrong? Thanks you all.

EDIT

Based on @matiaslauriti response, I changed my API calling to guzzle:

public function index(Request $request)
    {
        //$places =  redirect()->route('api.places.index', ['request' => $request ])->content();

        $response = Http::get(env('API_URL').'/places');
        $places = $response->object();

        return view('places.index', ['places' => $places]);
    }

But I still having exactly the same problem. I tested other methods than $response->object(), like collection. But I never get an object that can use $places->links() method in the view.


Solution

  • $places->links()

    This function that you tend to call is a member of Laravel paginator class and $places should be instance of this class to support this function

    if you want to continue with this implementation that you have, and support Laravel pagination features you should make an instance of Laravel lengthawarepaginator and manually create paginator instance.

    According to its parameters this might looks something like this:

    in your front controller add:

    $paginatedPlaces = new LengthAwarePaginator($places->data, $places->meta->total, $places->meta->per_page)
    

    then pass $paginatedPlaces to your view

    the second option that i suggest is that instead of using ->links() function just simply print out the links property from the response.