Fractal: a Practical Walkthrough

Tweet

If you ever developed for an API, you might have had troubles with changes of your database schema. If you didn’t happen to have a good implementation, you had to rework your whole model when changing some column names. In this article I will demonstrate how you can use Fractal as a layer between your models and JSON output. This post will show you how this package will make API development easier.

Fractal as a package

Fractal is a project written in PHP, and part of The League of Extraordinary Packages. These packages all comply to several requirements, like usage of PHP-FIG and Unit Test coverage. Fractal is mainly developed by Phil Sturgeon and still receives regular improvements. It can also be used with Composer.

Setting up an environment

For demonstration purposes, I will now set up a framework using Silex and Illuminate/Database (the ORM component of Laravel). It doesn’t matter if you don’t have any experience with one of those. The things I will do are very straightforward and I will explain them as much as I can. If something is unclear do not hesitate to leave a comment.

I will now set up a framework. Note that if you don’t want to follow the steps, you can download all the code at the end of the article. Now start with creating a new folder inside root folder. We’ll start with creating our composer.json file, with all the dependencies that we need. In our case: Silex and Illuminate\Database. Create a composer.json file like this:

    {
        "require": {
            "silex/silex": "~1.2",
            "illuminate/database": "*"
        },
    }

Install the packages with composer install.

The database

I will take the example of an online music database. The database will provide information for several songs: the songname, the artist name, artist website, album name, release date and music label. In the beginning this will all be in one table. If you want to try it out yourself download the file 1.sql from this article’s repository and run it on your database.

The code

In order to use Silex with Illuminate\Database, we’ll need some code. Inside the app folder create a new file index.php. This is where we’ll start Silex, connect to the database and define routes:

    <?php
    require("../vendor/autoload.php");
    
    $app = new Silex\Application();
    
    $app['database'] = require("database.php");
    
    $app->mount('/tracks', include 'controllers/tracks.php');
    
    $app->run();

On the first line we require the autoload file for composer. Then we create a new Silex application and load in Illuminate/Database. We then create a controller for /tracks so that all URLs which start with /tracks will be handled by controllers/tracks.php. The database.php file looks like this, don’t forget to change the connection settings:

    <?php
    use Illuminate\Database\Capsule\Manager as Capsule;
    
    $capsule = new Capsule;
    
    $capsule->addConnection([
        'driver'    => 'mysql',
        'host'      => 'localhost',
        'database'  => 'musicstore',
        'username'  => 'root',
        'password'  => '',
        'charset'   => 'utf8',
        'collation' => 'utf8_unicode_ci',
        'prefix'    => '',
    ]);
    
    
    use Illuminate\Events\Dispatcher;
    use Illuminate\Container\Container;
    $capsule->setEventDispatcher(new Dispatcher(new Container));
    
    $capsule->setAsGlobal();
    
    $capsule->bootEloquent();
    
    return $capsule;

First API version

Initially, the database only contains one table (import 1.sql). This table contains information about the track itself (title, music label), the album (title and release date), and the artist (name and website). Our API will provide two things: when navigating to /tracks, it will output a json list with the track ids, their title and artist. When navigating to /tracks/$id, it will output all the track information as a json object.

Tracklisting

We begin with the list of tracks. Put this code comes into /controllers/tracks.php:

    <?php
    
    $tracks = $app['controllers_factory'];
    
    $tracks->get('/', function() use ($app)
    {
        $tracklist = Track::getTrackList();
        $output = array("data" => $tracklist);
        return json_encode($output);
    });
    
    return $tracks;

and this code for the model, /models/Track.php:

    public static function getTrackList()
    {
        return Track::select('id','name','artist_name')->get();
    }

This uses the Illuminate\Database querybuilder to get a list of id’s, names and articles and then outputs them. The output will have this format:

{
    data: [
        {
            id: 1,
            name: "Song 1",
            artist_name: "Artist 1"
        },
        {
            id: 2,
            name: "Song 2",
            artist_name: "Artist 2"
        }
    ]
}

This works as expected, but if the table schema changes, so will the JSON output. We will now use a Fractal transformer to force a specific structure of output. This Transformer is a class that extends Fractal\TransformerAbstract. Create a new folder for transformers in your app folder with the name transformers. The first transformer is TracklistTransformer.php which we’ll use solely for /tracks, and it looks like this:

    <?php
    namespace Musicstore;
    
    class TracklistTransformer extends \League\Fractal\TransformerAbstract
    {
        public function transform(Track $track)
        {
            return [
                'id' => (int) $track->id,
                'title' => $track->title,
                'artist' => $track->artist_name
            ];
        }
    }

Every transformer requires a transform() method, with its argument being the object you want to transform, in our case an instance of the class Track. In the return statement the structure is defined as an associative array.

We’ll now apply the transformation and create the output array by using a Serializer. We are about to use the DataArraySerializer because this will put our data under the data-key in the JSON object. This allows you to have other information in your response like status codes or error messages. In order to use this serializer, we’ll need an instance of the manager class. I will use the DI container for this so that we don’t have to rewrite boilerplate code every time. The new /controllers/tracks.php file looks like this:

    <?php
    
    $tracks = $app['controllers_factory'];
    
    $tracks->get('/', function() use ($app)
    {
        $tracks = Track::getTrackList();
        $tracklist = new \League\Fractal\Resource\Collection($tracks, new TracklistTransformer);
        $output = $app['serializer']->createData($tracklist)->toArray();
        return json_encode($output);
    });
    
    return $tracks;

We use $tracklist = new \League\Fractal\Resource\Collection($tracks, new TracklistTransformer); to apply the transformation. In this route we are using a collection of tracks: our $tracks variable is an object of the class Illuminate\Database\Eloquent\Collection. This class is compatible with Fractal and its transformers. For the other route we’re going to use a transformer on a single table item. But more on that later.

For using $app['serializer'], place this code in index.php, right under the app['database'] statement:

    $app['serializer'] = $app->share(function()
    {
        $manager = new \League\Fractal\Manager();
        $manager->setSerializer(new League\Fractal\Serializer\DataArraySerializer());
        return $manager;
    });

Now you have a working tracklisting.

Track information

I will now quickly demonstrate how to show information about specific tracks and then make edits to the data schema with Fractal. Open controllers/tracks.php and add the following code above the return statement:

    <?php
    $tracks->get('/{id}', function($id) use ($app)
    {
        return Track::find($id);
    });

Track::find($id) returns an object of class Track with its id matching the URL. Again this works but it’s not a good way, so we will implement Fractal. Create a new transformer transformers/TrackTransformer.php:

    <?php
    namespace Musicstore;
    
    class TrackTransformer extends \League\Fractal\TransformerAbstract
    {
        public function transform(Track $track)
        {
            return [
                'id' => (int) $track->id,
                'title' => $track->title,
                'artist_name' => $track->artist_name,
                'artist_website' => $track->artist_website,
                'album_name' => $track->album_name,
                'album_release' => $track->album_release,
                'album_label' => $track->album_label
            ];
        }
    }

In your controller edit the route to this:

    $tracks->get('/{id}', function($id) use ($app)
    {
        $track = Track::find($id);
        $track = new \League\Fractal\Resource\Item($track, new TrackTransformer);
        $output = $app['serializer']->createData($track)->toArray();
        return json_encode($output);
    });

As you can see it is rather similar to the previous route, except that in this case we’re not dealing with a Collection but a single Item. If you are using 1.sql you can navigate to localhost/app/tracks/1 and see some sample data.

Updated API version

Our music store wants to expand and provide more information about the artists and the albums. In the new database schema there are separate tables for the artists and the albums. You can download 2.sql to use the new schema. The tables look like this:

TRACKS:

  • id
  • title
  • artist_id
  • album_id

ARTISTS:

  • id
  • name
  • website

ALBUMS:

  • id
  • name
  • release
  • label

In the tracks table, the artist and album columns are integers which correspond to rows in the respective tables. We’ll use the tranformers to keep our output structure the same. In order to make things easier we’ll use some features of Illuminate\Database. Go inside models/Track.php and add two more methods:

    public function artist()
    {
        return $this->belongsTo('\Musicstore\Artist');
    }
    
    public function album()
    {
        return $this->belongsTo('\Musicstore\Album');
    }

If we have a Track object we can access its Album and Artist by using $track->album and $track->artist. First we need to make those models so create two files models/Artist.php and models/Album.php:

    // models/Artist.php
    <?php
    namespace Musicstore;
    class Artist extends \Illuminate\Database\Eloquent\Model
    {
        public function tracks()
        {
            return $this->hasMany('\Musicstore\Track');
        }
    }
    
    // models/Album.php
    <?php
    namespace Musicstore;
    class Album extends \Illuminate\Database\Eloquent\Model
    {
        public function tracks()
        {
            return $this->hasMany('\Musicstore\Track');
        }
    }

The last thing we have to do now is edit our transformers:

    // transformers/TrackTransformer.php
    return [
        'id' => (int) $track->id,
        'title' => $track->title,
        'artist_name' => $track->artist->name,
        'artist_website' => $track->artist->website,
        'album_name' => $track->album->name,
        'album_release' => $track->album->release,
        'album_label' => $track->album->label
    ];
            
    // transformers/TracklistTransformer.php
    return [
        'id' => (int) $track->id,
        'title' => $track->title,
        'artist' => $track->artist->name
    ];

If you test everything out, you’ll see that it works.

Conclusion

As I’ve demonstrated, you can, by adding Fractal before your output, easily perform database changes, without the end user noticing. You can download the full example code here at my repository. If something is unclear please do not hesitate to leave a comment.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • http://www.velosofy.com/ Michiel De Greef

    Interesting article Alexander!