invis1ble/symfony-serializer-extension
A lightweight extension for Symfony Serializer that adds practical enhancements to object (de)serialization, helping PHP applications handle common normalization and denormalization scenarios with less custom code.
This is a Symfony Serializer extension — normalizers/encoders plus a cached path for URI round trips and JSON — not a Laravel package. There is no service provider, no config file, no facade. Expect to wire it yourself; budget an hour, not a day.
Install the serializer and the extension together. Laravel does not require symfony/serializer, so pull it explicitly:
composer require symfony/serializer invis1ble/symfony-serializer-extension
Choose your constraint deliberately. Any existing ^1.0 / ^1.1 constraint already resolves to v1.2.0 — there are no breaking changes and the normalizer API is untouched, so most apps get this on the next composer update for free. Pin the new line only if you want to signal intent:
composer require invis1ble/symfony-serializer-extension:^1.2 --with-all-dependencies
Symfony Serializer 8 requires PHP 8.4.1+. On PHP 8.1–8.3, Composer resolves the Symfony 6.4/7 branch of the same package version — same release, different dependency graph. Check composer show symfony/serializer before assuming which one you got.
Where to look first. The README is thin; the tests are the documentation. Read vendor/invis1ble/symfony-serializer-extension/src for the normalizer list, then the test suite — the v1.2 integration coverage is explicitly organised around the four things you'll actually do: URI round trips, JSON, context handling, and cached support. Those tests are executable examples.
First use case. Serialize a DTO or URI value object to JSON in a controller, then deserialize it back:
// app/Providers/SerializerServiceProvider.php
public function register(): void
{
$this->app->singleton(Serializer::class, function () {
return new Serializer(
normalizers: [/* package normalizers, then ObjectNormalizer */],
encoders: [new JsonEncoder()],
);
});
}
Confirm the round trip works before you build anything on top of it — a broken URI normalizer surfaces later as a 500 in a webhook path.
Bind a singleton, inject it. One Serializer instance per app, registered in a dedicated provider (not AppServiceProvider, which tends to become a dumping ground). Inject into controllers, actions, jobs, and console commands by type. Keep it immutable and stateless so it's safe under Octane.
URI round trips. This is the sharpest tool in the package. URLs, signed callback endpoints, pagination links, and Location headers all become value objects you can normalize to a string and denormalize back without hand-rolled string juggling:
$json = $serializer->serialize($webhookEndpoint, 'json');
$again = $serializer->deserialize($json, WebhookEndpoint::class, 'json');
Write a test asserting $again->equals($webhookEndpoint), not just that deserialization didn't throw — round trip fidelity is the whole point.
Map context to query parameters. Symfony's serialization context maps cleanly onto Laravel's ?include= / ?fields= conventions. Build the context array in a form request, pass it through to serialize(), and you get sparse responses without touching API Resources. Handle invalid include names by validating against a whitelist before passing them into the serializer — unknown group names fail quietly rather than loudly.
Cache with Laravel's own stores. The package's cached support expects a PSR-6/PSR-16 cache. Illuminate\Contracts\Cache\Repository already extends Psr\SimpleCache\CacheInterface, so wrapping Cache::store() in Symfony\Component\Cache\Adapter\Psr16Adapter avoids introducing a second cache subsystem. Use array in tests, Redis in production, and namespace the pool by package version so a deploy invalidates stale metadata.
Turn invalid input into 422s, not 500s. Deserialization failures throw NotNormalizableValueException / PartialDenormalizationException, which Laravel will happily render as a 500. Catch ExceptionInterface at the boundary and rethrow as ValidationException with the offending fields. Enable collect_denormalization_errors in the deserialization context so you report all bad fields at once instead of failing on the first.
Composer constraints across a CI matrix. If you test on multiple PHP versions, let the constraint float rather than pinning ^1.2 — you'll get Symfony 8 on 8.4/8.5 and Symfony 7 on 8.2/8.3, which is exactly what this release supports. Pinning ^1.2 with a PHP 8.2 runner will fail to resolve.
composer show symfony/serializer is the first command to run.config/serializer.php, you'll be writing it.symfony/serializer:^8.0 alongside symfony/http-foundation:^7.x (pulled by Laravel 11/12) works, but any third-party package requiring symfony/serializer:^7 will hard-block the upgrade. Check composer why symfony/serializer before blaming this package.getSupportedTypes() in custom normalizers. Symfony removed the old supportsNormalization()-per-class caching path. If you wrote custom normalizers against an older Symfony, they'll still function but lose cached-metadata benefits — and this is the seam most likely to bite when moving between the 6.4/7/8 branches.composer.lock, and write your own contract tests around the specific value objects and URI shapes you depend on — especially if you rely on edge cases like IDNs, ports, or empty query strings.JsonResource for that.get_class() of the normalizer that handled the value by iterating getNormalizers() (or your own wrapper) — the package's normalizers sit ahead of the generic ones, so ordering bugs look like data bugs.How can I help you explore Laravel packages today?