Lumen API Tutorial – Response transformers with PHP League’s Fractal
Introduction
In this tutorial, we will install PHP League’s Fractal and write helper methods to transform models into arrays which can be turned into JSON. Fractal also formats the array using JSON API standard https://jsonapi.org. It can also load related objects in one request. In this project, we will use Lumen as our code base.
Installation
Use composer to install Fractal in our project.
composer require league/fractal
That is all have to do to install Fractal in our project.
Helper Methods
To use Fractal easily, we can create a class to contain our helper methods but we can also put it in our parent controller. First we need to write a method to create the Fractal Manager.
Edit app/Http/Controllers/Controller.php or any other parent controller that you have and add this method.
This will be all the imports that we will be using
use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Http\Request; use League\Fractal\Manager; use League\Fractal\Pagination\IlluminatePaginatorAdapter; use League\Fractal\Resource\Collection; use League\Fractal\Resource\Item; use League\Fractal\Serializer\JsonApiSerializer;
Now we can write a method to make a Fractal Manager.
private function getFractalManager() { $request = app(Request::class); $manager = new Manager(); $manager->setSerializer(new JsonApiSerializer()); if (!empty($request->query('include'))) { $manager->parseIncludes($request->query('include')); } return $manager; }
The include
query parameter will be use to load related models in the response.
Single Item Transformer
public function item($data, $transformer) { $manager = $this->getFractalManager(); $resource = new Item($data, $transformer, $transformer->type); return $manager->createData($resource)->toArray(); }
Collection Transformer
public function collection($data, $transformer) { $manager = $this->getFractalManager(); $resource = new Collection($data, $transformer, $transformer->type); return $manager->createData($resource)->toArray(); }
Pagination Transformer
/** * @param LengthAwarePaginator $data * @param $transformer * @return array */ public function paginate($data, $transformer) { $manager = $this->getFractalManager(); $resource = new Collection($data, $transformer, $transformer->type); $resource->setPaginator(new IlluminatePaginatorAdapter($data)); return $manager->createData($resource)->toArray(); }
The transformers are referencing type
on the transformer objects, this is just a key that helps API consumer identify what kind of object they get.
Example Transformer
In my example project, I have User and Post models with sample records in the database. User can have 0 or more Posts.
We can create a parent Transformer so whenever we add some kind of common functionality to all transformers, we can add it easily.
use League\Fractal\TransformerAbstract; abstract class Transformer extends TransformerAbstract { public $type = 'unknown'; public abstract function transform($post); }
UserTransformer.php
class UserTransformer extends Transformer { public $type = 'user'; /** * @param \App\User $post * @return array */ public function transform($post) { return [ 'id' => $post->id, 'name' => $post->name, 'email' => $post->email, ]; } }
PostTransformer.php
class PostTransformer extends Transformer { public $type = 'post'; protected $availableIncludes = ['user']; public function transform($post) { return [ 'id' => $post->id, 'title' => $post->title, 'body' => $post->body, ]; } public function includeUser($post) { return $this->item($post->user, new UserTransformer(), 'user'); } }
PostTransformer.php overrides $availableIncludes
. This tells fractal that it can load the related object user
for a Post
when it finds include=user
on the query parameter.
Trying it out
So to return a formatted representation of a Post
we can write something like this on our controller.
use App\Post; use App\Transformers\PostTransformer; class ExampleController extends Controller { public function index() { return $this->paginate(Post::paginate(5), new PostTransformer()); } }
Additional Notes
Make sure to eager-load the related models if you have “include”s available. When dealing with models with multiple related models and maybe some nested ones, the API will have to execute multiple SQL to generate the response.
That is all. Thanks for reading!
Tiago
October 11, 2020 at 11:04 am
Hi, you save me!!!! nice job!
Stefano
January 26, 2021 at 2:09 pm
Great tutorial!
Thank you
Stefano
Daniel
March 21, 2021 at 9:58 am
This tutorial helped me understand and fix an issue I’ve spent the day trying to solve, thank you!