PHP
Article

PHP Fractal – Make Your API’s JSON Pretty, Always!

By Younes Rafie

This article was peer reviewed by Viraj Khatavkar. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!


If you’ve built an API before, I’ll bet you’re used to dumping data directly as a response. It may not be harmful if done right, but there are practical alternatives that can help solve this small problem.

One of the available solutions is Fractal. It allows us to create a new transformation layer for our models before returning them as a response. It’s very flexible and easy to integrate into any application or framework.

Image of fractals with PHP embedded in them

Installation

We will be using a Laravel 5.3 app to build an example and integrate the Fractal package with it, so go ahead and create a new Laravel app using the installer or via Composer.

laravel new demo

or

composer create-project laravel/laravel demo

Then, inside the folder, we require the Fractal package.

composer require league/fractal

Creating the Database

Our database contains a users and roles table. Every user has a role, and each role has a list of permissions.

// app/User.php

class User extends Authenticatable
{
    protected $fillable = [
        'name',
        'email',
        'password',
        'role_id',
    ];

    protected $hidden = [
        'password', 'remember_token',
    ];

    /**
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function role()
    {
        return $this->belongsTo(Role::class);
    }
}
// app/Role.php

class Role extends Model
{
    protected $fillable = [
        'name',
        'slug',
        'permissions'
    ];

    /**
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function users()
    {
        return $this->hasMany(User::class);
    }
}

Creating Transformers

We’re going to create a transformer for each model. Our UserTransformer class looks like this:

// app/Transformers/UserTransformer.php

namespace App\Transformers;

use App\User;
use League\Fractal\TransformerAbstract;

class UserTransformer extends TransformerAbstract
{
    public function transform(User $user)
    {
        return [
            'name' => $user->name,
            'email' => $user->email
        ];
    }
}

Yup, that’s all it takes to create a transformer! It just transforms the data in a way that can be managed by the developer, and not left to the ORM or the repository.

We extend the TransformerAbstract class and define the transform method that will be called with a User instance. The same thing goes for the RoleTransformer class.

namespace App\Transformers;

use App\Role;
use League\Fractal\TransformerAbstract;

class RoleTransformer extends TransformerAbstract
{
    public function transform(Role $role)
    {
        return [
            'name' => $role->name,
            'slug' => $role->slug,
            'permissions' => $role->permissions
        ];
    }
}

Creating Controllers

Our controllers should transform the data before sending it back to the user. We’re going to work on the UsersController class and only define the index and show actions for the moment.

// app/Http/Controllers/UsersController.php

class UsersController extends Controller
{
    /**
     * @var Manager
     */
    private $fractal;

    /**
     * @var UserTransformer
     */
    private $userTransformer;

    function __construct(Manager $fractal, UserTransformer $userTransformer)
    {
        $this->fractal = $fractal;
        $this->userTransformer = $userTransformer;
    }

    public function index(Request $request)
    {
        $users = User::all(); // Get users from DB
        $users = new Collection($users, $this->userTransformer); // Create a resource collection transformer
        $users = $this->fractal->createData($users); // Transform data

        return $users->toArray(); // Get transformed array of data
    }
}

The index action will query all users from the database, create a resource collection with the list of users and the transformer, and then perform the actual transformation process.

{
  "data": [
    {
      "name": "Nyasia Keeling",
      "email": "crooks.maurice@example.net"
    },
    {
      "name": "Laron Olson",
      "email": "helen55@example.com"
    },
    {
      "name": "Prof. Fanny Dach III",
      "email": "edgardo13@example.net"
    },
    {
      "name": "Athena Olson Sr.",
      "email": "halvorson.jules@example.com"
    }
    // ...
  ]
}

Of course, it doesn’t make sense to return all the users at once, and we should implement a paginator for this.

Pagination

Laravel tends to make things simple. We can accomplish pagination like this:

$users = User::paginate(10);

But to make this work with Fractal, we may need to add a little bit more code to transform our data before calling the paginator.

// app/Http/Controllers/UsersController.php

class UsersController extends Controller
{
    // ...

    public function index(Request $request)
    {
        $usersPaginator = User::paginate(10);

        $users = new Collection($usersPaginator->items(), $this->userTransformer);
        $users->setPaginator(new IlluminatePaginatorAdapter($usersPaginator));

        $users = $this->fractal->createData($users); // Transform data

        return $users->toArray(); // Get transformed array of data
    }
}

The first step is to paginate data from the model. Next, we create a resource collection like before, and then we set the paginator on the collection.

Fractal provides a paginator adapter for Laravel to convert the LengthAwarePaginator class, and it also has one for Symfony and Zend.

{
    "data": [
        {
            "name": "Nyasia Keeling",
            "email": "crooks.maurice@example.net"
        },
        {
            "name": "Laron Olson",
            "email": "helen55@example.com"
        },
        // ...
    ],
    "meta": {
        "pagination": {
            "total": 50,
            "count": 10,
            "per_page": 10,
            "current_page": 1,
            "total_pages": 5,
            "links": {
                "next": "http://demo.vaprobash.dev/users?page=2"
            }
        }
    }

}

Notice that it adds extra fields for pagination details. You can read more about pagination in the documentation.

Including Sub-Resources

Now that we’ve become familiar with Fractal, it’s time to learn how to include sub-resources (relations) with the response when it’s requested by the user.

We can request extra resources to be included with the response like this http://demo.vaprobash.dev/users?include=role. Our transformer can automatically detect what’s being requested and parse the include parameter.

// app/Transformers/UserTransformer.php

class UserTransformer extends TransformerAbstract
{
    protected $availableIncludes = [
        'role'
    ];

    public function transform(User $user)
    {
        return [
            'name' => $user->name,
            'email' => $user->email
        ];
    }

    public function includeRole(User $user)
    {
        return $this->item($user->role, App::make(RoleTransformer::class));
    }
}

The $availableIncludes property tells the transformer that we may need to include some extra data with the response. It will call the includeRole method if the include query parameter is requesting the user roles.

// app/Http/Controllers/UsersController.php

class UsersController extends Controller
{
    // ...

    public function index(Request $request)
    {
        $usersPaginator = User::paginate(10);

        $users = new Collection($usersPaginator->items(), $this->userTransformer);
        $users->setPaginator(new IlluminatePaginatorAdapter($usersPaginator));

        $this->fractal->parseIncludes($request->get('include', '')); // parse includes
        $users = $this->fractal->createData($users); // Transform data

        return $users->toArray(); // Get transformed array of data
    }
}

The $this->fractal->parseIncludes line is responsible for parsing the include query parameter. If we request the list of users we should see something like this:

{
    "data": [
        {
            "name": "Nyasia Keeling",
            "email": "crooks.maurice@example.net",
            "role": {
                "data": {
                    "name": "User",
                    "slug": "user",
                    "permissions": [ ]
                }
            }
        },
        {
            "name": "Laron Olson",
            "email": "helen55@example.com",
            "role": {
                "data": {
                    "name": "User",
                    "slug": "user",
                    "permissions": [ ]
                }
            }
        },
        // ...
    ],
    "meta": {
        "pagination": {
            "total": 50,
            "count": 10,
            "per_page": 10,
            "current_page": 1,
            "total_pages": 5,
            "links": {
                "next": "http://demo.vaprobash.dev/users?page=2"
            }
        }
    }
}

If every user had a list of roles, we could change the transformer to be like this:

// app/Transformers/UserTransformer.php

class UserTransformer extends TransformerAbstract
{
    protected $availableIncludes = [
        'roles'
    ];

    public function transform(User $user)
    {
        return [
            'name' => $user->name,
            'email' => $user->email
        ];
    }

    public function includeRoles(User $user)
    {
        return $this->collection($user->roles, App::make(RoleTransformer::class));
    }
}

When including a sub-resource, we can nest relations by using the a dot notation. Let’s say every role has a list of permissions stored in a separate table and we wanted to list users with their role and permissions. We can do it like this include=role.permissions.

Sometimes, we are required to include some necessary relations by default, like an address relation for example. We can do that by using the $defaultIncludes property inside the transformer.

class UserTransformer extends TransformerAbstract
{
    // ...

    protected $defaultIncludes = [
        'address'
    ];

    // ...
}

One of the things I really love about the Fractal package is the ability to pass parameters to include parameters. A good example from the documentation is order by. We can apply it to our example like so:

// app/Transformers/RoleTransformer.php

use App\Role;
use Illuminate\Support\Facades\App;
use League\Fractal\ParamBag;
use League\Fractal\TransformerAbstract;

class RoleTransformer extends TransformerAbstract
{
    protected $availableIncludes = [
        'users'
    ];

    public function transform(Role $role)
    {
        return [
            'name' => $role->name,
            'slug' => $role->slug,
            'permissions' => $role->permissions
        ];
    }

    public function includeUsers(Role $role, ParamBag $paramBag)
    {
        list($orderCol, $orderBy) = $paramBag->get('order') ?: ['created_at', 'desc'];

        $users = $role->users()->orderBy($orderCol, $orderBy)->get();

        return $this->collection($users, App::make(UserTransformer::class));
    }
}

The important part here is list($orderCol, $orderBy) = $paramBag->get('order') ?: ['created_at', 'desc'];, this will try to get the order parameter from the users include and will apply it to the query builder.

We can now order our included users list by passing parameters (/roles?include=users:order(name|asc)). You can read more about including resources in the documentation.

But, what if the user didn’t have any roles attached? It will halt with an error because it was expecting valid data instead of null. Let’s make it delete the relation from the response instead of showing it with a null value.

// app/Transformers/UserTransformer.php

class UserTransformer extends TransformerAbstract
{
    protected $availableIncludes = [
        'roles'
    ];

    public function transform(User $user)
    {
        return [
            'name' => $user->name,
            'email' => $user->email
        ];
    }

    public function includeRoles(User $user)
    {
        if (!$user->role) {
            return null;
        }

        return $this->collection($user->roles, App::make(RoleTransformer::class));
    }
}

Eager Loading

Because Eloquent will lazy load models when accessing them, we may encounter the n+1 problem. This can be solved by eager loading relations at once to optimize the query.

class UsersController extends Controller
{

    // ...

    public function index(Request $request)
    {
        $this->fractal->parseIncludes($request->get('include', '')); // parse includes

        $usersQueryBuilder = User::query();
        $usersQueryBuilder = $this->eagerLoadIncludes($request, $usersQueryBuilder);
        $usersPaginator = $usersQueryBuilder->paginate(10);

        $users = new Collection($usersPaginator->items(), $this->userTransformer);
        $users->setPaginator(new IlluminatePaginatorAdapter($usersPaginator));
        $users = $this->fractal->createData($users); // Transform data

        return $users->toArray(); // Get transformed array of data
    }

    protected function eagerLoadIncludes(Request $request, Builder $query)
    {
        $requestedIncludes = $this->fractal->getRequestedIncludes();

        if (in_array('role', $requestedIncludes)) {
            $query->with('role');
        }

        return $query;
    }
}

This way, we won’t have any extra queries when accessing model relations.

Conclusion

I came across Fractal while reading the Building APIs you won’t hate by Phil Sturgeon, which was a great and informative read that I wholeheartedly recommend.

Do you use transformers when building your API? Do you have any preferred package that does the same job, or do you just dump the json_encodes? Let us know in the comments section below!

  • I love Fractal! But I do think that it’s API could be a bit more developer friendly. That’s why I created a wrapper package around it that makes working with Fractal a breeze. It’s called Fractalistic: https://github.com/spatie/fractalistic

    If you’re using Laravel check out the laravel-fractal package that contains everything from Fractalistic + a few Laravel specific goodies: https://github.com/spatie/laravel-fractal

    • younesrafie

      Nice work on the package (y) I think the problem is not that Fractal is not developer friendly, it’s the developers that tend to repeat things! I you think about it for a second, you’ll see that you can extract all the repetitive parts and put them in the parent controller. The only thing that should be changed (in my opinion) is how you include extra resources.

  • Daniel Jurkovic

    The Idea behind fractal is what helped us build a flexible, easy to extend and extremely performant API, not fractal itself. We have built our own layer that maps the incoming request to one sql query for every collection that occurs in the result and transforms the sql result to the api output. Includes are mapped to functions with query builder instructions like you do it. The same is done for filters and orders. Every function can return a set of directives that are applied to fetched sql data enabling independency from the underlying data structure. Skipping your object model and directly mapping to sql can bring up a powerful api out of a simple db structure. I wonder about your approaches on providing users with an includable numberOfRoles property without the need of having the numberOfRoles stored in the user table.

    • younesrafie

      Can you share a Gist with code samples? I’m really keen to see how you mapped requests -> DB -> Transformers
      As for your question, I guess it depends on your models. You can easily map it to a function to include extra properties.

Recommended
Sponsors
Get the latest in PHP, once a week, for free.