paysera/lib-normalization-bundle
A Symfony bundle that integrates Paysera’s normalization library, providing configurable normalizers and denormalizers for converting PHP objects to arrays and back—ideal for consistent API request and response data handling.
First, verify this is the right package. paysera/lib-normalization-bundle is a Symfony bundle, not a Laravel package. It ships a Bundle subclass plus a DI extension that registers Paysera's normalization services into a Symfony container. Laravel will not auto-discover it, will never boot the bundle, and will never process its configuration tree. So the real dependency you want in a Laravel app is almost certainly the underlying paysera/lib-normalization library; the bundle is only useful to you as a reference for how to wire those services.
Minimal steps
composer require paysera/lib-normalization-bundle:^1.4 on a branch, then run composer why-not paysera/lib-normalization-bundle 1.4.0 and composer outdated --direct. Release 1.4.0 is a Symfony 7.4 compatibility change; confirm your Laravel version's Symfony component constraints (Laravel 11/12 sit on Symfony 7.x) resolve cleanly.vendor/paysera/lib-normalization-bundle/ and look at, in this order:
*Bundle.php and DependencyInjection/*Extension.php — the canonical list of service IDs, aliases, parameters, and tags this package expects.Resources/config/*.xml|yaml|php — the actual definitions you'll need to mirror.composer.json of both the bundle and paysera/lib-normalization for the PHP and Symfony version matrix.NormalizationServiceProvider.FormRequest::prepareForValidation(), run the incoming payload through the normalizer so downstream validation, DTOs, and persistence all see the same types.protected function prepareForValidation(): void
{
$this->replace(
app(NormalizerRegistry::class)->normalizeArray($this->all(), $this->normalizationMap())
);
}
Pick that boundary deliberately. If you normalize in controllers, jobs, and console commands separately, you'll get three different canonical forms within a month.
Wire it once, in one provider. Because the bundle's extension never runs, you own the container definitions. Two viable shapes:
register()/boot(), binding interfaces to implementations and tagging custom normalizers.paysera/lib-normalization directly and require --dev the bundle only as documentation. This is usually the lower-maintenance choice — you avoid a "Symfony bundle" entry in a Laravel app that does nothing at runtime and confuses the next developer.Use a registry, not scattered new. Laravel has no tag system, so emulate Symfony's tagged-collector:
// config/normalization.php
return [
'normalizers' => [
Money::class => MoneyNormalizer::class,
LocalDate::class => DateNormalizer::class,
],
];
Then in the provider, foreach (config('normalization.normalizers') as $type => $class) { $registry->register($type, $this->app->make($class)); }. This gives you the bundle's extensibility story without its container.
Normalize at the edges, not in the domain. Three boundaries where this earns its keep:
FormRequest::prepareForValidation() or a global middleware for API routes."off" from a CSV and an "off" from a form produce identical values."12.34", dates as ISO-8601) regardless of driver.Compose, don't chain ad hoc. Chain steps as a pipeline — trim → empty-to-null → type cast → format — and keep the order fixed in config. Order is the entire contract; null→""→null round-tripping is a bug you can only avoid by pinning the sequence.
Keep money and decimals as strings. Normalizers that hand back floats will silently lose precision on the way to a DECIMAL(10,2) column. Normalize to a canonical string, and let the DB or a value object own arithmetic.
Testing. Unit-test each normalizer as a pure function. Then add one feature test per endpoint that posts deliberately hostile payloads ("0", "false", null, "", " 42 ", "2026-02-30") and asserts on what landed in the database, not on the validator's error bag. That's where normalization regressions actually surface.
Auto-discovery does not exist here. composer require plus nothing else will leave you with a package that is installed, autoloaded, and completely inert. If you see zero change in behaviour, that is expected — not broken.
Don't app()->make(PayseraNormalizationBundle::class) and hope. The bundle class is a Symfony kernel concept; instantiating it outside a kernel gets you nothing.
Config defaults will diverge. Symfony's Configuration tree supplies defaults the bundle applies silently. If you hand-translate to config/normalization.php, compare against the tree in DependencyInjection/Configuration.php — the default timezone, date format, and null-vs-empty policy are exactly the values people forget to copy, and the failure mode is quiet data drift, not an error.
Exceptions are not HTTP responses. Whatever the library throws for unnormalizable input (an InvalidArgumentException or a Paysera domain exception) becomes a 500 unless you catch it and rethrow as ValidationException::withMessages() or wrap it in a middleware. Decide this once in the provider or a middleware, not per controller.
Boolean coercion is the classic trap. "false", "off", "0", and 0 do not behave uniformly across normalizer implementations. Read the library's truthiness rules and write an explicit test — don't infer them from PHP's (bool) cast.
Timezone and locale are ambient state. Date normalization reads the default timezone. Set date_default_timezone_set(config('app.timezone')) consistently and include a DST-boundary test; a date-only normalizer is safe, a datetime one is not.
Bind implementations as singletons. Stateless normalizers should be singleton() in the provider. Rebuilding a normalizer graph per request is pure overhead, and if any normalizer caches format rules, prototype binding defeats it.
Debugging: app()->bound('your.service.id'), then dd(app()->make(...)). If a Symfony-style dotted service ID isn't resolving on a class-typed constructor argument, you're missing an alias — Laravel resolves by class name, Symfony by ID, and the bundle only defines the latter.
Version and provenance. This is a 0-star package with a small bus factor; the 1.4.0 note is a compatibility bump, not a feature release, and says nothing about CI coverage against Symfony 7.4. Pin an exact version in composer.json, commit the lockfile, and confirm the tag is signed/provenanced. Check that 1.4.0 didn't raise minimum constraints in a way that forces a broader Symfony upgrade across your other Laravel packages.
Extension point to watch: if you later need the bundle's own behaviour (Twig filter, validator constraint, autowired service), that's the moment to reassess — otherwise staying on the underlying library keeps your Laravel app free of a permanently dormant Symfony bundle.
How can I help you explore Laravel packages today?