rexlabs/smokescreen
Smokescreen provides configurable redaction for Laravel apps, helping keep passwords, tokens, and other sensitive values out of logs and exception reports. Define what to mask and reduce accidental data exposure during debugging.
Smokescreen is a Fractal-style transformation layer: you write a transformer class per resource, and the library turns models/arrays into clean, serializable output. It sits between your models and your JSON responses, so controllers stay thin and API shape stays consistent.
Minimal setup:
composer require rexlabs/smokescreen
Look at these four things first, in this order:
Transformer/AbstractTransformer — the base class you extend.Resource/Item and Resource/Collection — what a transformer returns when nested.Serializer/DefaultSerializer or equivalent) — controls the final envelope.First use case — a single transformer for one endpoint:
use Rexlabs\Smokescreen\Transformer\AbstractTransformer;
class UserTransformer extends AbstractTransformer
{
public function transform(User $user): array
{
return [
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
];
}
}
Then transform one item and a collection through the manager, and toArray()/toJson() the result. Once that works, add one includeX() method for a relation — that is the 80% use case for this package.
One transformer per resource, transform() returns a plain array. No queries, no formatting side effects, no conditionals on the request inside transform(). Anything request-dependent belongs in includes or in the controller.
Relationships via includes. Define $availableIncludes (whitelist) and $defaultIncludes (always rendered), and implement includeAuthor() style methods that return nested resources:
protected $availableIncludes = ['posts', 'company'];
public function includePosts(User $user)
{
return $this->collection($user->posts, new PostTransformer());
}
public function includeCompany(User $user)
{
return $this->item($user->company, new CompanyTransformer());
}
Drive includes from the query string, but whitelist them. Parse an ?include=posts,company parameter through the manager, and rely on $availableIncludes to reject anything unknown. Never let a client request an include that isn't declared, or you have an unbounded query surface.
Compose, don't duplicate. Nested $this->item(...) / $this->collection(...) calls are how you get consistent shapes across endpoints — the same PostTransformer used standalone and inside UserTransformer. Keep transformers reusable and dependency-free (constructor injection only for services like URL generators or formatters).
Wrap the manager as a singleton in Laravel. Bind the manager + chosen serializer once in a service provider, resolve it in a base controller or an ApiResponse helper, and keep transformers as ordinary classes. Typical controller:
return response()->json(
$smokescreen->item($user, new UserTransformer())->toArray()
);
Paginate before transforming. Run the query with paginate(), hand the paginator to the manager/adapter, and transform the page's items. Transforming then slicing means you build every row's array for nothing.
Test transformers directly. They're pure functions of a model — instantiate the transformer, pass a factory-built model, assert the array with assertSame. This catches accidental schema drift faster than HTTP tests and needs no database round trip for simple cases.
Use a consistent envelope decision early. Default serializer vs. a data-wrapped serializer is a one-way door for your frontend. Pick once, document it, and route every endpoint through the same serializer instance.
with(['posts', 'company']) on the query before transforming, or you get N+1s that only appear when a client asks for includes. Add a Model::preventLazyLoading() in local/CI to catch it.$defaultIncludes are viral. They apply to every use of that transformer, including nested ones, so a "small" default include can balloon a collection response. Prefer $availableIncludes and opt in per endpoint.$this inside transform(); it will leak between rows. Pass values through return arrays or method arguments.includeCompany() on a user with no company still runs; return $this->item(null, ...) (or a null-safe check) rather than letting the transformer call methods on null. Decide now whether absent relations serialize as null or are omitted, and stay consistent.keyBy), which can serialize as a JSON object instead of an array. Reindex with values() before handing a collection to the transformer.meta/links come from the paginator adapter or you add them at the controller level. Don't try to inject pagination info from inside transform().->toArray() on the item/collection before any HTTP layer mangles it — most "missing field" bugs are a serializer envelope or an include that silently didn't resolve.transform() side-effect free. Anything that writes, dispatches events, or hits the network makes collections non-deterministic and slows down list endpoints by an order of magnitude. Do that work in the controller/service, and let the transformer only shape data.How can I help you explore Laravel packages today?