Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Transformer Laravel Package

surgiie/transformer

Surgiie/Transformer provides a fluent, declarative API for reshaping arrays and Eloquent data in Laravel. Define reusable blueprints to rename keys, transform values, and format output, keeping controllers and API responses clean, consistent, and easy to maintain.

Deep Wiki
Context7

Getting Started

Install with Composer and let Laravel auto-discover the service provider:

composer require surgiie/transformer

There is nothing to configure for a first pass — the package works out of the box. If you want to tweak defaults (whether keys are transformed, default transformers, rule delimiters), publish the config:

php artisan vendor:publish --tag=transformer-config

Where to look first: the Transformer base class and its $rules property. Everything else (custom methods, validation, key handling) hangs off that one array. Skim the README's list of built-in transformations — that list is the API surface you'll actually use day to day (trim, lower, upper, capitalize, slug, toInt, toFloat, toBool, toArray, toCarbon, stripTags, escape, etc.).

First use case — sanitizing an HTTP payload before persisting:

php artisan make:transformer UserTransformer
namespace App\Transformers;

use Surgiie\Transformer\Transformer;

class UserTransformer extends Transformer
{
    protected $rules = [
        'name'    => 'trim|capitalize',
        'email'   => 'trim|lower',
        'age'     => 'toInt',
        'website' => 'trim|lower',
        'bio'     => 'trim|stripTags',
    ];
}

Then in a controller or form request:

$clean = UserTransformer::transform($request->all());

User::create($clean);

That two-line flow — define rules once, call transform() anywhere — is the whole mental model. You are not validating; you are normalizing. Validation still belongs in a FormRequest.

Implementation Patterns

1. Transformers as a first-class layer between requests and models. The most common pattern is: FormRequest validates the shape, Transformer normalizes the values, Eloquent persists them. Doing it this way keeps controllers tiny and makes the normalization rules testable in isolation:

class StoreUserRequest extends FormRequest
{
    public function rules(): array
    {
        return ['name' => 'required|string', 'email' => 'required|email', 'age' => 'required|integer'];
    }
}

// Controller
public function store(StoreUserRequest $request)
{
    User::create(UserTransformer::transform($request->validated()));
}

2. Reuse transformers for multiple inputs. The same UserTransformer should happily process a request payload, a CSV row from an import job, or an array coming out of a webhook. If you find yourself writing a transformer that only makes sense for one endpoint, you probably wanted a validation rule instead.

3. Custom transformation methods. Any protected method on the transformer class can be referenced as a rule, which is where the package earns its keep:

class UserTransformer extends Transformer
{
    protected $rules = [
        'phone' => 'trim|normalizePhone',
        'name'  => 'trim|titleCase',
    ];

    protected function normalizePhone($value): string
    {
        return preg_replace('/[^\d+]/', '', $value);
    }

    protected function titleCase($value): string
    {
        return mb_convert_case($value, MB_CASE_TITLE);
    }
}

This is the extension point you'll lean on 90% of the time — keep domain-specific normalization (currency, phone, SKUs, slugs with locale rules) here rather than in mutators or observers.

4. Nested data and collections. Dotted rule keys address nested arrays ('address.city' => 'trim|titleCase'), and wildcards work the way you'd expect for arrays of objects ('items.*.sku' => 'trim|upper'). Pair this with transformMany() (or map over a collection) when importing batches:

$rows = collect($csv)->map(fn ($row) => UserTransformer::transform($row));

5. Transform the keys too, when ingesting third-party data. If you're cleaning up a webhook or an external API response, normalizing keys (FirstName → first_name) at the same layer avoids sprinkling Arr::get calls across your app. Enable it via the transformer (check the config key — usually a $transformKeys property or a transformKeys() method) rather than hand-rolling a mapping array.

6. Validation before transformation. Many teams pair validate() on the transformer with transform() so the whole pipeline lives in one class:

$transformer = new UserTransformer($payload);
$transformer->validate();   // throws a ValidationException on bad input
$clean = $transformer->transform();

This is convenient for jobs and commands where there's no FormRequest boundary. For HTTP endpoints, prefer the FormRequest + Transformer split so validation errors still render as 422s automatically.

7. Test transformers like value objects. They have no dependencies, no DB, no HTTP — assert input → output in plain unit tests. This is the cheapest test suite you'll ever write and catches most normalization regressions.

Gotchas and Tips

  • transform() returns the transformed array; it does not mutate the instance in a way you should rely on. Treat each call as input → output. If you're chaining ->transform() multiple times, you're probably doing it wrong.

  • Nulls and missing keys. A rule on a key that isn't present typically leaves it absent rather than throwing. Be explicit with required in your validator if absence matters; don't let the transformer silently skip the field and then hit a NOT NULL constraint three layers later.

  • Rule order matters. trim|lower and lower|trim both "work," but toInt|toString vs toString|toInt do not. Read rules left to right, like a shell pipeline. When debugging an unexpected output, log the result after each step by temporarily splitting the rule into a custom method.

  • Type coercion is real. toInt on "12abc" and toBool on "yes" follow the package's (PHP's) cast semantics — document the behavior in your own tests, because "truthy string" bugs are the number one complaint about sanitization layers.

  • stripTags is not XSS protection for rich input. It's a blunt instrument. If users are allowed to submit HTML, use a proper sanitizer (HTML Purifier, mews/purifier) inside a custom transformer method and expose it as a rule. Don't assume stripTags replaces escaping at output time.

  • trim and friends do not descend recursively — you need explicit wildcard rules ('items.*.name' => 'trim') or a custom recursive method. This bites people sanitizing nested arrays from JSON APIs.

  • Don't sanitize passwords, hashes, or tokens. Any value you round-trip through a transformer is a value you might accidentally log or re-persist. Keep secrets out of the rule set, or explicitly exclude them before calling transform() (e.g. Arr::except(..., ['password'])).

  • Validation exceptions vs. Validator facade. If validate() throws, catch Illuminate\Validation\ValidationException — Laravel already knows how to render it. If you're outside HTTP (queue worker, artisan command), log $e->errors() explicitly, because otherwise failures look like silent no-ops in workers.

  • Config quirks: if you publish the config, re-check it after upgrades. Default-transformers and delimiter settings occasionally shift between minor versions, and a custom $rules string that suddenly parses differently is a nasty production surprise. Pin the package version in composer.json if you have complex delimiter usage.

  • Performance on large imports. Transforming 100k rows one at a time is fine, but instantiate the transformer once and reuse it in a loop rather than creating a new instance per row — reflection lookups on rules are the hot path. Use chunk() from the query builder for big syncs.

  • Extension points worth knowing: custom methods (as above), the base Transformer class for shared domain logic (class MoneyTransformer extends Transformer), and the config's default-transformer list if your team wants a house style (upper by default, toInt by default, etc.). Everything else is intentionally small — resist the urge to build an abstraction layer over it.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core