While working with mapped associations in Doctrine I came upon the problem of serializing proxies. Imagine you have a REST service that returns some objects with an associated collection. E.g. a list of dishes that have a list of ingredients associated.

The problem: serializing proxy objects and their special properties

Doctrine is going to create a proxy class for the mapped ingredients. You can find more information about those proxy objects in the Doctrine documentation. The proxy objects themselves are a great helper when combined with lazy loading. Unfortunately they mess up our REST service because we can not just serialize the object tree we are loading from the database, without adding useless data to the result!

To give you an example, imagine this method in our API controller:

    <?php
    // omitting class and packages for simplicity

    /**
     * @Route("/apiroute")
     */
    public function getDishes() {
        // EntityRepository from Doctrine
        $dishes = $this->repository->findAll(); 

        // the default Symfony serializer
        $data = $this->serializer->serialize(array('result' => $dishes), 'json');
        $response = new JsonResponse();
        $response->setContent($data);
        return $response;
    }

This controller might result in something like that:

    {
    "result": [{
        "id": 1,
        "name": "French Toast Bake",
        "prepTime": 60,
        "serves": 4,
        "ingredients": [{
            "id": 1,
            "amount": "8",
            "type": {
                "id": 1,
                "singular": "egg",
                "plural": "eggs",
                "__initializer__": null,
                "__cloner__": null,
                "__isInitialized__": true
            }
        }]
    }]
    }

As you can see the mapping of Dish->Ingredient works fine, but when we map Dish->Ingredient->IngredientType we get those weird “__initializer__” properties. That is a proxy class. How do we prevent our REST service from throwing that out?

Working on a solution: adding the @Groups annotation to our entites

As you can see in the controller code snippet we are using a serialzer. This one is from the Symfony library. And it actually has some information about excluding properties from serializing. The recommended way is using the @Groups annotation. Check out the Symfony documentation for more information about the @Groups annotation.

We start by adding the annotation to our entities:

    <?php

    namespace AppBundle\Entity;

    use Doctrine\ORM\Mapping as ORM;
    use Symfony\Component\Serializer\Annotation\Groups;


    /**
     * Dish
     *
     * @ORM\Table(name="dish")
     * @ORM\Entity(repositoryClass="AppBundle\Repository\DishRepository")
     */
    class Dish
    {
        /**
         * @Groups({"rest"})
         * @var int
         *
         * @ORM\Column(name="id", type="integer")
         * @ORM\Id
         * @ORM\GeneratedValue(strategy="AUTO")
         */
        private $id;

        /**
         * @Groups({"rest"})
         * @var string
         *
         * @ORM\Column(name="name", type="string", length=255)
         */
        private $name;

        /**
         * @Groups({"rest"})
         * @var integer
         * @ORM\Column(name="prepTime", type="smallint")
         */
        private $prepTime;

        /**
         * @Groups({"rest"})
         * @var integer
         * @ORM\Column(name="serves", type="smallint")
         */
        private $serves;

        /**
         * @Groups({"rest"})
         * @ORM\ManyToMany(targetEntity="IngredientInstance")
         */
        private $ingredients;

    }

Add the annotation to the referenced entities properties too!

You can of cause add more groups, for example in cases were you’d like to serve only parts of your entities. E.g. a slim list of dishes without ingredients. I skip the Groups annotation for the id property of all subelements like ingredient, because I do not have a use case were I’d like to retrieve just one ingredient.

Working on a solution: Using a newly configured serializer

Ok so far we did the following:

  • Use the Symfony serializer to serialize our data for the REST service
  • Add the @Groups annotation to all fields in all required entities

Now we have to tell the serializer about our groups!

Before we start: I always declare my controller as a service! Therefore I rely on dependency injection. This solution might look different if you extend Symfony’s Controller class.

The standard Serializer that is injected by Symfony is already configured to use the @Groups annotation. This is part of my services.yml in case you need it:

    services:
      dish_controller:
        class: AppBundle\Controller\v1\DishController
        arguments: ["@doctrine.orm.entity_manager", "@serializer"]

Using the serializer with groups is easy:

    <?php

    // omitting class and packages for simplicity
    /**
     * @Route("/apiroute")
     */
    public function getDishes() {
        // EntityRepository from Doctrine
        $dishes = $this->repository->findAll(); 

        // the default Symfony serializer
        $data = $this->serializer->serialize(
            array('result' => $dishes), 
            'json',
            array('groups' => array('rest'))
        );

        $response = new JsonResponse();
        $response->setContent($data);
        return $response;
    }

This is it! Just add some annotations, tell the serializer about it and you are good to go.