Search code examples
phpsymfonyeventsjmsserializerbundlejms-serializer

How to change method to call for an entity on the PreSerializeEvent?


I have a Category entity that contains a bunch of Assets. The Category has a getCount method that return the amount of assets in it.

I use FosRestBundle and hence JMSSerializerBundle to serialize the Category asset to json. Now, there was a feature change that introduced different kind of assets, e.g. external and internal ones.

I still want to use the entity serialization, and I thought by creating a PreSerializeEvent I actually could do that.

Yet the docs for the serializer component are obscure by referring to the relevant part as do something

How am I actually supposed to do something here?

What I want to achieve is to change the method that is called if a flag is set. Normal case for the serializer should be to use the getCount method, on the other the getExternalCountOnly method on the Category object if getCount is serialized.

Is this actually possible to do here or am I on the wrong track?

See my CategorySerializationSubscriber:

<?php
namespace Hn\AssetDbBundle\Listener;

use Hn\AssetDbBundle\Entity\Category;
use Hn\UserBundle\Entity\User;
use JMS\Serializer\Annotation\PostSerialize;
use JMS\Serializer\EventDispatcher\EventSubscriberInterface;
use JMS\Serializer\EventDispatcher\ObjectEvent;
use JMS\Serializer\EventDispatcher\PreSerializeEvent;
use Psr\Log\LoggerInterface;
use Symfony\Component\Security\Core\SecurityContextInterface;

/**
 * Class CategorySerializationSubscriber
 * @package Hn\AssetDbBundle\Listener
 */

class CategorySerializationSubscriber implements EventSubscriberInterface
{
    /**
     * @var LoggerInterface
     */
    private $logger;

    /**
     * @var User $user
     */

    private $user;

    /**
     * @param LoggerInterface $logger
     */

    public function __construct(LoggerInterface $logger, SecurityContextInterface $context)
    {
        $this->user = $context->getToken()->getUser();
        $this->logger = $logger;
        $logger->critical($this->user->getFullNameOrEmail());
    }

    /**
     * @return array
     */
    public static function getSubscribedEvents()
    {
        return [
            [
                'event' => 'serializer.pre_serialize',
                'method' => 'onPreSerialize'
            ],
        ];
    }

    /**
     * @param PreSerializeEvent $event
     */

    public function onPreSerialize(PreSerializeEvent $event)
    {
        $category = $this->getCategoryFromEvent($event);

        if (!$category) {
            $this->logger->info('is not a category, aborting');
            return;
        }

        $userCanViewInternalAsset = $this->user->isAllowedToViewInternalAssets();

        /**
         * How to actually do something?
         * 
         * Pseudocode follows:
         */

        if ($userCanViewInternalAsset) {
            $seriaization->replaceMethod($category, 'getCount', 'getExternalAssetCount');
        } else {
            /**
             * $category->getCount(); remains intact
             */
        }
    }

    /**
     * @param PreSerializeEvent $event
     * @return Category|null
     */

    protected function getCategoryFromEvent(PreSerializeEvent $event) {

        /**
         * @var Category $category
         */

        $category = $event->getObject();

        if (!$category instanceof Category) {
            return null;

        }

        return $category;

    }
}

Solution

  • How about excluding by default and then using the postSerialize() subscriber to add it with the function you want? For example:

    Define in services.yml (or under services in config.yml):

    category_serialization_subscriber:
        class: Hn\AssetDbBundle\Listener\CategorySerializationSubscriber
        tags:
            - { name: jms_serializer.event_subscriber }
    

    Then the following, which is modified from your class above:

    public static function getSubscribedEvents()
    {
        return array(array(
            'event'  => 'serializer.post_serialize',
            'class'  => 'Hn\AssetDbBundle\Entity\Category',
            'method' => 'onPostSerialize'
        ));
    }
    
    public function onPostSerialize(ObjectEvent $event)
    {
        $category = $event->getObject();
        $visitor  = $event->getVisitor();
    
        if ($this->user->isAllowedToViewInternalAssets()) {
            $visitor->addData('count', $category->getExternalAssetCount());
        } else {
            $visitor->addData('count', $category->getCount());
        }
    }