invis1ble/serializer-extension-bundle
Symfony bundle that extends the Serializer component with additional normalizers, encoders, and configuration options, making it easier to serialize and deserialize PHP objects to JSON, XML, and other formats in Symfony applications.
Install the bundle with Composer:
composer require invis1ble/serializer-extension-bundle
Version 1.1.0 adds Symfony 8 support while keeping Symfony 6.4 / 7.x working. Existing ^1.0 constraints already resolve to it; to pin the release line explicitly:
composer require invis1ble/serializer-extension-bundle:^1.1 --with-all-dependencies
If Flex doesn't provide a recipe, register it manually in config/bundles.php:
return [
// ...
Invis1ble\SerializerExtensionBundle\SerializerExtensionBundle::class => ['all' => true],
];
There is typically nothing mandatory to configure — the bundle's job is to register a set of normalizers/context helpers on top of the framework's serializer service. Check whether a config file was created under config/packages/ (the alias is usually derived from the bundle name, e.g. serializer_extension); if not, defaults apply. No service configuration changes are required when upgrading to v1.1.0.
Where to look first:
vendor/invis1ble/serializer-extension-bundle/src/DependencyInjection/ — the extension class and its Configuration show every config key and how services are wired.vendor/invis1ble/serializer-extension-bundle/src/Resources/config/ — as of v1.1.0 the service definitions are PHP files (Symfony 8 dropped XML DI). Look for *.php definitions with the serializer.normalizer tags; older tags are still at priority 10.invis1ble/symfony-serializer-extension package (v1.1.0 requires ^1.2) — the bundle is mostly a DI wrapper; the actual normalization logic lives there.First use case: you have DTOs/entities containing \DateTimeInterface, backed enums, and \Stringable value objects, and you're tired of writing one-off normalizers or sprinkling #[Context] attributes. Inject SerializerInterface as usual and serialize — the bundle's normalizers should produce consistent scalar output without extra code:
final class OrderController
{
public function __construct(private SerializerInterface $serializer) {}
public function __invoke(Order $order): Response
{
return new Response(
$this->serializer->serialize($order, 'json'),
200,
['Content-Type' => 'application/json']
);
}
}
Treat it as a serializer configuration layer, not a replacement. The bundle extends symfony/serializer; you still use SerializerInterface, #[Groups], #[SerializedName], #[MaxDepth], and the standard context keys. The win is fewer custom normalizers and more predictable output for common PHP types.
Reusable context builders. Rather than repeating context arrays across controllers, centralize them in a small service or the bundle's context-builder abstraction (if exposed). A typical pattern:
final class ApiContextFactory
{
public function forList(): array
{
return [
'groups' => ['order:list'],
'datetime_format' => \DateTimeInterface::ATOM,
'json_encode_options' => JSON_UNESCAPED_UNICODE,
];
}
}
Then $serializer->serialize($orders, 'json', $contextFactory->forList()). This keeps format/date/enum behavior consistent across endpoints.
Overriding the bundle's normalizers. When the bundle's behavior for a type doesn't match your domain (e.g. you want a custom date format for a specific class, or a different representation for a backed enum), register your own normalizer and control ordering via the tag priority:
services:
App\Serializer\MoneyNormalizer:
tags:
- { name: serializer.normalizer, priority: 100 }
Higher priority wins for supportsNormalization(). The bundle registers its normalizers at priority 10 (unchanged in v1.1.0), so anything above that reliably takes precedence. Verify the effective order with bin/console debug:container --tag=serializer.normalizer.
PHP service definitions, not XML. Since v1.1.0 the bundle ships PHP DI files because Symfony 8 removed XML configuration support. If you were overriding or decorating a bundle service by importing or editing its XML, rewrite that override as a PHP service definition (services.php or a services: block in YAML) using the same service IDs — the IDs themselves did not change. This is a mechanical port, but it must be done before you upgrade to Symfony 8, since the legacy XML files are gone.
Per-format handling. Symfony's serializer supports json, xml, csv, etc. The bundle's normalizers generally return scalars, so they compose well across formats — but be deliberate: date formats and empty-collection handling differ between JSON and XML. Use EncoderInterface-level context for encoder-specific options and keep normalizer context type-agnostic.
URI value objects. The bundle exposes a URI factory alias in the container (kept stable in v1.1.0, and covered by the release's URI/JSON round-trip tests). If you serialize URI-bearing DTOs, rely on that alias rather than instantiating a URI factory yourself, so the same configuration is used everywhere.
API Platform and Messenger. Anything resolving the shared serializer service benefits automatically: API Platform's JSON-LD/JSON:API output, Messenger's SerializerInterface transport serializer, and symfony/messenger envelope stamping. If you use Messenger with a custom transport serializer, confirm it decorates the same serializer service and not a separately-built one.
Value objects and enums. Backed enums typically normalize to their ->value; pure (non-banked) enums and \Stringable objects usually go through __toString() or a dedicated normalizer. Audit one representative payload per type early (var_dump the JSON) so you know exactly what shape downstream consumers get.
Testing. Use KernelTestCase and fetch serializer from the container so you exercise the real bundle configuration rather than a hand-built Serializer:
self::assertJsonStringEqualsJsonString(
'{"createdAt":"2024-01-01T00:00:00+00:00","status":"paid"}',
self::getContainer()->get('serializer')->serialize($order, 'json')
);
Mirroring the bundle's own approach in v1.1.0 — container checks for normalizer registration/autowiring/priority, plus HTTP-style round trips and invalid-input cases — is a good template for your own integration tests.
DateTimeNormalizer and BackedEnumNormalizer already. The bundle registers its normalizers at priority 10, which can silently change output versus the framework defaults. Always dump debug:container --tag=serializer.normalizer and confirm which one wins for each type.serializer.normalizer tags depend on bundle order in bundles.php. If custom normalizers are ignored, try registering the bundle after FrameworkBundle and rebuild the cache.serializer_extension vs invis1ble_serializer_extension). Inspect DependencyInjection/Configuration.php before writing config — a wrong key throws an InvalidConfigurationException at cache warmup, not at runtime.@Type annotations, no exclusion policies. If you're migrating from JMS, plan a separate port; this bundle only extends the native attribute-based serializer.CircularReferenceHandler configured and use #[MaxDepth]/#[Groups] to break graph cycles before they hit the serializer.[] encodes as a JSON array — fine — but a normalized empty \stdClass may encode as {}. If consumers are strict, normalize empty collections to [] explicitly in the DTO or via context.debug:container --tag=serializer.normalizer, (2) call $normalizer->supportsNormalization($object) directly, (3) inspect the returned array before encoding. Most "the bundle broke my output" issues trace to a priority collision or an unexpected context key.SerializerInterface (for cross-cutting context defaults) or a small normalizer for your value objects. Avoid monkey-patching bundle services; override via services.yaml (or PHP definitions) bindings instead.php >= 8.4.1, and (b) any application code that directly imported the bundle's legacy XML files is switched to the equivalent PHP files. Applications that only registered the bundle need no changes. Confirm the resolved dependency with composer show invis1ble/serializer-extension-bundle invis1ble/symfony-serializer-extension.composer.json, read the service definitions before upgrading, and be prepared to vendor/fork the normalizer you depend on. v1.1.0 is a better signal than before: it fixes issue #1, ships a documented changelog diff, and passed CI across four PHP/Symfony combinations plus a production-consumer install without dev dependencies. Even so, verify the actual tag and version you get with composer show invis1ble/serializer-extension-bundle rather than trusting "last release" metadata — a future-dated entry usually means an unreleased branch or a metadata quirk.How can I help you explore Laravel packages today?