sidus/base-serializer-bundle
Symfony bundle that integrates the Sidus base serializer library, wiring configurable serialization services, normalizers, and encoders into the container for converting objects to and from JSON and other formats.
Reality check first. This is a Symfony bundle, not a Laravel package. There is no Flex recipe, no ServiceProvider, no facades, and — critically — sidus/base-serializer-bundle requires sidus/base-bundle, which is itself a Symfony DI/HttpKernel bundle. On a Laravel codebase, "installing" it is not an install step; it is a partial re-implementation exercise. Zero stars, zero dependents, single maintainer. Treat it as a reference design, not a runtime dependency.
Minimal steps to evaluate it (timebox: half a day):
git clone https://github.com/VincentChalnot/SidusBaseSerializerBundle
cd SidusBaseSerializerBundle
cat composer.json # check `php` and `symfony/*` constraints against your Laravel version
ls Normalizer Denormalizer # the only portable artifacts are here
Where to look first, in this order:
DependencyInjection/SidusBaseSerializerExtension.php — the config tree. Tells you what the bundle thinks is configurable, and therefore what it thinks matters.Resources/config/services.xml (or .yml) — ground truth for service IDs and tags. The bundle predates autowiring; IDs are explicit and do not match the class names.Normalizer/ and Denormalizer/ — the actual serialization logic. These classes are the only thing you might reuse; the DI wiring around them is Symfony-only.First use case (the Laravel-native version). Don't adopt the bundle. If you need context-aware serialization of models/DTOs, the equivalent day-one move is:
composer require spatie/laravel-data
or, if you specifically want the Symfony Serializer object graph behavior the bundle wraps:
composer require symfony/serializer
then bind a singleton in a service provider:
// app/Providers/SerializerServiceProvider.php
$this->app->singleton(SerializerInterface::class, function () {
return new Serializer([
new ObjectNormalizer(null, null, null, new ReflectionExtractor()),
new ArrayDenormalizer(),
new JsonEncoder(),
], [new JsonEncoder()]);
});
Wrap that behind your own App\Contracts\Serializer interface so the dependency stays swappable — never let third-party serializer types leak into controllers.
Pattern 1 — Mine the normalizers, port the intent. Open each class in Normalizer/ and ask one question: is this plain PHP acting on plain objects, or is it coupled to Symfony's ContainerInterface / SerializerInterface? Plain-PHP normalizers port cleanly into a spatie/laravel-data transformer or a custom JsonResource. Container-aware ones don't — they exist to resolve services at serialization time, and Laravel resolves services differently (no compiler passes, no tagged-collection iteration).
Pattern 2 — Standalone Symfony Serializer behind an app-owned interface. This is the only adoption path with sane ergonomics:
interface Serializer
{
public function toArray(mixed $value, array $context = []): array;
public function fromArray(array $data, string $class, array $context = []): object;
}
Bind an implementation that owns a Symfony\Component\Serializer\Serializer instance internally. Application code depends only on your interface. When the Symfony version under it drifts (and it will), you change one file.
Pattern 3 — Context propagation, Laravel-flavored. The bundle's real value proposition is context-aware serialization: the same object serializes differently per endpoint (['groups' => ['api:read']], depth limits, ID-vs-embed decisions). The Laravel analogue is conditional field exposure:
// API Resource
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->when($request->user()?->can('viewEmail', $this->resource), $this->email),
];
Do not try to recreate Symfony's groups/MaxDepth machinery in Laravel unless you have a measured need. when() and whenLoaded() cover most of it with far less machinery.
Pattern 4 — Queue and persistence serialization. The bundle is often reached for to serialize Doctrine entities for queues. The Laravel pattern is SerializesModels on jobs (Eloquent models), or explicit DTOs implementing __serialize/__unserialize. Do not serialize hydrated Eloquent models through a generic serializer — you get lazy-loaded relations, appends side effects, and stale state on the queue.
Pattern 5 — The comparative spike. If you have genuine serialization pain (cycles, polymorphic graphs, field filtering per endpoint), spend 1–2 days evaluating three options against your actual domain objects:
| Option | When it wins |
|---|---|
JsonResource / ResourceCollection |
Shallow, per-endpoint response shaping. Default choice. |
spatie/laravel-data |
Typed DTOs, nested structures, validation + serialization in one. Strong default for API-first. |
Standalone symfony/serializer |
You need normalizer composition and context arrays you already understand. |
Use the bundle as the fourth column — a read-only reference for how someone structured groups and contexts, not a candidate.
Framework mismatch is structural. There is no Laravel analogue for AppKernel, bundles.php, tagged services, or compiler passes. Registering this bundle in Laravel without a Symfony kernel shim is not possible. Do not budget for a shim — that is net-new code with no upstream support and no transfer.
Transitive dependency trap. sidus/base-serializer-bundle extends sidus/base-bundle. Both are zero-star and pin Symfony component versions from the Symfony 3/4 era. Those pins conflict with the symfony/* versions Laravel 9+ requires. Expect Composer resolution to fail loudly at install time — that is the cheap failure. The expensive one is a partial resolution that drags in old components and silently breaks something else.
PHP version skew. Old Symfony-ecosystem bundles commonly cap at PHP 7.x. Laravel 10/11 requires PHP 8.1+. Check composer.json require.php before anything else; if it caps below 8.1, the evaluation is over and you should note it in your dependency review.
Silent serialization bugs are the dangerous class. A normalizer that isn't registered doesn't throw — it falls through to a default, and fields get dropped or leaked. In an API response, both are incidents. If you adopt any serialization layer, write contract tests that assert the exact key set for each endpoint, and a test that asserts no sensitive key ever appears. Do this regardless of which library you pick.
Config loading is bundle-relative. The bundle expects config under its own Resources/config/ and merges via the DI extension. There is no config/base_serializer.php you can publish with vendor:publish. If you see docs referencing bundle config keys, they are Symfony config-tree keys and have no Laravel equivalent.
No autowiring assumptions. Service IDs in services.xml are explicit and do not match class names. If you are reading the source to understand what it does, do not guess Sidus\BaseSerializerBundle\Normalizer\FooNormalizer as a DI key — grep the XML.
Forking is the honest operating model. With 0 dependents there is no downstream pressure to keep this compatible with new Symfony or PHP releases. If you adopt it, you are the maintainer. Budget accordingly — usually this inverts build-vs-buy for a package this small.
Extension points worth borrowing (not adopting). Read these for design intent, then reimplement against spatie/laravel-data or JsonResource:
$context propagation is where most homegrown serializers break).Onboarding cost is not zero. Symfony bundle idioms — compiler passes, tagged iterators, config trees — are unfamiliar to a Laravel team and transfer nowhere else in your stack. Estimated 2–4 engineer-days just to reach confident literacy on the wiring, before you have written a line of application code.
Recommendation for day-to-day use: don't. Standardize on spatie/laravel-data (or JsonResource for thin response shaping) behind an internal Serializer interface. Use this repository as a design reference for context propagation and normalizer composition when you hit a real gap.
How can I help you explore Laravel packages today?