php-open-source-saver/fractal
Fractal is a maintained fork of thephpleague/fractal for transforming complex data into consistent API output. Provides a presentation layer with transformers, type casting, relationship includes, custom serializers, and pagination support for JSON/YAML APIs.
## Getting Started
### Minimal Setup
1. **Install the Package**:
```bash
composer require php-open-source-saver/fractal
Replace League\Fractal with PHPOpenSourceSaver\Fractal in your codebase.
First Use Case: Transform a Single Model
Create a transformer for your Eloquent model (e.g., User):
use PHPOpenSourceSaver\Fractal\TransformerAbstract;
class UserTransformer extends TransformerAbstract
{
public function transform($user)
{
return [
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
];
}
}
Create a Resource and Serialize:
use PHPOpenSourceSaver\Fractal\Manager;
use PHPOpenSourceSaver\Fractal\Resource\Item;
$manager = new Manager();
$resource = new Item($user, new UserTransformer());
$serializer = $manager->createData($resource)->toArray();
Output JSON:
return response()->json($serializer);
// Transformer for a single model (e.g., User)
class UserTransformer extends TransformerAbstract
{
public function transform($user)
{
return [
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
'posts_count' => $user->posts()->count(), // Eager-loaded or lazy
];
}
// Include relationships (e.g., posts)
public function includePosts($user)
{
return $this->collection($user->posts, new PostTransformer());
}
}
// Transformer for a collection (e.g., Posts)
class PostTransformer extends TransformerAbstract
{
public function transform($post)
{
return [
'id' => $post->id,
'title' => $post->title,
'content' => $post->content,
];
}
}
// Usage:
$posts = Post::all();
$resource = new Collection($posts, new PostTransformer());
$serializer = $manager->createData($resource)->toArray();
Request relationships via URL (e.g., /users?include=posts.comments):
// In your controller:
$includes = $request->input('include', []);
$resource = new Item($user, new UserTransformer());
$resource->setIncludes($includes);
$serializer = $manager->createData($resource)->toArray();
use PHPOpenSourceSaver\Fractal\Pagination\IlluminatePaginatorAdapter;
$paginator = Post::paginate(10);
$resource = new Collection($paginator->items(), new PostTransformer());
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
$serializer = $manager->createData($resource)->toArray();
For frameworks like Doctrine or Pagerfanta:
use PHPOpenSourceSaver\Fractal\Pagination\DoctrinePaginatorAdapter;
$doctrinePaginator = $entityManager->getRepository(Post::class)->findAll();
$adapter = new DoctrinePaginatorAdapter($doctrinePaginator);
$resource->setPaginator($adapter);
use PHPOpenSourceSaver\Fractal\Serializer\JsonApiSerializer;
$serializer = new JsonApiSerializer();
$manager->setSerializer($serializer);
$output = $manager->createData($resource)->toArray();
Output:
{
"data": {
"type": "users",
"id": "1",
"attributes": {
"name": "John Doe",
"email": "john@example.com"
},
"relationships": {
"posts": {
"data": [{"type": "posts", "id": "1"}]
}
}
}
}
use PHPOpenSourceSaver\Fractal\Serializer\ArraySerializer;
$serializer = new ArraySerializer();
$manager->setSerializer($serializer);
$output = $manager->createData($resource)->toArray();
Output:
{
"data": {
"id": 1,
"name": "John Doe",
"email": "john@example.com",
"posts": [
{"id": 1, "title": "First Post"}
]
}
}
Limit fields returned via ?fields[users]=name,email:
// In your transformer:
public function getAvailableIncludes()
{
return ['posts'];
}
public function getDefaultIncludes()
{
return [];
}
public function getAvailableFields()
{
return ['id', 'name', 'email']; // Only these fields can be requested
}
// In your controller:
$fields = $request->input('fields.users', ['id', 'name', 'email']);
$resource = new Item($user, new UserTransformer());
$resource->setFields($fields);
$serializer = $manager->createData($resource)->toArray();
Add metadata to responses:
$resource = new Item($user, new UserTransformer());
$resource->setMeta([
'custom_field' => 'value',
'timestamp' => now()->toIso8601String(),
]);
Override serializer links (e.g., for HAL):
$serializer = new ArraySerializer();
$serializer->setLinks([
'self' => url()->current(),
'next' => $resource->getPaginator()->getNextPageUrl(),
]);
Namespace Mismatch:
Class 'League\Fractal\Manager' not found.League\Fractal with PHPOpenSourceSaver\Fractal (see Migration Guide).Circular References:
User->posts->user).->limit() or ->take() in relationships or implement a visited array in transformers:
public function includePosts($user, $visited = [])
{
if (isset($visited[$user->id])) return null;
$visited[$user->id] = true;
return $this->collection($user->posts, new PostTransformer(), $visited);
}
Pagination Meta Conflicts:
links or meta keys).setMeta() after setting the paginator:
$resource->setPaginator($adapter);
$resource->setMeta(['custom' => 'value'], 'pagination'); // Target specific meta group
Fieldset Parsing:
parseFieldsets() failing with malformed input.$fields = explode(',', $request->input('fields.users', ''));
$resource->setFields(array_filter($fields));
JSON:API ID Uniqueness:
post_id in comments).getRouteKey() or getKey() returns a unique identifier.Inspect Transformed Data:
Use toArray() before serialization to debug:
$data = $manager->createData($resource)->toArray();
dd($data); // Debug the output
Log Includes:
Add logging to parseIncludes() to verify relationship requests:
$includes = $manager->parseIncludes($request->input('include'));
\Log::debug('Includes parsed:', $includes->all());
Serializer-Specific Quirks:
type and id are always present in nested resources.ArraySerializer::setPreserveKeys(true) if needed.Performance:
How can I help you explore Laravel packages today?