Skip to main content

Enhanced Resources for Laravel

· 3 min read
Connor Tumbleson
Director of Engineering
Jason Jones
Senior Software Engineer II, Lead

Developing an API was a common task for engineers working with Laravel in the early days of the framework. Prior to version 5.5 Laravel did not offer an elegant way to format/prepare API responses. Historically, it worked by calling toArray() on your model instance.

This pattern of calling toArray() risked the possibility of exposing attributes that should not be exposed over an API. As a result, it was common to use the $hidden property to exclude the specified attributes during serialization which was helpful in building an API response as both a mechanism to avoid exposing sensitive data and more generally controlling which data would be included. The example shown below was a common pattern prior to Laravel 5.5.

<?php

class User extends Eloquent {
protected $hidden = ['password', 'remember_token'];
}

// Usage
$user = User::find(1);
$user->toArray();
// Returns: ['name' => 'John', 'email' => 'john@example.com']

This approach was flexible, but any change to your database schema resulted in those changes appearing in your API. We needed something more concrete and landed on Fractal which served us well until Laravel added official support for API Resources, and thus we worked on an enhancement for that feature.

A stock installation of Laravel leveraging API resources may have a class similar to the one shown below.

<?php

class UserResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
}

A common complaint we had was that an entity (in this case a User) may have a different responses depending on the permissions of the user making the request. An admin may see more information about a user than a support user, or staff member. Laravel offered conditional attributes we could leverage, but those exploded in complexity pretty quickly when balancing many roles and many conditional attributes.

So we built Enhanced Resources to make this is a bit easier.

<?php

use Sourcetoad\EnhancedResources\Formatting\Attributes\Format;
use Sourcetoad\EnhancedResources\Formatting\Attributes\IsDefault;
use Sourcetoad\EnhancedResources\Resource;

class QuestionResource extends Resource
{
#[IsDefault, Format]
public function base(): array
{
return [
'type' => $this->type,
'text' => $this->text,
'answers' => $this->answers,
];
}

#[Format]
public function teacher(): array
{
$data = $this->base();
$data['correct_answer'] = $this->correct_answer;

return $data;
}
}

In this example we had a format that was the default (or base) for usage of the Question model. This example reflected a question schema which may have a question type, the question itself and any possible answers. We know students shouldn't have the answers, but the teacher should. We can easily assume the properties of the base resource and add on multiple conditional properties.

Over time, we found this package to excel when formatting data differently depending on the permissions/roles of the requesting user. In addition to the structured approach that the Resource file had we also supported a variety of mutations directly on the resource.

  • modify - Change a key/value.
  • except - Exclude specific keys.
  • only - Include specific keys only.

This meant we could quickly apply a change if needed without adapting the resource file. We could also apply an attribute directly to the model to bind it to the corresponding Resource that represented it.

use Illuminate\Database\Eloquent\Model;
use Sourcetoad\EnhancedResources\Resourceable\AsResource;
use Sourcetoad\EnhancedResources\Resourceable\ConvertsToResource;

/**
* @method ExampleResource toResource()
*/
#[AsResource(ExampleResource::class)]
class Example extends Model
{
use ConvertsToResource;
}

(new Example)->toResource();

Enhanced Resources has been around since 2019 and is still in use to this day.