06 Jul 2019
cat: API, Laravel, PHP
3 Comments

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());
    }
}

Lumen API Tutorial – Response transformers with PHP League’s Fractal

Lumen API Tutorial – Response transformers with PHP League’s Fractal

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!

References


  1. Hi, you save me!!!! nice job!


  2. Great tutorial!
    Thank you
    Stefano


  3. This tutorial helped me understand and fix an issue I’ve spent the day trying to solve, thank you!

Post A Comment