Transformers
Transformers, often referred to as Response Transformers, serve a similar purpose to Views, but specifically for JSON responses. While Views are responsible for presenting data in HTML format, Transformers take data and format it into JSON representation.
For more detailed information about transformers and their usage, you can refer to the official documentation of Fractal, which is the underlying library used for handling transformations in Apiato.
To generate new transformers,
you may use the apiato:make:transformer interactive command:
php artisan apiato:make:transformer
Definition and Principlesβ
Read Porto SAP Documentation (#Transformers).
Rulesβ
- All Transformers:
- MUST be placed in the
app/Containers/{Section}/{Container}/UI/API/Transformersdirectory. - MUST extend the
App\Ship\Parents\Transformers\Transformerclass.- The parent extension SHOULD be aliased as
ParentTransformer.
- The parent extension SHOULD be aliased as
- MUST have a public
transformmethod returning an array.
- MUST be placed in the
Folder Structureβ
app
βββ Containers
βββ Section
βββ Container
βββ UI
βββ API
βββ Transformers
βββ TransformerA.php
βββ TransformerB.php
βββ ...
Code Exampleβ
use App\Ship\Parents\Transformers\Transformer as ParentTransformer;
class UserTransformer extends ParentTransformer
{
protected $availableIncludes = [];
protected $defaultIncludes = [];
public function transform(User $user)
{
return [
'type' => $user->getResourceKey(),
'id' => $user->getHashedKey(),
'name' => $user->name,
// ...
];
}
}
Including Relationshipsβ
You can include model relationships for complex data structures using the include query parameter.
These relationships can be included in the response either per API consumer request or
by default.
The include parameter can be used on any endpoint
that has relationships defined in its transformer.
Configurationβ
You can configure the includes settings in the config/fractal.php file.
'auto_includes' => [
'enabled' => true,
'request_key' => 'include',
],
Defining Relationshipsβ
To define relationships in the transformer, follow these two steps:
- Define the relationship method in the transformer.
- Add the relationship to the
$availableIncludesor$defaultIncludesproperty.
availableIncludescan be included in the response per API consumer request.defaultIncludesare included in the response by default.
Any relationships not defined in the $availableIncludes or $defaultIncludes properties will be ignored.
The relationship method should be named include{RelationshipName} and return a Fractal Item or Collection object.
The include{RelationshipName} method will be called automatically by the Transformer
when the relationship is requested.
For example, let's assume we have a User model with a roles relationship.
The UserTransformer would have an includeRoles method that returns a Collection object.
public function includeRoles(User $user): Collection
{
return $this->collection($user->roles, new RoleTransformer());
}
Now,
the roles relationship can be included in the response
by adding it to the $availableIncludes or $defaultIncludes property.
protected array $availableIncludes = [
'roles',
];
// or
protected array $defaultIncludes = [
'roles',
];
Include Per API Consumer Requestβ
In cases where you have multiple relationships for a model, such as User with Roles and Avatar relationships,
you can include specific relationships in the response based on the API consumer's request.
To enable this,
you add the desired relationships to the $availableIncludes property in the transformer
and create corresponding methods for each relationship in the transformer to specify how to include that data.
Here's an example using a UserTransformer with roles and avatar relationships added to the $availableIncludes property:
protected array $availableIncludes = [
'roles',
'avatar',
];
public function includeRoles(User $user): Collection
{
return $this->collection($user->roles, new RoleTransformer());
}
public function includeAvatar(User $user): Item
{
return $this->item($user->avatar, new AvatarTransformer());
}
To request the roles data along with the User resource, you can pass the include=roles query parameter with the request:
api.apiato.test/v1/users?include=roles
This will include the roles data in the response:
{
"data": [
{
"type": "User",
"id": "0one37vjk49rp5ym",
"roles": [
{
"type": "Role",
"id": "bmo7y84xpgeza06k"
}
]
}
]
}
You can also include multiple relationships by separating them with a comma:
api.apiato.test/v1/users?include=roles,avatar
This includes both the roles and avatar relationships in the response.
Nested includes are also possible.
If, for instance, the Avatar model has a relationship with an Image object,
you can request nested includes using dot notation:
api.apiato.test/v1/users?include=avatar,avatar.image
This includes the Avatar relationship with the Image nested under it in the response.
It's important to note that for nested includes, the nested relationship must also be defined.
In this example,
the AvatarTransformer would need
to have an includeImage method defined and the image relationship added to it's $availableIncludes property.
By defining the availableIncludes and implementing the corresponding include{RelationshipName} methods,
you allow API consumers to specify which related data they want in the response,
enhancing the flexibility and efficiency of your API.
Include By Defaultβ
To automatically include a relationship in every response generated by the transformer,
you can define the relationship in the transformer's $defaultIncludes property.
This means
that the specified relationship will be included by default without the need for API consumers to request it explicitly.
Here's an example using a UserTransformer where the avatar relationship is defined as a default include:
protected array $defaultIncludes = [
'avatar',
];
public function includeAvatar(User $user): Item
{
return $this->item($user->avatar, new AvatarTransformer());
}
By setting the default includes in this manner,
the avatar relationship will automatically be included in every response created by this transformer.
This can simplify responses and reduce the need for additional API requests for related data,
ultimately enhancing the efficiency and usability of your API.
Eager Loadingβ
Read more about eager loading in the Repositories section.
Excluding Relationshipsβ
You can exclude relationships from the response by using the exclude query parameter.
For example, if you have a User model with roles and avatar relationships,
you can exclude the roles relationship by passing the exclude=roles query parameter with the request:
api.apiato.test/v1/users?exclude=roles
You can also exclude multiple relationships by separating them with a comma:
api.apiato.test/v1/users?exclude=roles,avatar
This will exclude both the roles and avatar relationships from the response.
Excluding nested relationships is also possible.
If, for instance, the Avatar model has a relationship with an Image object,
you can exclude nested relationships using dot notation:
api.apiato.test/v1/users?exclude=avatar.image
This will exclude the Image relationship nested under the Avatar in the response.
The exclude query parameter takes precedence over the include query parameter and the default includes.
Configurationβ
You can configure the excludes settings in the config/fractal.php file.
'auto_excludes' => [
'enabled' => true,
'request_key' => 'exclude',
],
Helper Methodsβ
nullableItemβ
The nullableItem method returns an item if the model has a specific relationship, otherwise, it returns null.
use League\Fractal\Resource\Item;
use League\Fractal\Resource\Primitive;
public function includeRelation(Model $model): Primitive|Item
{
return $this->nullableItem($model->relation, new RelationTransformer();
}
If $model->relation is not null (meaning it has a related model),
the method returns an item formatted using the specified transformer.
Otherwise, it returns null.
The nullableItem method is a shortcut for the following code:
use League\Fractal\Resource\Item;
use League\Fractal\Resource\Primitive;
public function includeRelation(Model $model): Primitive|Item
{
return $model->relation ? $this->item($model->relation, new RelationTransformer()) : $this->primitive(null)
}
Response Payloadβ
You have the flexibility to define your own custom response payload or utilize one of the supported serializers. Serializer classes let you switch between various output formats with minimal effect on your Transformers.
Current supported serializers:
ArraySerializerDataArraySerializerJsonApiSerializer
To modify the default Fractal serializer,
access the app/Ship/Configs/fractal.php configuration file
and update the default_serializer setting to your preferred serializer.
By default, Apiato uses DataArraySerializer.
This serializer is not to everyoneβs tastes, because it adds a data namespace to the output.
A very basic response of the DataArraySerializer will look like this:
{
"data": {
"type": "User",
"id": "XbPW7awNkzl83LD6",
"name": "Mohammad Alavi"
}
}
The DataArraySerializer is handy because it allows space for meta data
(like pagination, or totals) in both Items and Collections.
{
"data": [ ... ],
"meta": {
"include": [
"xxx",
"yyy"
],
"custom": [],
"pagination": {
"total": 999,
"count": 999,
"per_page": 999,
"current_page": 999,
"total_pages": 999,
"links": {
"next": "http://api.apiato.test/v1/accounts?page=999"
}
}
}
}
For more detailed information, please refer to Fractal and Laravel Fractal Wrapper documentations.