kissifrot/apai-io-bundle
Symfony bundle for integrating the apai.io API into your project. It wires up the client services and configuration needed to call apai.io endpoints from your application, keeping setup minimal and the integration clean and reusable across environments.
composer require <vendor>/<package> — Laravel auto-discovery registers the service provider; no manual wiring needed on 5.5+.php artisan vendor:publish --tag=<package>-config if you need to change default wrapping, serializer, or include-key behaviour. Most projects never touch this file.config/<package>.php (defaults), then the base Transformer / AbstractTransformer class in src/ — that is the contract every transformer you write inherits.// app/Transformers/UserTransformer.php
class UserTransformer extends Transformer
{
public function transform(User $user): array
{
return [
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
];
}
}
// Controller
return response()->json(
(new UserTransformer())->transform($user)
);
Once that works, add a collection call and a nested relation — those two steps cover 80% of real usage.
One transformer per model, one key per relation. Keep transform() returning a flat array of scalars plus explicit nested transformer calls. Resist returning $user->toArray() — the whole point is a stable, hand-curated contract that survives column additions.
Inject via the constructor now that 2.0.2 lands. Transformers are resolved through the container, so constructor dependencies work:
class OrderTransformer extends Transformer
{
public function __construct(private PriceFormatter $prices) {}
}
Use this for formatters, signed-URL generators, and current-user-aware policies rather than reaching for app() or Auth::user() inside transform().
Compose, don't copy. Have a DefaultTransformer return the fields every endpoint needs, and extend it for detail views. Override only the keys that differ; call parent::transform($model) and merge.
Eager-load in the query, transform in the transformer. The transformer is the wrong place to decide what to load. In the controller/repository: User::with(['posts.comments'])->paginate(). The transformer should only read relations.
Conditional includes. If the package supports an include/sparse-fieldset mechanism, gate optional relations on it instead of shipping three near-identical transformers:
if ($this->shouldInclude('posts')) {
$data['posts'] = (new PostTransformer())->transformCollection($user->posts);
}
Wrap once, at the edge. Transform in controllers, API Resource classes, or a dedicated response macro — not inside models, accessors, or events. Queued jobs that need the same shape should call the same transformer directly, which keeps email payloads and API payloads in sync.
Testing pattern. Assert on the transformed array, not the HTTP response: $this->assertSame($expected, (new UserTransformer())->transform($user)). Reserve HTTP tests for status codes and wrapping/meta concerns.
__construct() to a transformer would fail resolution or silently drop your dependencies — workaround there is method injection or resolving inside transform(). Bump to 2.0.2 before adopting DI.transform() again produces nested, confusing output. Keep the boundary crisp: raw model in, array out, exactly once.$user->profile->avatar explodes when profile is null. Guard with optional() or an explicit ?-> plus a null key, and decide deliberately whether a missing relation yields null or is omitted.->toArray() passthrough will eventually leak a password hash or internal flag. Add $hidden as belt-and-braces even when transforming manually.$model->created_at is a Carbon instance — serialise with ->toIso8601String() explicitly so the shape doesn't shift when someone adds a $casts entry.Model::preventLazyLoading() in non-production.dd() inside transform() before the response layer. Half of all "the transformer is broken" reports are actually middleware or a resource wrapper reshaping the array afterwards.How can I help you explore Laravel packages today?