opensoft/opensoft-simple-serializer-bundle
A Symfony bundle that simplifies serializing PHP objects into arrays or JSON using the Symfony Serializer. Configure and control output through annotations or attributes, reducing boilerplate for APIs and data transfer objects.
This bundle is a thin Symfony 2 integration layer around Opensoft's Simple Serializer library — it wires the plain-PHP Serializer into the container, adds annotation/attribute mapping discovery, and exposes a Twig extension. Treat it as a legacy integration: Symfony 2 is EOL and the last release is 2018-01-23, so use it only in an existing SF2 project that already depends on opensoft/simple-serializer, not in anything new.
Minimal setup:
opensoft/simple-serializer version):
composer require opensoft/opensoft-simple-serializer-bundle
app/AppKernel.php (SF2 has no bundle auto-discovery):
$bundles = [
// ...
new Opensoft\SimpleSerializerBundle\OpensoftSimpleSerializerBundle(),
];
DependencyInjection/Configuration.php — the actual config tree (groups, cache, annotation reader, handlers).Resources/config/services.xml — service IDs and aliases; this tells you whether the service is opensoft_simple_serializer.serializer, simple_serializer, or aliased to the library class.DependencyInjection/OpensoftSimpleSerializerBundleExtension.php — defaults that get applied if you configure nothing.// controller
public function showAction($id)
{
$entity = $this->getDoctrine()->getRepository(Post::class)->find($id);
$data = $this->get('opensoft_simple_serializer.serializer')
->toArray($entity);
return new JsonResponse($data);
}
Annotate the model with whatever the underlying library expects (@Exclude, @Groups, @SerializedName, custom handler annotations) — the bundle's job is mainly making those annotations resolvable via the container's annotation reader.Define serialization shape on the model, not in controllers. The whole value of this bundle over hand-written toArray() methods is that each entity carries its own annotations. Keep controllers dumb:
use Opensoft\SimpleSerializerBundle\Annotation\Groups;
use Opensoft\SimpleSerializerBundle\Annotation\Exclude;
use Opensoft\SimpleSerializerBundle\Annotation\SerializedName;
class User
{
/** @Groups({"list", "detail"}) */
private $id;
/** @Groups({"detail"}) */
private $email;
/** @Exclude */
private $passwordHash;
}
Use groups for API versioning / list-vs-detail. This is the single most useful day-to-day pattern: one model, several serialization contexts:
$listItem = $serializer->toArray($user, ['groups' => ['list']]);
$full = $serializer->toArray($user, ['groups' => ['detail']]);
Wrap it in a small application-level service. Don't scatter the bundle's service ID through controllers; wrap it once so you can swap serializers (e.g. to JMS or Symfony Serializer) later without a repo-wide find-and-replace:
class ApiSerializer
{
public function __construct(private $serializer) {}
public function forList($object): array
{
return $this->serializer->toArray($object, ['groups' => ['list']]);
}
}
Compose with Symfony's JsonResponse / view listeners, not the bundle's Twig filter for API work. The Twig extension is fine for templates, but for API payloads you want the array in a controller so you can version, paginate, and apply HTTP caching deliberately.
Feed the serializer plain arrays or DTOs when the shape doesn't match the entity. Simple Serializer is shallow-ish by design; deep entity graphs are cheaper to flatten yourself into an array and serialize the leaf objects, than to fight the mapping.
Handle collections explicitly. Most serializers of this style do not recurse into ArrayCollection automatically in a predictable order — loop and serialize, then wrap:
$items = array_map(fn($u) => $serializer->toArray($u, ['groups' => ['list']]), $users);
Integration tips
doctrine/annotations) as a service and point the bundle at it; without it annotation-based mapping silently serializes everything by reflection.prod only; annotation parsing is the slowest part and caching it in dev gets stale during model edits.Symfony 2 only, and abandoned. The bundle targets the SF2 bundle structure (AppKernel, services.xml, no autowiring). It will not drop into Symfony 4+ unchanged: no AbstractBundle/AbstractBundle alias, no #[AsService], and the service definitions assume SF2's container semantics. In an SF4+ project, port the services.xml definitions to your own services.yaml and skip the bundle class entirely.
PHP version ceiling. 2018-era code targeting SF2 typically runs on PHP 5.6–7.x. If you're on PHP 8, watch for ReflectionParameter::getClass() deprecations, dynamic property notices, and each()-style helpers in the vendor tree. Run composer why-not php 8.x before assuming it installs.
Service ID guessing is the #1 time-waster. The bundle's serializer service is aliased, but the exact ID varies by version. Don't guess — inspect the container:
php app/console debug:container | grep -i serial
Then use the canonical alias, or (better) inject the underlying library's Serializer type-hint via the alias so you're not coupled to the ID.
Annotations are read from Doctrine\Common\Annotations. If doctrine/annotations is missing or the reader isn't configured, @Exclude/@Groups are silently ignored and your private fields leak into JSON. Test this deliberately: serialize an object with an @Excluded field and assert it's absent. Do this in an integration test, not just a unit test — the failure is configuration, not logic.
No groups configured means everything serializes. toArray($obj) with no group option includes every public getter and (depending on the underlying library's reflection strategy) possibly private properties. Always pass groups explicitly for user-facing responses; don't rely on "it only serializes what I annotated."
Circular references and lazy proxies. A serialized Doctrine entity with a bidirectional relation will either recurse forever or serialize a proxy object as []. @Exclude the inverse side, or better, serialize a DTO. If you see {} for a related entity in JSON, that's the proxy, not a bug in the serializer.
JsonSerializable / jsonSerialize() on entities will not be called. This serializer builds arrays from reflection and annotations, bypassing your model's own JSON contract. If you've written jsonSerialize() methods, they won't fire — a common source of "why is my custom formatting ignored?"
Config caching bites in dev. If you enable the bundle's metadata cache, changes to annotations on a model won't apply until you clear var/cache/dev. Clear cache after model edits, or disable caching for dev in config_dev.yml.
Extension points. When the built-in mapping isn't enough, the intended escape hatches are (a) custom annotation handlers registered via config, and (b) the underlying library's handler/normalizer interfaces — register them as services and pass them into the serializer. The bundle itself adds almost no behavior beyond wiring, so any serious customization belongs on the library side, not as a bundle override.
Have an exit plan. Given 9 stars, zero dependents, and no release since 2018, assume no security fixes. Budget for migration to symfony/serializer with groups + normalizers, or JMS Serializer. The wrapper-service pattern from "Implementation Patterns" makes that a one-class change rather than a project-wide rewrite — worth doing on day one.
How can I help you explore Laravel packages today?