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

Serializer Laravel Package

tsantos/serializer

A PHP serialization library for Laravel projects that converts objects to arrays and JSON using configurable metadata, with support for custom handlers, relationships, and versioned API output.

View on GitHub
Deep Wiki
Context7

Getting Started

tsantos/serializer is a compile-time serialization component: you declare mapping metadata on your classes (annotations, XML, or YAML), and the library generates PHP serializer/denormalizer classes into a cache directory instead of reflecting over objects at runtime. That is the whole value proposition — but it also means the first thing you must do is give it a writable cache directory and a way to warm it.

Minimum steps:

  1. Install the component (and the bundle if you are on Symfony):

    composer require tsantos/serializer
    # Symfony apps:
    composer require tsantos/serializer-bundle
    

    Note the last release is from December 2018. Check the composer.json PHP constraint before adopting it on a modern runtime — plan for PHP 7.x, and do not assume PHP 8 compatibility without testing.

  2. Build the serializer once, as a service, not per request:

    use Tsantos\Serializer\SerializerBuilder;
    
    $serializer = (new SerializerBuilder())
        ->setCacheDir(__DIR__ . '/var/cache/serializer')
        ->setDebug(true)          // regenerate when sources change (dev only)
        ->create();
    
  3. Annotate the class you want to serialize. Metadata is mandatory — this is not a zero-config reflection serializer:

    use Tsantos\Serializer\Mapping\Annotation as Serializer;
    
    /**
     * @Serializer\ExclusionPolicy("all")
     */
    class Person
    {
        /** @Serializer\Expose */
        public $id;
    
        /** @Serializer\Expose
         *  @Serializer\Type("string")
         */
        public $name;
    }
    
  4. Use it — this is your first real use case (turning a DTO into an array for a JSON response):

    $data = $serializer->toArray($person);
    $person = $serializer->fromArray($data, Person::class);
    

Where to look first:

  • The README for the exact builder methods and annotation names for your pinned version.
  • Tsantos\Serializer\SerializerBuilder — every option (cache dir, debug, metadata loaders) hangs off it.
  • Tsantos\Serializer\Mapping\Loader\* — AnnotationLoader, XmlLoader, YamlLoader, and the Configuration object that selects them.
  • Tsantos\Serializer\SerializationContext / DeserializationContext — how you pass groups and versions.
  • The generated files in your cache dir. When something is wrong, read the generated class; it tells you exactly which properties it thinks exist and how it casts them.

If you are on Symfony, skip manual wiring and install tsantos/serializer-bundle; configure mapping paths in config/packages/tsantos_serializer.yaml and let the container inject the serializer.

Implementation Patterns

Treat it as a DTO/API-resource serializer, not a general-purpose one. The best fit is a set of thin, explicitly-annotated DTOs that back JSON responses and request payloads. Annotate those classes, keep them separate from Doctrine entities, and let the serializer cross that boundary.

Groups for context switching. Use @Groups({"list"}) / @Groups({"detail"}) on properties and select at call time:

$context = SerializationContext::create()->setGroups(['detail']);
$data = $serializer->toArray($order, $context);

Use groups rather than maintaining two DTOs for list-vs-detail endpoints — this is the cleanest day-to-day win over hand-rolled toArray() methods.

Type annotations for nested objects and collections. The generator needs to know how to recurse and how to denormalize back into objects:

/** @Serializer\Expose
 *  @Serializer\Type("App\Dto\Address")
 */
public $address;

/** @Serializer\Expose
 *  @Serializer\Type("array<App\Dto\LineItem>")
 */
public $lines;

Denormalization is where missing @Type hurts most: without it, nested structures come back as plain arrays or strings, and you discover it far from the cause.

Wire it as a singleton service. Build it once (container service, Symfony autowiring, or a small factory) and inject it. Constructing SerializerBuilder per request throws away the entire performance argument and floods the cache dir.

Warm the cache in the deploy pipeline. This is the pattern that makes the package worth using in production: generate all serializer classes during build/deploy (the bundle provides a warmup command — confirm the exact name with bin/console list | grep tsantos for your pinned version), commit the build artifact or share the cache volume, then run with setDebug(false).

Dates and scalars. Declare formats explicitly with @Type("DateTime<'Y-m-d H:i:s'>") (and equivalents) so both directions round-trip consistently. Do not rely on PHP type hints alone; the metadata is the contract.

Contexts are two-way. Groups and versions must be set on DeserializationContext as well, otherwise you will happily serialize fields that the denormalizer then refuses to populate (or vice versa).

Versioning. If you expose versioned APIs, annotate properties with version constraints and pass setVersion() on the context rather than branching on v1/v2 at the controller level.

Migration path stays open. The annotation vocabulary deliberately resembles JMS Serializer / Symfony Serializer (@ExclusionPolicy, @Expose, @Groups, @Type). If you later migrate off this package, most mappings translate with find-and-replace — a strong reason to keep your DTOs annotated in that style from day one.

Gotchas and Tips

The package is effectively unmaintained (last release 2018). Pin an exact version, and treat PHP upgrades as a migration project. Before committing a project to it, verify it runs on your target PHP and that the generated code is compatible — the code generator writes raw PHP, so language-version changes can surface in generated output rather than in your own source.

Cold cache means slow, first requests. Every new annotated class triggers code generation on first use. In production with setDebug(false), a missing generated class either regenerates (slow first hit) or fails outright, depending on config. Always warm during deploy; never rely on lazy generation behind a read-only filesystem.

Stale generated code is the #1 debugging trap. Change a property, add an @Groups, rename a class — and you may still be running old generated code until the cache is cleared. When behavior "doesn't match the annotation," clear the serializer cache first, then investigate. In dev keep setDebug(true); in prod clear on every deploy.

A read-only or container-ephemeral cache dir breaks the whole model. Ensure the cache directory persists for the life of the deployment and is writable if you allow runtime generation at all.

Missing metadata produces silent emptiness, not obvious errors. @ExclusionPolicy("all") with no @Expose serializes []. If you see empty payloads, check the exclusion policy before you check anything else. Conversely, the default policy may expose more than you want — set it explicitly on every DTO.

Denormalization needs setters, public props, or documented accessors. Private properties without a mutator will not be populated. Decide early whether DTOs use public properties (fast, simple, matches the package's performance ethos) or getters/setters, and be consistent.

Watch large object graphs. There is no lazy loading here; nested collections are traversed eagerly during generation-driven traversal. Circular references will bite — model DTOs as acyclic, referencing IDs rather than back-references.

Customization lives in three places: custom metadata loaders (registered via the configuration/builder), a custom PSR-compatible cache implementation for the generated code, and the builder's option surface. If you need to change how a value is written, check whether the generated-code model exposes an extension point before assuming it does — this is a code generator, not a normalizer pipeline, so runtime extension is more limited than with Symfony or JMS.

Benchmark honestly. The performance claim only holds for hot paths with a warm cache. For a handful of serializations per request, a maintained reflection-based serializer will be easier to operate. Profile toArray before and after adoption; if you cannot show a meaningful gain, prefer a maintained alternative.

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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource