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 Uuid Laravel Package

mhujer/jms-serializer-uuid

Adds JMS Serializer support for ramsey/uuid: serialize UUID objects as strings and deserialize string values back into UUID instances, so UUID fields work cleanly in your JMS Serializer mappings.

Deep Wiki
Context7

Getting Started

  1. Install and inspect before trusting.

    composer require mhujer/jms-serializer-uuid
    composer show mhujer/jms-serializer-uuid   # check the PHP / ramsey-uuid / jms-serializer constraints
    

    The last release is from 2016, so Composer's constraint solver is your first obstacle. Look at vendor/mhujer/jms-serializer-uuid/src/ — the whole package is one SubscribingHandlerInterface implementation with a serialize/deserialize callback pair (~40 lines). That file is the documentation; read it first and note the exact class name and the type string it subscribes to.

  2. Register the handler. Standalone SerializerBuilder:

    use JMS\Serializer\SerializerBuilder;
    use Mhujer\JmsSerializer\Uuid\UuidHandler; // verify the FQCN in vendor/
    
    $serializer = SerializerBuilder::create()
        ->addDefaultHandlers()
        ->configureHandlers(function ($handlers) {
            $handlers->registerSubscribingHandler(new UuidHandler());
        })
        ->build();
    

    Symfony (JMS Serializer Bundle auto-collects the tag):

    services:
        Mhujer\JmsSerializer\Uuid\UuidHandler:
            tags: ['jms_serializer.subscribing_handler']
    
  3. Declare the type on every UUID property. This is the step people miss — JMS picks the handler from the declared type, not from the runtime object:

    use JMS\Serializer\Annotation as Serializer;
    
    class User
    {
        /** @Serializer\Type("Ramsey\Uuid\Uuid") */
        private $id;
    
        /** @Serializer\Type("array<Ramsey\Uuid\Uuid>") */
        private $roleIds;
    }
    
  4. First use case: a REST endpoint returning {"id": "7c9e6679-7425-40de-944b-e07fc1f90ae7"} where the entity property is a Ramsey\Uuid\Uuid. GET proves serialization; POST with a UUID string proves deserialization and gives you a Uuid object in the hydrated DTO.

Implementation Patterns

Round-trip test as the acceptance criterion. Write this before wiring anything else — it catches almost every failure mode (handler not registered, wrong type string, Ramsey version mismatch):

$json = $serializer->serialize($user, 'json');
self::assertStringContainsString('"id":"7c9e6679-7425-40de-944b-e07fc1f90ae7"', $json);

$user = $serializer->deserialize($json, User::class, 'json');
self::assertInstanceOf(\Ramsey\Uuid\UuidInterface::class, $user->getId());

Collections work for free. array<Ramsey\Uuid\Uuid>, ArrayCollection of UUIDs, and nested DTOs all route each element through the same handler — as long as the generic type string matches the subscribed type exactly.

Nullable / optional fields. JMS short-circuits null before the handler is called, so "id": null round-trips fine. Empty string "" does not — it reaches the handler and Uuid::fromString('') throws. Decide early whether "" means null (normalize in a custom callback) or a 400.

Subclass the handler instead of patching vendor. The only two things you'll realistically want to change are the type list and validation:

final class AppUuidHandler extends UuidHandler
{
    public static function getSubscribingMethods(): array
    {
        $methods = [];
        foreach (['Ramsey\Uuid\Uuid', 'Ramsey\Uuid\UuidInterface'] as $type) {
            foreach (['json', 'xml', 'yaml'] as $format) {
                $methods[] = ['type' => $type, 'format' => $format, 'method' => 'serializeUuid'];
                $methods[] = ['type' => $type, 'format' => $format, 'method' => 'deserializeUuid'];
            }
        }
        return $methods;
    }
}

(Keep the parent's method names; in JMS Serializer 3.x getSubscribingMethods() is static and returns an array without a direction key, which means "both directions".)

Register your subclass instead of the vendor class, never both — duplicate subscriptions for the same type/format make the winning callback order-dependent and effectively undefined.

API Platform 1.x/2.x with the JMS backend uses the same annotations, so this handler integrates with no extra work; the property-level @Type is the only contract.

If you control the codebase, consider replacing the dependency with a 30-line in-app handler. The maintenance surface is tiny:

public function deserialize(VisitorInterface $visitor, $data, array $type, Context $context)
{
    if ($data === null || $data === '') {
        return null;
    }
    try {
        return \Ramsey\Uuid\Uuid::fromString((string) $data);
    } catch (\InvalidArgumentException $e) {
        throw new \JMS\Serializer\Exception\RuntimeException(
            sprintf('Invalid UUID "%s"', $data), 0, $e
        );
    }
}

public function serialize(VisitorInterface $visitor, $uuid, array $type, Context $context): string
{
    return $uuid->toString();
}

This also lets you type against UuidInterface and keep up with ramsey/uuid 4.x.

Gotchas and Tips

  • Ramsey UUID version drift is the big one. This package was written against the Ramsey\Uuid\Uuid concrete class of the 2.x/3.x era. ramsey/uuid 4.x returns UuidV1/UuidV4/LazyUuidFromString implementations of UuidInterface that are not instances of Uuid. If the handler's type hint is the concrete class, it silently never matches. Check vendor/ before assuming it works.
  • The symptom of an uninvoked handler is {}. If a UUID property serializes to an empty JSON object ("id":{}), JMS fell through to the generic object visitor — the type string in @Type doesn't match any subscribed type. Same root cause for deserialization returning an unpopulated object.
  • Type strings must match character-for-character, including the namespace. Ramsey\Uuid\Uuid ≠ Ramsey\Uuid\UuidInterface ≠ ramsey\uuid\uuid. Annotating an UuidInterface-typed property with @Type("Ramsey\Uuid\Uuid") is legitimate (the handler creates the concrete object), but the annotation string must be what the handler subscribes to.
  • doctrine/annotations must be present for @HandlerCallback docblock annotations to be read. On JMS Serializer 3.x running without it (or with attributes configured), the callbacks may not be discovered at all. If your @Serializer\Type annotations work but UUIDs don't, this is the second thing to check after the type string.
  • Invalid UUID strings throw InvalidUuidStringException, which is not a JMS\Serializer\Exception\RuntimeException. Unwrapped, it escapes Symfony's jms_serializer error handling and returns a 500 instead of a clean 400. Always wrap it in your subclass.
  • Clear the metadata cache after changing annotations. JMS caches serialization metadata (file/PSR/symfony cache pool depending on config); stale metadata is a classic "I fixed the annotation and nothing changed" trap.
  • Only the formats listed in getSubscribingMethods() are handled. If you serialize csv or yaml in a batch export, UUIDs fall back to object serialization. Add formats via the subclass rather than assuming wildcard support.
  • Debug fast: dump the subscription table instead of guessing —
    var_dump((new UuidHandler())->getSubscribingMethods());
    
    and log inside serialize/deserialize to confirm the callback actually fires. If it fires but the output is wrong, the problem is in the callback; if it never fires, it's registration/type/annotation.
  • Don't add the vendor handler and your subclass together, and don't new the handler per request in Symfony — let the bundle's compiler pass manage it as a shared service.
  • Plan the exit. With no releases since 2016 and no CI against modern PHP, treat this as vendored-by-fork rather than a dependency you can bump blindly. Pin ramsey/uuid and JMS Serializer in composer.json, and keep the round-trip test in your suite so a transitive upgrade that breaks the handler fails loudly.
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