Transformers Layer with league/fractal
In This Document
Introduction
In Laravel development, the Transformers layer plays a crucial role in shaping data for API responses. The league/fractal package is widely adopted for this purpose, offering a flexible and powerful toolset to transform raw data into a consistent and well-structured format suitable for API consumers.
Transformers are classes that convert your internal data structures into standardized API responses, ensuring consistency and proper formatting across your entire API.
Why Transformers?
Transformers help decouple the data retrieval process from the API response formatting. By employing Transformers, you can ensure that the data returned by your API is presented in a standardized way, promoting consistency and simplifying the maintenance of your codebase.
Installation of league/fractal
To get started with league/fractal, you need to install the package using Composer:
composer require league/fractal
Once installed, you can create Transformers to define how your data should be represented in API responses.
Implementation Guide
- Creating Transformers
- Using Transformers
- Using Fractal Helper
- Including Relationships
Creating Transformers
Transformers are classes responsible for converting raw data into a specific structure for API responses. In Laravel, Transformers often extend the League\Fractal\TransformerAbstract class.
Basic Transformer
<?php
namespace App\Transformers;
use App\Models\User;
use League\Fractal\TransformerAbstract;
class UserTransformer extends TransformerAbstract
{
public function transform(User $user)
{
return [
'id' => (int) $user->id,
'name' => $user->name,
'email' => $user->email,
'created_at' => $user->created_at->toIso8601String(),
'updated_at' => $user->updated_at->toIso8601String(),
];
}
}
Transformer with Relationships
<?php
namespace App\Transformers;
use App\Models\Post;
use League\Fractal\TransformerAbstract;
class PostTransformer extends TransformerAbstract
{
protected $availableIncludes = [
'author', 'comments'
];
public function transform(Post $post)
{
return [
'id' => (int) $post->id,
'title' => $post->title,
'content' => $post->content,
'slug' => $post->slug,
'published' => (bool) $post->published,
'created_at' => $post->created_at->toIso8601String(),
'updated_at' => $post->updated_at->toIso8601String(),
];
}
public function includeAuthor(Post $post)
{
return $this->item($post->author, new UserTransformer());
}
public function includeComments(Post $post)
{
return $this->collection($post->comments, new CommentTransformer());
}
}
Utilizing Transformers in Controllers
Once you have created your Transformers, you can use them in your controllers to format data before sending it as an API response.
Using with Single Item
<?php
namespace App\Http\Controllers;
use App\Models\User;
use App\Transformers\UserTransformer;
use League\Fractal\Resource\Item;
use League\Fractal\Manager;
class UserController extends Controller
{
public function show($id)
{
$user = User::findOrFail($id);
$transformer = new UserTransformer();
$resource = new Item($user, $transformer);
$fractal = new Manager();
$data = $fractal->createData($resource)->toArray();
return response()->json($data);
}
}
Using with Collections
<?php
namespace App\Http\Controllers;
use App\Models\User;
use App\Transformers\UserTransformer;
use League\Fractal\Resource\Collection;
use League\Fractal\Manager;
class UserController extends Controller
{
public function index()
{
$users = User::all();
$transformer = new UserTransformer();
$resource = new Collection($users, $transformer);
$fractal = new Manager();
$data = $fractal->createData($resource)->toArray();
return response()->json($data);
}
}
Using the Fractal Helper
Laravel provides a convenient way to use Fractal with a helper function or a facade.
Installing the Helper
composer require spatie/laravel-fractal
Publishing the Config
php artisan vendor:publish --provider="Spatie\Fractal\FractalServiceProvider"
Using the Helper in Controllers
<?php
namespace App\Http\Controllers;
use App\Models\User;
use App\Transformers\UserTransformer;
class UserController extends Controller
{
public function show($id)
{
$user = User::findOrFail($id);
return fractal()
->item($user)
->transformWith(new UserTransformer())
->toArray();
}
public function index()
{
$users = User::all();
return fractal()
->collection($users)
->transformWith(new UserTransformer())
->toArray();
}
}
Including Relationships
Fractal allows you to include related resources in your API responses.
Controller with Includes
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use App\Transformers\PostTransformer;
use Illuminate\Http\Request;
class PostController extends Controller
{
public function show(Request $request, $id)
{
$post = Post::findOrFail($id);
// Get includes from the request
$includes = $request->get('include', '');
return fractal()
->item($post)
->transformWith(new PostTransformer())
->parseIncludes($includes)
->toArray();
}
}
Example API Request with Includes
GET /api/posts/1?include=author,comments
This will include the author and comments relationships in the response.
Advanced Features
Pagination
Fractal provides support for paginated results:
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use App\Transformers\PostTransformer;
use Illuminate\Http\Request;
class PostController extends Controller
{
public function index(Request $request)
{
$paginator = Post::paginate(10);
$posts = $paginator->getCollection();
$response = fractal()
->collection($posts)
->transformWith(new PostTransformer())
->paginateWith(new \League\Fractal\Pagination\IlluminatePaginatorAdapter($paginator))
->toArray();
return response()->json($response);
}
}
Custom Serializers
Fractal allows you to customize the structure of your API responses using serializers:
<?php
namespace App\Serializers;
use League\Fractal\Serializer\ArraySerializer;
class CustomSerializer extends ArraySerializer
{
public function collection($resourceKey, array $data)
{
return $resourceKey ? [$resourceKey => $data] : $data;
}
public function item($resourceKey, array $data)
{
return $resourceKey ? [$resourceKey => $data] : $data;
}
}
return fractal()
->item($user)
->transformWith(new UserTransformer())
->serializeWith(new CustomSerializer())
->toArray();
Conditional Attributes
You can conditionally include attributes in your transformers:
<?php
namespace App\Transformers;
use App\Models\User;
use League\Fractal\TransformerAbstract;
class UserTransformer extends TransformerAbstract
{
public function transform(User $user)
{
$data = [
'id' => (int) $user->id,
'name' => $user->name,
'email' => $user->email,
'created_at' => $user->created_at->toIso8601String(),
];
// Only include admin status for admin users
if ($user->isAdmin()) {
$data['is_admin'] = true;
}
// Only include private data if the authenticated user is viewing their own profile
if (auth()->id() === $user->id) {
$data['phone'] = $user->phone;
$data['address'] = $user->address;
}
return $data;
}
}
Best Practices
- Consistency
- Performance
- API Versioning
Maintain Consistency
- Use the same format for all API responses
- Keep date formats consistent (ISO 8601 is recommended)
- Use consistent naming conventions (camelCase or snake_case)
- Always cast IDs and boolean values to their proper types
return [
'id' => (int) $model->id,
'is_active' => (bool) $model->is_active,
'created_at' => $model->created_at->toIso8601String(),
];
Optimize Performance
- Use eager loading to avoid N+1 query problems
- Consider caching transformed data for frequently accessed resources
- Be mindful of deeply nested includes
$posts = Post::with(['author', 'comments'])->paginate(10);
Version Your Transformers
- Create separate transformers for different API versions
- Use namespaces to organize transformers by version
// app/Transformers/V1/UserTransformer.php
namespace App\Transformers\V1;
// app/Transformers/V2/UserTransformer.php
namespace App\Transformers\V2;
Conclusion
Implementing the Transformers layer using league/fractal in Laravel contributes to a cleaner and more maintainable codebase. By separating the concerns of data transformation from the rest of your application logic, you ensure that your API responses are consistent and easily adaptable to future changes.
Remember to explore the advanced features of league/fractal to handle more complex scenarios in your API development, such as includes, pagination, and custom serializers.