Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Jms Serializer Extension Bundle Laravel Package

teamneusta/jms-serializer-extension-bundle

A Symfony bundle that extends JMS Serializer with practical integrations and configuration options, making it easier to customize and manage serialization and deserialization in Symfony applications.

View on GitHub
Deep Wiki
Context7

Getting Started

First, confirm this is the package you want. teamneusta/jms-serializer-extension-bundle is a Symfony bundle that adds extensions on top of JMS Serializer — its purpose is handling nested/related object graphs (the Doctrine entity case) that stock JMS Serializer chokes on with recursion and proxy errors. There is no extra.laravel block, no service provider, no facade. Composer will install it in a Laravel app, but nothing will wire itself up.

So the decision tree is:

  • Eloquent models → you almost certainly don't want this. JMS Serializer plus a Doctrine-oriented extension bundle is a lot of machinery to replace JsonResource.
  • Doctrine ORM inside Laravel (mid-migration off Symfony, or a legacy shared domain layer) → this is exactly the package's sweet spot, and worth the manual wiring below.
  • Just need fast, attribute-driven serialization → use JMS Serializer directly, or spatie/laravel-data, and skip this bundle.

Minimal setup if you're in the second bucket:

composer require teamneusta/jms-serializer-extension-bundle:^2.0

Then, because Laravel has no bundles.php and no DI compiler passes, register the bundle's services by hand:

// app/Providers/SerializerServiceProvider.php
public function register(): void
{
    $this->app->singleton(Serializer::class, function ($app) {
        $builder = SerializerBuilder::create();
        $builder->setCacheDir(storage_path('framework/cache/jms'));
        $builder->setDebug(app()->isLocal());

        // Register the bundle's extensions explicitly — this is the
        // part the Symfony container would normally do for you.
        foreach (config('jms.extensions', []) as $extension) {
            $builder->addExtension($app->make($extension));
        }

        return $builder->build();
    });
}

Where to look first: the bundle's own DependencyInjection/ directory. That is the authoritative list of services and tags it registers, and it is what you are reimplementing by hand. Read Configuration.php to see which YAML keys exist, then port those keys into config/jms.php.

First use case: serializing a Doctrine entity with a bidirectional relation (Order ↔ OrderLine) without a recursion explosion — $serializer->serialize($order, 'json') where the entity carries @Serializer\ExclusionPolicy / @MaxDepth annotations.

Implementation Patterns

Config translation. The bundle expects YAML. Keep your Laravel-side config flat and boring, and map it into the builder rather than trying to parse YAML at runtime:

// config/jms.php
return [
    'cache_dir'  => storage_path('framework/cache/jms'),
    'debug'      => env('APP_DEBUG', false),
    'extensions' => [
        \TeamNeusta\JmsSerializerExtension\...\NestedEntityExtension::class,
    ],
    'metadata_dirs' => [app_path('Entities')],
];

Serializer as a singleton, built once. Building a serializer re-reads metadata. Bind it singleton (as above) and never new SerializerBuilder inside a controller or job.

Warm the metadata cache at deploy. The single biggest production win: run an artisan command in your deploy pipeline that resolves the serializer once, so the first web request doesn't pay annotation-parsing cost.

// app/Console/Commands/WarmSerializerCache.php
public function handle(Serializer $serializer): int
{
    // Touch it so the builder writes metadata to the cache dir.
    $this->info('Serializer metadata cached.');
    return self::SUCCESS;
}

Use it behind a boundary, not everywhere. Wrap the serializer in a small app/Support/Json.php service exposing toArray() / toJson(). If you later drop the bundle or migrate to API Resources, you change one file.

Testing. Don't inject the container-built singleton into unit tests — you want a serializer with setDebug(true) and a temp cache dir so metadata errors surface as exceptions:

protected function setUp(): void
{
    $this->serializer = SerializerBuilder::create()
        ->setDebug(true)
        ->setCacheDir(sys_get_temp_dir().'/jms-test-'.uniqid())
        ->addExtension(new YourExtension())
        ->build();
}

Version pinning matters here. 2.0.0 is a major bump. Pin ^2.0 and let Dependabot tell you about 2.1 rather than floating *.

Gotchas and Tips

The Symfony/Laravel impedance mismatch is the whole story. Three things will silently not happen: bundle auto-registration, DI compiler passes (so any addCompilerPass logic in the bundle never runs), and tagged-service collection (so extensions registered via tags won't be picked up). If the bundle relies on a compiler pass to gather and order extensions, you must replicate that ordering manually — extension order can change output.

Doctrine proxies vs Eloquent — the extension's value collapses. The reason this bundle exists is that JMS Serializer walks lazy-loading proxies and circular entity references badly. Eloquent has the same shape of problem ($order->lines->first()->order), but the extension is written against Doctrine's proxy interfaces, not Illuminate\Database\Eloquent\Model. Unless you're on Doctrine, expect to write your own ExtensionInterface / visitor for Eloquent relations — at which point evaluate whether JMS Serializer earns its keep at all.

The 2.0.0 release notes are empty. The "release notes" are a bare compare/1.0.0...2.0.0 link — no changelog entries, no migration guide. Do not bump on trust. Before upgrading, open that diff and check specifically:

  • composer.json constraint changes (php, symfony/*, jms/serializer) — a JMS Serializer major shift changes annotation/attribute behavior and default naming strategies.
  • Renamed or removed extension classes (your manual extensions array is a hard reference and will fatal).
  • Config key renames in Configuration.php (your config/jms.php silently ignores unknown keys).
  • Dropped annotations in favor of PHP attributes — this changes how you annotate entities.

Read the diff, not the release page.

Cache directory permissions. storage_path('framework/cache/jms') must exist and be writable by the web user and the deploy user, or you get an opaque RuntimeException from the file locator on the first request after deploy — not at build time.

Debugging serialization errors. Set setDebug(true) and clear the metadata cache dir between runs; stale cached metadata is the usual cause of "this worked a minute ago" annotation errors. For deep recursion, temporarily raise MaxDepth rather than guessing at exclusion policies.

Extension points worth knowing. Everything in this package is built on JMS Serializer's ExtensionInterface, VisitorInterface, and Context. If the bundled extension doesn't fit, subclass or write a visitor rather than forking the bundle — that keeps you upgradeable across future 2.x releases.

Bottom line for a day-to-day Laravel developer: low default fit. Reach for it only when you already have Doctrine entities in a Laravel app that must serialize with JMS. If you're on Eloquent, this is a Symfony solution to a problem Laravel already solves — and 2.0.0's opaque release notes mean the upgrade should be a deliberate diff review, not a composer update.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource