Serializer using Normalizer returns nothing when using setCircularReferenceHandler
Asked Answered
G

1

11

Question:

Why does my response return "blank" when I set the setCircularReferenceHandler callback?

EDIT:

Would appear that it returns nothing, but does set the header to 500 Internal Server Error. This is confusing as Symfony should send some kind of error response concerning the error?

I wrapped $json = $serializer->serialize($data, 'json'); in a try/catch but no explicit error is thrown so nothing is caught. Any ideas would be really helpful.

Context:

When querying for an Entity Media I get a blank response. Entity Media is mapped (with Doctrine) to Entity Author. As they are linked, indefinite loops can occur when trying to serialize.

I had hoped that using the Circular Reference Handler I could avoid just that, but it's not working.

Error:

This is the error I'm getting when I'm NOT setting the Circular Reference Handler:

A circular reference has been detected when serializing the object of class "Proxies__CG__\AppBundle\Entity\Author\Author" (configured limit: 1) (500 Internal Server Error)

Now this error is completely expected, as my Entity Author points back to the Entity Media when originally querying for a Media ( Media -> Author -> Media )

   class Author implements JsonSerializable {

     //Properties, Getters and setters here

     /**
     * Specify data which should be serialized to JSON
     * @link http://php.net/manual/en/jsonserializable.jsonserialize.php
     * @return mixed data which can be serialized by <b>json_encode</b>,
     * which is a value of any type other than a resource.
     * @since 5.4.0
     */
    function jsonSerialize()
    {
        return [
            "title"     => $this->getTitle(),
            "id"        => $this->getId(),
            "firstname" => $this->getFirstname(),
            "lastname"  => $this->getLastname(),

            //This is the problem right here. Circular reference.
            "medias"    => $this->getAuthorsMedia()->map(function($object){
                return $object->getMedia();
            })
        ];
    }
  }

What I've tried:

My Entities implement JsonSerializable interface so I define what attributes are returned (Which is what JsonSerializeNormalizer requires). This works completely when I remove the "medias" property in the Author's class, everything works.

Here is how I use my serliazer with my normalizer.

/**
 * @Route("/media")
 * Class MediaController
 * @package BackBundle\Controller\Media
 */
class MediaController extends Controller
{

    /**
     * @Route("")
     * @Method({"GET"})
     */
    public function listAction(){
        /** @var MediaService $mediaS */
        $mediaS= $this->get("app.media");

        /** @var array $data */
        $data = $mediaS->getAll();
        $normalizer = new JsonSerializableNormalizer();
        $normalizer->setCircularReferenceLimit(1);
        $normalizer->setCircularReferenceHandler(function($object){
            return $object->getId();
        });
        $serializer = new Serializer([$normalizer], [new JsonEncoder()]);

        $json = $serializer->serialize($data, 'json');

        return new Response($json);
    }

}

Github issue opened

Gonna answered 5/9, 2017 at 15:34 Comment(3)
Did you try adding metadata and enabling the enable_max_depth setting to work around this? See "Handling Serialization Depth"Enlarger
How many levels deep do you want to serialize?Enlarger
enable_max_depth is used by anything extending AbstractObjectNormalizer. JsonSerializableNormalizer inherits directly from AbstractNormalizer, and doesn't use that option at all.Sisterhood
S
1

I tried to reproduce your error, and for me everything worked as expected (see code samples below).

So, your setCircularReferenceHandler() works fine.

Maybe try to run my code, and update it with your real entities and data sources step by step, until you see what causes the error.

Test (instead of your controller):

class SerializerTest extends \PHPUnit\Framework\TestCase
{
    public function testIndex()
    {
        $media = new Media();
        $author = new Author();

        $media->setAuthor($author);
        $author->addMedia($media);
        $data = [$media];

        $normalizer = new JsonSerializableNormalizer();
        $normalizer->setCircularReferenceLimit(1);
        $normalizer->setCircularReferenceHandler(function($object){
            /** @var Media $object */
            return $object->getId();
        });
        $serializer = new Serializer([$normalizer], [new JsonEncoder()]);

        $json = $serializer->serialize($data, 'json');

        $this->assertJson($json);
        $this->assertCount(1, json_decode($json));
    }
}

Media entity

class Media implements \JsonSerializable
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var Author
     *
     * @ORM\ManyToOne(targetEntity="Author", inversedBy="medias")
     * @ORM\JoinColumn(name="author_id", referencedColumnName="id")
     */
    private $author;

    /**
     * {@inheritdoc}
     */
    function jsonSerialize()
    {
        return [
            "id"        => $this->getId(),
            "author"    => $this->getAuthor(),
        ];
    }
    //todo: here getter and setters, generated by doctrine
}

Author entity

class Author implements \JsonSerializable
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var Media[]
     *
     * @ORM\OneToMany(targetEntity="Media", mappedBy="author")
     */
    private $medias;

    /**
     * {@inheritdoc}
     */
    function jsonSerialize()
    {
        return [
            "id"        => $this->getId(),
            "medias"    => $this->getMedias(),
        ];
    }
    //todo: here getter and setters, generated by doctrine
}
Subtraction answered 9/9, 2017 at 23:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.