Search code examples
laravellaravel-eloquent-resource

Laravel Eloquent: API Resources - How does it work, so that I can create a similar class?


Look at this example:

use App\Http\Resources\UserResource;
use App\Models\User;

Route::get('/user/{id}', function ($id) {
    return new UserResource(User::findOrFail($id));
});

How does this internally work? Because at first glance we just return an Object of the class UserResource. But Laravel is magically calling the toArray function and resolves it correctly.

I want the same for my Recommendation class to work.

use App\Http\Recommendations\RecentlyUpdatedRecommendation;
use ...

Route::get('/someurl', function () {
    return ['collections' => [
       new RecentlyUpdatedRecommendation(),
       new WatchlistRecommendation(),
       new LastWatchedRecommendation(),
       ...
    ]];
});

With

/**
 * All Recommendations use this as output
 */
abstract class Recommendation
{
    // No Idea what needs to be put here
}

The output array/json should resolve into this:

    return [
        'title' => $title,
        'description' => $description,
        'courses' => (collection) $courses
    ];

Solution

  • Any resource that you create is instance of Illuminate\Contracts\Support\Responsable Interface, This means that each of them must have toResponse method implemented.

    If we look at JsonResponse's toResponse method we see this code:

    public function toResponse($request)
    {
        return (new ResourceResponse($this))->toResponse($request);
    }
    

    Internally it is calling Resource's resolve method.

    resolve method is where we see toArray method to be called

    public function resolve($request = null)
    {
        $data = $this->toArray(
            $request = $request ?: Container::getInstance()->make('request')
        );
    }
    

    By creating new response you are just overriding toArray method with your logic.

    In Your case, you don't need to write too much to get toArray method "magically" called

    use Illuminate\Contracts\Support\Arrayable;
    use JsonSerializable;
    
    abstract class Recommendation implements Arrayable, JsonSerializable {
      
    }
    ...
    class SomeRecomendation extends Recommendation {
    
      public function jsonSerialize()
      {
        return $this->toArray();
      }
    
      public function toArray(){
        return [
          'title' => $title,
          'description' => $description,
          'courses' => (collection) $courses
        ];
      }
    }
    
    

    Now watch will happen, When you return ['collecion' => new SomeRecomendation()], Laravel calls json_encode on it ( to return response as json ), Because new SomeRecomendation() is instance of JsonSerializable it will call jsonSerialize method and encodes an array returned from it.

    When you return only return new SomeRecomendation(); from controller, it will call, because it is instance of Arrayable, Laravel calls toArray method magically.

    Hope this answer helps you.