JMSSerializer deserialize entity by id
Asked Answered
B

1

6

i'm using JMSSerializer to deserialize a JSON request and i'm having troubles with ManyToOne relations. I would like to deserialize the relation entity from a id given. Example:

Class Game {


/**
 * @var Team
 *
 * @ORM\ManyToOne(targetEntity="Team")
 * @ORM\JoinColumn(name="home_team_id", referencedColumnName="id")
 * @JMSSerializer\SerializedName("home")
 */
private $homeTeam;

/**
 * @ORM\ManyToOne(targetEntity="Team")
 * @ORM\JoinColumn(name="visitor_team_id", referencedColumnName="id")
 * @JMSSerializer\SerializedName("visitor")
 */
private $visitorTeam;
}

So when i get this Json

{"home": "id1", "visitor": "id2"}

Get the related entities. Any clouds?? i can't figure it out

Thanks in advance

Bondman answered 5/2, 2017 at 11:44 Comment(0)
A
9

Custom serializer handler allows to do it.

At first, you need to create your own serialization handler. Something like this:

<?php

namespace AppBundle\Serializer\Handler;

use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bridge\Doctrine\RegistryInterface;
use JMS\Serializer\Context;
use JMS\Serializer\Exception\InvalidArgumentException;
use JMS\Serializer\GenericDeserializationVisitor;
use JMS\Serializer\Handler\SubscribingHandlerInterface;
use JMS\Serializer\VisitorInterface;
use JMS\Serializer\GraphNavigator;

class EntityHandler implements SubscribingHandlerInterface
{
    /**
     * @var RegistryInterface
     */
    protected $registry;

    /**
     * @return array
     */
    public static function getSubscribingMethods()
    {
        $methods = [];

        foreach (['json', 'xml', 'yml'] as $format) {
            $methods[] = [
                'type' => 'Entity',
                'direction' => GraphNavigator::DIRECTION_DESERIALIZATION,
                'format' => $format,
                'method' => 'deserializeEntity',
            ];

            $methods[] = [
                'type' => 'Entity',
                'format' => $format,
                'direction' => GraphNavigator::DIRECTION_SERIALIZATION,
                'method' => 'serializeEntity',
            ];
        }

        return $methods;
    }

    /**
     * EntityHandler constructor.
     * @param RegistryInterface $registry
     */
    public function __construct(RegistryInterface $registry)
    {
        $this->registry = $registry;
    }

    /**
     * @param VisitorInterface $visitor
     * @param $entity
     * @param array $type
     * @param Context $context
     * @return mixed
     */
    public function serializeEntity(VisitorInterface $visitor, $entity, array $type, Context $context)
    {
        $entityClass = $this->getEntityClassFromParameters($type['params']);
        if (!$entity instanceof  $entityClass) {
            throw new InvalidArgumentException(
                sprintf("Entity class '%s' was expected, but '%s' got", $entityClass, get_class($entity))
            );
        }

        $entityManager = $this->getEntityManager($entityClass);
        $primaryKeyValues = $entityManager->getClassMetadata($entityClass)->getIdentifierValues($entity);
        if (count($primaryKeyValues) > 1) {
            throw new InvalidArgumentException(
                sprintf("Composite primary keys does'nt supported now (found in class '%s')", $entityClass)
            );
        }
        if (!count($primaryKeyValues)) {
            throw new InvalidArgumentException(
                sprintf("No primary keys found for entity '%s')", $entityClass)
            );
        }

        $id = array_shift($primaryKeyValues);
        if (is_int($id) || is_string($id)) {
            return $visitor->visitString($id, $type, $context);
        } else {
            throw new InvalidArgumentException(
                sprintf(
                    "Invalid primary key type for entity '%s' (only integer or string are supported",
                    $entityClass
                )
            );
        }
    }

    /**
    * @param GenericDeserializationVisitor $visitor
    * @param string $id
    * @param array $type
    */
    public function deserializeEntity(GenericDeserializationVisitor $visitor, $id, array $type)
    {
        if (null === $id) {
            return null;
        }

        if (!(is_array($type) && isset($type['params']) && is_array($type['params']) && isset($type['params']['0']))) {
            return null;
        }

        $entityClass = $type['params'][0]['name'];
        $entityManager = $this->getEntityManager($entityClass);

        return $entityManager->getRepository($entityClass)->find($id);
    }

    /**
     * @param array $parameters
     * @return string
     */
    protected function getEntityClassFromParameters(array $parameters)
    {
        if (!(isset($parameters[0]) && is_array($parameters[0]) && isset($parameters[0]['name']))) {
            throw new InvalidArgumentException('Entity class is not defined');
        }

        if (!class_exists($parameters[0]['name'])) {
            throw new InvalidArgumentException(sprintf("Entity class '%s' is not found", $parameters[0]['name']));
        }

        return $parameters[0]['name'];
    }

    /**
     * @param string $entityClass
     * @return EntityManagerInterface
     */
    protected function getEntityManager($entityClass)
    {
        $entityManager = $this->registry->getEntityManagerForClass($entityClass);
        if (!$entityManager) {
            throw new InvalidArgumentException(
                sprintf("Entity class '%s' is not mannaged by Doctrine", $entityClass)
            );
        }

        return $entityManager;
    }
}

Then you should register it in your service configuration file. If you use yaml, it will be something like that:

custom_serializer_handle:
    class: AppBundle\Serializer\Handler\EntityHandler
    arguments: ['@doctrine']
    tags:
        - {name: 'jms_serializer.subscribing_handler'}

In your entity, define JMSSerializer Type annotation

/**
 * @var Team
 * * @ORM\ManyToOne(targetEntity="Team")
 * @ORM\JoinColumn(name="home_team_id", referencedColumnName="id")
 * @JMSSerializer\SerializedName("home")
 * @JMSSerializer\Type("Entity<AppBundle\Entity\Team>")
 * List item
 */
private $homeTeam;

Don't forget clear caches. That's all.

Aniakudo answered 23/2, 2017 at 15:38 Comment(1)
This works, but if the $homeTeam also has a @groups annotation, that one is ignored even though the group is the one that should be exposed. Any idea why?Attractant

© 2022 - 2024 — McMap. All rights reserved.