#
You're reading a compendium of Rizqi’s misadventures in design & coding.

Permalink

Simple Transformers for JSON in Laravel

Stop relying on $hidden — build real, but easy, transformers for your Eloquent models.

You know the drill when you have an Eloquent model to turn to JSON. When you’ve got a User

class User {
    protected $fillable = ['name', 'email', 'password'];
    protected $hidden = ['password'];

    public function messages() {
        return $this->hasMany(Messages::class);
    }

}

And just want to convert it to JSON in a controller…

$user = User::find(1);
return $user;

Woo, instant JSON! But wait, we’re seeing the user’s email. This won’t do — regular users shouldn’t be allowed to see other peoples’ email. Let’s get rid of it! Easy, right? We just use Eloquent’s handy $hidden property.

class User {
    protected $fillable = ['name', 'email', 'password'];
    protected $hidden = ['email', 'password']; 
    /* ... */
}

And there we go. Job well done, let’s call it a day.

(Your phone rings ominously.)

“Hello?”

“Hi. We need a new user management page for admins, that displays users’ full profile, messages and join dates.”

Okay, so sometimes the email field visible, and sometimes it’s not, you think to yourself. Hmm, maybe there’s a setHidden() method? But what about messages? Messages have a pair of recipients and a body. If we just print out all the messages…


{
    'name': 'Bob',
    'email': 'bob@acme.com',
    'messages': [
        {
            'body': 'Hi alice!',
            'to': 'Alice'
        }
    ]
}

That would be ideal. But how do we format a Message so it intelligently sets the to field? Maybe the $appends property, and an accessor? But we only want this format when it’s embedded in a User — the JSON for a message on its own should include both recipients.

What now?! There’s a lot of logic here, and manipulating the model to change how it’s formatted just doesn’t sound right.

Build your own transformer!

“Transformer” is a big word. In reality, it’s this simple:

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

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

And then you’d use it as such:

$user = User::find(1);
return (new UserTransformer)->transformWithEmail($user);

Of course, that class needs to be autoloaded — look up PSR-4 autoloading in Laravel if you’re new to it.

So there you have it. You have full control over what format your data goes out in, and you’re not wrangling with Eloquent’s attributes and methods. Let’s flesh it out some more.

class MessageTransformer {
    // used for regular message JSON
    public function transform(Message $message) {
        return [
            'body' => $message->body,
            'to' => $message->recipient->name,
            'from' => $message->sender->name,
        ];
    }

    // used for messages when being included under a user
    public function transformWithUserContext(Message $message, User $user) {
        $response = [
            'body' => $message->body,
        ];

        if ($message->sender_id == $user) {
            $response['to'] = $message->recipient->name;
        } else {
            $response['from'] = $message->sender->name;
        }

        return $response;
    }

}

class UserTransformer {
    /* ... */

    public function transformWithEmail(User $user) {

        $messages = $user->messages;
        $messageTransformer = new MessageTransformer;
        $messages = $messages->map(function($v) use ($messageTransformer){
            return $messageTransformer->transformWithUserContext($v, $user);
        });

        return [
            'name' => $user->name,
            'email' => $user->email,
            'messages' => $messages
        ];
    }
}

Full freedom! We can build as many transformer classes and methods as we wish. Each of them can build JSON responses in any form, with any logic as they so desire. These can be as flexible or as rigid as needed — in this example, I hardcoded a new MessageTransformer into the UserTransformer — that can also be injected with constructor injection, and the controller would also inject the user transformer as well.

A related message: don’t be tempted to seek out a “ready-to-use” package every time a problem like this appears! There are several transformer packages out there, with various levels of complexity and feature lists. But you can really do these on your own just fine — be careful with spending more time trying to learn a rigid package than you would’ve spent doing it vanilla. Pick up a package when you’re sure you need it.

How you fully set up and organize the transformer classes are up to you. Do what’s logical. Don’t try to shoehorn the framework into fitting your needs, build the tools to adapt it instead!