Complex serialization

1.2k views Asked by At

For example, I have two entities: main (parent) entity (for example User) and dependent entity (Post). I want to serialize User entity using JMS Serializer with additional information of its first post date. The post date stored in DB is timestamp int, but I want to serialize it with my helper (just a service) which converts int to sting with some formatting.

Trying to create a virtual_property with method at entity class, but failed to inject my helper into entity class. The only way to solve it for me is to serialize by myself into controller:

public function someAction()
{
    $serializedUser = $this->serializeEntity($user);
}

public function serializeEntity(User $user)
{
    // JMS serialization
    $user = $this->get('jms_serializer')->serialize($user, 'array');

    if ($user->getPosts()->count()) {
        $post = $user->getPosts()->first();
        $user['first_post_date'] = $this->get('my_helper_service')->dateFormat($post->getDate());
    }

    return $user;
}

NB: this example is synthetic, in the real world I have more complex methods, not just date formatter. But the main idea is the same.

I feel there should be better way to do this.

2

There are 2 answers

0
Asmir Mustafic On BEST ANSWER

Dmtry's solution should work perfectly for your case.

An event listener/subscriber is the best solution in this case.

A bit more general solution, that works even with objects and will trigger the whole event system part of the JMS serializer (Dmtry's solution works only with primitives and only for JSON/YAML, not XML), is:

class MyFormatter implements EventSubscriberInterface
{

    public static function getSubscribedEvents()
    {
        return array(
            array(
                'event' => 'serializer.post_serialize', 
                'method' => 'onPostSerialize',
                'class' => 'YourEntity'
            ),
        );
    }

    public function __construct(MyFormatter $foermatter)
    {
        $this->formatter = $formatter;
    }

    public function onPostSerialize(ObjectEvent $event)
    {
        $visitor = $event->getVisitor();
        $context = $event->getContext();

        $timestamp = $event->getObject()->getTimestamp();
        $formattedTime = $this->formatter->format($timestamp);

        $metadata = new StaticPropertyMetadata('stdClass', 'first_post_date', $formattedTime);

        $visitor->visitProperty($metadata, $formattedTime, $context);
    }
}

stdClass is ignored by the serializer...

1
Dmitry Malyshenko On

Of course, there is a better way. It's called serialization events.

http://jmsyst.com/libs/serializer/master/event_system

You create event subscriber

my_bundle.serializer_subscriber:
    class: MyBundle\Serializer\MyEntitySerializerSubscriber
    arguments:
        - @bundle.time_formatter
    tags:
        - { name: jms_serializer.event_subscriber }

And then just add the data you need in your listener

public function myOnPostSerializeMethod(ObjectEvent $event)
{
    if (!($event->getObject() instance of YourEntity)) {
         return;
    }

    $timestamp = $event->getObject()->getTimestamp();

    $visitor = $event->getVisitor();
    $visitor->addData('date', $this->formatter->format($timestamp));
}

P.S. I didn't check the code, so maybe I'm mistaken somewhere with name of methods, but the idea is clear, I hope