Adam Anderly

Husband, Father, Developer

  • About
  • Blog
  • Archives
  • Plugins
    • WooCommerce MailChimp
    • Wistia Responsive
  • Contact

Copyright © 2025
Adam Anderly ยท Log in

Laravel Transformable – An Eloquent Model Trait for Consumable Models

September 8, 2016 By Adam 1 Comment

In working on a new Laravel-based REST API over an existing database, I quickly realized the need to shield API consumers from my underlying database structure, especially since I knew the structure would be changing. This is a common concern in API design and many times I would turn to creating DTOs (Data Transfer Objects) or transformer classes using either a scaled-down Transformer pattern or the excellent Fractal package. However, for this project time was of the essence so I needed a very quick way to simply provide a transformation map without having to create new transformer classes for each model and without having to update the visible and appends attributes on every model.

Hence, the Transformable trait was born. It’s a simple PHP trait that you drop into your model and then override the $transformation_map property to specify your consumable model attributes.

Here’s an example:

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use App\Traits\Transformable;

class Group extends Model
{
    use Transformable;

    protected $transformation_map = [
        'Group_ID' => 'id',
        'Name' => 'name',
        'PublicGroup' => 'public',
    ];
}

A quick explanation: we’re importing the Transformable trait (which I’ll cover shortly) and then we’re providing a database column name-to-consumable attribute name mapping. That’s it.

A consumer of this model will not see the underlying database column names on the left hand of the mapping such as “Group_ID”, “Name”, or “PublicGroup.” Instead, they’ll see the names on the right hand side: “id”, “name” and “public.” In essence, it’s a simple transformer. Here’s an example usage:

use App\Models\Group;

class GroupsController
{
    public function index()
    {
        return Group::all();
    }
}

With a call to the above GroupsController@index method, we’d end up with the following JSON:

[
    {
        "id": 1,
        "name": "Group 1",
        "public": "0"
    },
    {
        "id": 2,
        "name": "Group 2",
        "public": "0"
    }
}

Additionally, we can create the model specifying the consumable attribute names (not the database column names):

$group = new App\Models\Group(['name' => 'Group 3', 'public' => true]);

// Calling this
$group->toJson();

// Will yield this
{
    "id": 3,
    "name": "Group 3",
    "public":"1"
}

The magic of this is in the Transformable trait. Normally, to achieve this, we’d have to specify the $visible and $appends properties on our model and provide get and set mutators for each attribute mapping. Instead, with Transformable, we simply provide the $transformation_map and the Transformable trait takes care of the rest.

It does this by overriding several methods of the Eloquent model that determine if get and set mutators exist to include checking the $transformation_map property. This is how it can return you an instance of your model as well as create an instance of your model with the consumable attribute names without you needing to specify the source database column names.

I’m working on an enhancement to Transformable to allow queries (i.e. where* methods) on the models so that you can query with the consumable attribute names and not the database column names. As soon as I wrap up those changes, I’ll look at making this a composer package.

Here’s the entire trait implementation:

namespace App\Traits;

use Illuminate\Support\Str;

Trait Transformable
{
	protected $transformation_map = [];

    /**
     * Determine if a get mutator exists for an attribute.
     *
     * @param  string  $key
     * @return bool
     */
    public function hasGetMutator($key)
    {
        return $this->keyExistsInTransformationMap($key) || parent::hasGetMutator($key);
    }

    /**
     * Determine if a set mutator exists for an attribute.
     *
     * @param  string  $key
     * @return bool
     */
    public function hasSetMutator($key)
    {
        return $this->keyExistsInTransformationMap($key) || parent::hasSetMutator($key);
    }

    /**
     * Get the value of an attribute using its mutator.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @return mixed
     */
    protected function mutateAttribute($key, $value)
    {
        if ($this->keyExistsInTransformationMap($key)) {
            return $this->{$this->getTransformationMapInverse()[$key]};
        }
        return parent::mutateAttribute($key, $value);
    }

    /**
     * Set a given attribute on the model.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @return $this
     */
    public function setAttribute($key, $value)
    {
        if ($this->keyExistsInTransformationMap($key)) {
            if (method_exists($this, 'set'.Str::studly($key).'Attribute')) {
                parent::setAttribute($key, $value);
            }
            $key = $this->getTransformationMapInverse()[$key];
        }
        return parent::setAttribute($key, $value);
    }

    public function keyExistsInTransformationMap($key)
    {
        return array_key_exists($key, $this->getTransformationMapInverse());
    }

    public function getTransformationMapInverse()
    {
        return array_flip($this->transformation_map);
    }
}

This could certainly be enhanced further to support casting to/from datatypes and supporting compound properties via a Closure or similar.

Let me know what you think in the comments.

Share this:

  • Twitter
  • LinkedIn
  • Email
  • Print
  • More
  • Reddit

Filed Under: Laravel, PHP, REST Tagged With: APIs, Laravel, PHP, REST

RSS

RSS Feed

Subscribe

Enter your email address to subscribe and receive new posts by email.

Find It Here

Top Posts

  • Cross-Cutting Concerns with MediatR Pipeline Behaviors
  • Laravel User Timezone Aware Trait
  • Useful Laravel Model Traits: Gravatar
  • Laravel Transformable - An Eloquent Model Trait for Consumable Models
  • Gravity Forms + Microsoft Dynamics CRM
  • DNS from the command line: dnsimple-cli for Node.js
  • WooCommerce MailChimp 1.2 Released
  • WooCommerce MailChimp Update
  • Purchase Confirmation
  • Transaction Failed

Recent Posts

  • Cross-Cutting Concerns with MediatR Pipeline Behaviors
  • Laravel User Timezone Aware Trait
  • Useful Laravel Model Traits: Gravatar
  • Laravel Transformable – An Eloquent Model Trait for Consumable Models
  • Gravity Forms + Microsoft Dynamics CRM

Categories

  • .NET (12)
  • ASP.NET Core (1)
  • ASP.NET MVC (7)
  • Dependency Injection (3)
  • Laravel (3)
  • Laravel Traits (2)
  • MediatR (1)
  • Node.js (2)
  • NuGet (5)
  • PHP (2)
  • REST (4)
  • Silverlight (5)
  • Uncategorized (3)
  • WordPress (7)
  • WordPress Plugins (7)

Tags

.NET APIs ASP.NET Core ASP.NET MVC Azure Caching cli CRM Dependency Injection dns dnsimple Dynamics Dynamics CRM Expansive Fallback Gravity Forms iPhone Laravel Laravel Traits MailChimp MediatR Microsoft Dynamics CRM MobileMe Node.js NuGet PHP Polly REST Retry Rounding Scrutor Silverlight SimpleMembership sublime-text-2 VS2010 Wistia WooCommerce WordPress WordPress Plugins
  • About
  • Blog
  • Archives
  • Plugins
    • WooCommerce MailChimp
    • Wistia Responsive
  • Contact
loading Cancel
Post was not sent - check your email addresses!
Email check failed, please try again
Sorry, your blog cannot share posts by email.