serializationzend-framework2doctrine-odmdoctrine-mongodb

Document serialization with Doctrine MongoDB ODM


I'm trying to code a class handling serialization of documents by reading their metadata. I got inspired by this implementation for entities with Doctrine ORM and modified it to match how Doctrine ODM handles documents. Unfortunatly something is not working correctly as one document is never serialized more than once even if it is refered a 2nd time thus resulting on incomplete serialization.

For example, it outputs this (in json) for a user1 (see User document) that belongs to some place1 (see Place document). Then it outputs the place and the users belonging to it where we should see the user1 again but we don't :

{
  id: "505cac0d6803fa1e15000004",
  login: "user1",
  places: [
    {
      id: "505cac0d6803fa1e15000005",
      code: "place1",
      users: [
        {
          id: "505c862c6803fa6812000000",
          login: "user2"
        }
      ]
    }
  ]
}

I guess it could be related to something preventing circular references but is there a way around it ?

Also, i'm using this in a ZF2 application, would there be a better way to implement this using the ZF2 Serializer ?

Thanks for your help.


Solution

  • I have a serializer already written for DoctrineODM. You can find it in http://github.com/superdweebie/DoctrineExtensions - look in lib/Sds/DoctrineExtensions/Serializer.

    If you are are using zf2, then you might also like http://github.com/superdweebie/DoctrineExtensionsModule, which configures DoctrineExtensions for use in zf2.

    To use the Module, install it with composer, as you would any other module. Then add the following to your zf2 config:

    'sds' => [
        'doctrineExtensions' => [
            'extensionConfigs' => [
                'Sds\DoctrineExtensions\Serializer' => null,
            ),
        ),
    ),
    

    To get the serializer use:

    $serializer = $serivceLocator->get('Sds\DoctrineExtensions\Serializer');
    

    To use the serializer:

    $array = $serializer->toArray($document)
    $json = $serializer->toJson($document)
    
    $document = $serializer->fromArray($array)
    $document = $serializer->fromJson($json)
    

    There are also some extra annotations available to control serialization, if you want to use them:

    @Sds\Setter - specify a non standard setter for a property
    @Sds\Getter - specify a non standard getter fora  property
    @Sds\Serializer(@Sds\Ignore) - ignore a property when serializing
    

    It's all still a work in progress, so any comments/improvements would be much appreciated. As you come across issues with these libs, just log them on github and they will get addressed promptly.

    Finally a note on serializing embedded documents and referenced documents - embedded documents should be serialized with their parent, while referenced documents should not. This reflects the way data is saved in the db. It also means circular references are not a problem.

    Update

    I've pushed updates to Sds/DoctrineExtensions/Serializer so that it can now handle references properly. The following three (five) methods have been updated:

    toArray/toJson
    fromArray/fromJson
    applySerializeMetadataToArray
    

    The first two are self explainitory - the last is to allow serialization rules to be applied without having to hydrate db results into documents.

    By default references will be serialized to an array like this:

    [$ref: 'CollectionName/DocumentId']
    

    The $ref style of referencing is what Mongo uses internally, so it seemed appropriate. The format of the reference is given with the expectation it could be used as a URL to a REST API.

    The default behaviour can be overridden by defineing an alternative ReferenceSerializer like this:

    /**
     * @ODM\ReferenceMany(targetDocument="MyTargetDocument")
     * @Sds\Serializer(@Sds\ReferenceSerializer('MyAlternativeSerializer'))
     */
    protected $myDocumentProperty;
    

    One alternate ReferenceSerializer is already included with the lib. It is the eager serializer - it will serialize references as if they were embedded documents. It can be used like this:

    /**
     * @ODM\ReferenceMany(targetDocument="MyTargetDocument")
     * @Sds\Serializer(@Sds\ReferenceSerializer('Sds\DoctrineExtensions\Serializer\Reference\Eager'))
     */
    protected $myDocumentProperty;
    

    Or an alternate shorthand annotation is provided:

    /**
     * @ODM\ReferenceMany(targetDocument="MyTargetDocument")
     * @Sds\Serializer(@Sds\Eager))
     */
    protected $myDocumentProperty;
    

    Alternate ReferenceSerializers must implement Sds\DoctrineExtensions\Serializer\Reference\ReferenceSerializerInterface

    Also, I cleaned up the ignore annotation, so the following annotations can be added to properties to give more fine grained control of serialization:

    @Sds\Serializer(@Sds\Ignore('ignore_when_serializing'))
    @Sds\Serializer(@Sds\Ignore('ignore_when_unserializing'))
    @Sds\Serializer(@Sds\Ignore('ignore_always'))
    @Sds\Serializer(@Sds\Ignore('ignore_never'))
    

    For example, put @Sds\Serializer(@Sds\Ignore('ignore_when_serializing')) on an email property - it means that the email can be sent upto the server for update, but can never be serialized down to the client for security.

    And lastly, if you hadn't noticed, sds annotations support inheritance and overriding, so they play nice with complex document structures.