2

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.

jhuet
  • 396
  • 2
  • 11
  • To answer my question, @superdweebie made a great Doctrine library that he described below that contains a serializer. If you use the eager fetching then you'll have all the documents returned. For now though you might end up in circular references problem but we're working on [a feature](https://github.com/superdweebie/doctrineExtensions/issues/6) to implement a max nesting depth option. – jhuet Dec 16 '12 at 21:41

2 Answers2

3

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.

superdweebie
  • 1,576
  • 14
  • 23
  • Hi @superdweebie, thanks for your help here. I tried your serializer but ran into the problem that referenced documents are still serialized and retrieved as objects because [here](https://github.com/superdweebie/doctrineExtensions/blob/master/lib/Sds/DoctrineExtensions/Serializer/Serializer.php#L190) there doesn't seem to have a check for `$mapping['reference']` to not add them. Also what about getters that don't return strings such as date objects ? – jhuet Dec 07 '12 at 09:28
  • On a side note, how would you do to expose some kind of API to access the data of your app with references to documents if you don't retrieve those in the serializer itself ? – jhuet Dec 07 '12 at 09:30
  • Hey @jhuet, thanks for taking the time to give it a go. I'll add a test case for serializer ref documents (I don't think I had one). As for API, I was thinking of using something like this: http://dojotoolkit.org/reference-guide/1.8/dojox/json/ref.html. I haven't actually had need to do this (yet), which is why the code isn't all 100%. But this is a good change to harden the lib up a bit more. – superdweebie Dec 09 '12 at 08:35
  • No problem :) The dojox.json.ref looks like an interesting approach, do you know how widely it is adopted yet ? So far on any API i've come accross, the refered documents were just output as plain document. And btw it doesn't seem to solve how you will retrieve those refered documents in the 1st place. How do you know on the server-side if a document should be served as a reference or as a plain document if it's the 1st time it is sent ? I doubt the client should send all the documents he has knowledge of at every requests ? – jhuet Dec 09 '12 at 15:57
  • @jhuet I've just pushed a few changes, as noted above. They may help. – superdweebie Dec 10 '12 at 05:47
  • Wow, that's what you call a _few_ changes ! Awesome job there, thanks ! There's _almost_ everything i need ;) For now i feel more comfortable using the Eager way for serializing references and of course i came accross the circular references problem. Do you think some kind of max recursion depth detector could be added ? – jhuet Dec 10 '12 at 16:22
-1

Another very simple, framework independent way to transforming Doctrine ODM Document to Array or JSON - http://ajaxray.com/blog/converting-doctrine-mongodb-document-tojson-or-toarray

This solution gives you a Trait that provides toArray() and toJSON() functions for your ODM Documents. After useing the trait in your Document, you can do -

<?php
// Assuming in a Symfony2 Controller
// If you're not, then make your DocmentManager as you want
$dm = $this->get('doctrine_mongodb')->getManager();
$report = $dm->getRepository('YourCoreBundle:Report')->find($id);

// Will return simple PHP array
$docArray = $report->toArray();

// Will return JSON string
$docJSON = $report->toJSON();

BTW, it will work only on PHP 5.4 and above.

Anis
  • 3,349
  • 1
  • 21
  • 16