96

I'm developing game app and using Symfony 2.0. I have many AJAX requests to the backend. And more responses is converting entity to JSON. For example:

class DefaultController extends Controller
{ 
 public function launchAction()
 { 
 $user = $this->getDoctrine()
 ->getRepository('UserBundle:User') 
 ->find($id);
 // encode user to json format
 $userDataAsJson = $this->encodeUserDataToJson($user);
 return array(
 'userDataAsJson' => $userDataAsJson
 ); 
 }
 private function encodeUserDataToJson(User $user)
 {
 $userData = array(
 'id' => $user->getId(),
 'profile' => array(
 'nickname' => $user->getProfile()->getNickname()
 )
 );
 $jsonEncoder = new JsonEncoder(); 
 return $jsonEncoder->encode($userData, $format = 'json');
 }
}

And all my controllers do the same thing: get an entity and encode some of its fields to JSON. I know that I can use normalizers and encode all entitities. But what if an entity has cycled links to other entity? Or the entities graph is very big? Do you have any suggestions?

I think about some encoding schema for entities... or using NormalizableInterface to avoid cycling..,

likeitlikeit
5,6585 gold badges45 silver badges57 bronze badges
asked Jul 15, 2011 at 11:41
0

13 Answers 13

156

With php5.4 now you can do :

use JsonSerializable;
/**
* @Entity(repositoryClass="App\Entity\User")
* @Table(name="user")
*/
class MyUserEntity implements JsonSerializable
{
 /** @Column(length=50) */
 private $name;
 /** @Column(length=50) */
 private $login;
 public function jsonSerialize()
 {
 return array(
 'name' => $this->name,
 'login'=> $this->login,
 );
 }
}

And then call

json_encode(MyUserEntity);
answered Jul 4, 2012 at 8:23
4
  • 4
    This is a great solution if you are tying to keep your dependencies on other bundles to a minimum... Commented Jan 5, 2015 at 23:55
  • 7
    What about linked entities? Commented Apr 4, 2016 at 12:49
  • 8
    This does not seem to work with entity collections (ie: OneToMany relations) Commented Jun 14, 2016 at 15:28
  • 3
    This violates the single responsibility principle and is no good if your entities are auto generated by doctrine Commented Apr 16, 2018 at 16:21
85

Another option is to use the JMSSerializerBundle. In your controller you then do

$serializer = $this->container->get('serializer');
$reports = $serializer->serialize($doctrineobject, 'json');
return new Response($reports); // should be $reports as $doctrineobject is not serialized

You can configure how the serialization is done by using annotations in the entity class. See the documentation in the link above. For example, here's how you would exclude linked entities:

 /**
* Iddp\RorBundle\Entity\Report
*
* @ORM\Table()
* @ORM\Entity(repositoryClass="Iddp\RorBundle\Entity\ReportRepository")
* @ExclusionPolicy("None")
*/
....
/**
* @ORM\ManyToOne(targetEntity="Client", inversedBy="reports")
* @ORM\JoinColumn(name="client_id", referencedColumnName="id")
* @Exclude
*/
protected $client;
debianek
5891 gold badge9 silver badges30 bronze badges
answered Dec 13, 2011 at 15:43
5
  • 7
    you need to add use JMS\SerializerBundle\Annotation\ExclusionPolicy; use JMS\SerializerBundle\Annotation\Exclude; in your entity and install JMSSerializerBundle in order for this to work Commented Jul 7, 2012 at 14:21
  • 3
    Works great if you change it to: return new Response($reports); Commented Feb 3, 2013 at 7:21
  • 7
    Since the annotations have been moved out of the bundle, the correct use statements are now: use JMS\Serializer\Annotation\ExclusionPolicy; use JMS\Serializer\Annotation\Exclude; Commented Apr 6, 2013 at 20:02
  • 3
    The documentation for Doctrine says not to serialize objects or serialize with great care. Commented May 31, 2013 at 22:36
  • I did not even need to install JMSSerializerBundle. Your code worked without requiring JMSSerializerBundle. Commented Aug 22, 2020 at 18:12
41

You can automatically encode into Json, your complex entity with:

use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
$serializer = new Serializer(array(new GetSetMethodNormalizer()), array('json' => new 
JsonEncoder()));
$json = $serializer->serialize($entity, 'json');
Celmaun
24.9k9 gold badges73 silver badges58 bronze badges
answered Jul 15, 2011 at 15:55
6
  • 3
    Thanks, but I have Player entity that has link to Game entities collection and every Game entity have link to players that played in it. Something like this. And do you think GetSetMethodNormalizer will correctly work (it uses recursive algorithm)? Commented Jul 15, 2011 at 21:11
  • 2
    Yes it's recursive and that was my problem in my case. So, for specific entities, you can use the CustomNormalizer and its NormalizableInterface as you seem know. Commented Jul 17, 2011 at 13:42
  • 2
    When I tried this I got "Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 64 bytes) in /home/jason/pressbox/vendor/symfony/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php on line 44". I wonder why? Commented Jun 22, 2012 at 15:13
  • 1
    when I tried I got below exception.. Fatal error: Maximum function nesting level of '100' reached, aborting! in C:\wamp\www\myapp\application\libraries\doctrine\Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer.php on line 223 Commented Oct 2, 2013 at 18:26
  • 1
    @user2350626, see stackoverflow.com/questions/4293775/… Commented Oct 3, 2013 at 9:23
14

To complete the answer: Symfony2 comes with a wrapper around json_encode: Symfony/Component/HttpFoundation/JsonResponse

Typical usage in your Controllers:

...
use Symfony\Component\HttpFoundation\JsonResponse;
...
public function acmeAction() {
...
return new JsonResponse($array);
}
Dharman
33.9k27 gold badges103 silver badges153 bronze badges
answered Jul 9, 2013 at 15:38
10

I found the solution to the problem of serializing entities was as follows:

#config/config.yml
services:
 serializer.method:
 class: Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer
 serializer.encoder.json:
 class: Symfony\Component\Serializer\Encoder\JsonEncoder
 serializer:
 class: Symfony\Component\Serializer\Serializer
 arguments:
 - [@serializer.method]
 - {json: @serializer.encoder.json }

in my controller:

$serializer = $this->get('serializer');
$entity = $this->get('doctrine')
 ->getRepository('myBundle:Entity')
 ->findOneBy($params);
$collection = $this->get('doctrine')
 ->getRepository('myBundle:Entity')
 ->findBy($params);
$toEncode = array(
 'response' => array(
 'entity' => $serializer->normalize($entity),
 'entities' => $serializer->normalize($collection)
 ),
);
return new Response(json_encode($toEncode));

other example:

$serializer = $this->get('serializer');
$collection = $this->get('doctrine')
 ->getRepository('myBundle:Entity')
 ->findBy($params);
$json = $serializer->serialize($collection, 'json');
return new Response($json);

you can even configure it to deserialize arrays in http://api.symfony.com/2.0

answered Dec 21, 2011 at 16:59
1
6

I just had to solve the same problem: json-encoding an entity ("User") having a One-To-Many Bidirectional Association to another Entity ("Location").

I tried several things and I think now I found the best acceptable solution. The idea was to use the same code as written by David, but somehow intercept the infinite recursion by telling the Normalizer to stop at some point.

I did not want to implement a custom normalizer, as this GetSetMethodNormalizer is a nice approach in my opinion (based on reflection etc.). So I've decided to subclass it, which is not trivial at first sight, because the method to say if to include a property (isGetMethod) is private.

But, one could override the normalize method, so I intercepted at this point, by simply unsetting the property that references "Location" - so the inifinite loop is interrupted.

In code it looks like this:

class GetSetMethodNormalizer extends \Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer {
 public function normalize($object, $format = null)
 {
 // if the object is a User, unset location for normalization, without touching the original object
 if($object instanceof \Leonex\MoveBundle\Entity\User) {
 $object = clone $object;
 $object->setLocations(new \Doctrine\Common\Collections\ArrayCollection());
 }
 return parent::normalize($object, $format);
 }
} 
answered Jul 27, 2011 at 17:41
1
  • 1
    I wonder how easy it'd be to generalize this, so that 1. never any need to touch the Entity classes, 2. Not just blank the "Locations", but every Collections type field which potentially maps to other Entites. I.e. no internal/advance knowledge of Ent required to serialize it, recursion-free. Commented Mar 30, 2015 at 16:27
6

I had the same problem and I chosed to create my own encoder, which will cope by themself with recursion.

I created classes which implements Symfony\Component\Serializer\Normalizer\NormalizerInterface, and a service which holds every NormalizerInterface.

#This is the NormalizerService
class NormalizerService 
{
 //normalizer are stored in private properties
 private $entityOneNormalizer;
 private $entityTwoNormalizer;
 public function getEntityOneNormalizer()
 {
 //Normalizer are created only if needed
 if ($this->entityOneNormalizer == null)
 $this->entityOneNormalizer = new EntityOneNormalizer($this); //every normalizer keep a reference to this service
 return $this->entityOneNormalizer;
 }
 //create a function for each normalizer
 //the serializer service will also serialize the entities 
 //(i found it easier, but you don't really need it)
 public function serialize($objects, $format)
 {
 $serializer = new Serializer(
 array(
 $this->getEntityOneNormalizer(),
 $this->getEntityTwoNormalizer()
 ),
 array($format => $encoder) );
 return $serializer->serialize($response, $format);
}

An example of a Normalizer :

use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
class PlaceNormalizer implements NormalizerInterface {
private $normalizerService;
public function __construct($normalizerService)
{
 $this->service = normalizerService;
}
public function normalize($object, $format = null) {
 $entityTwo = $object->getEntityTwo();
 $entityTwoNormalizer = $this->service->getEntityTwoNormalizer();
 return array(
 'param' => object->getParam(),
 //repeat for every parameter
 //!!!! this is where the entityOneNormalizer dealt with recursivity
 'entityTwo' => $entityTwoNormalizer->normalize($entityTwo, $format.'_without_any_entity_one') //the 'format' parameter is adapted for ignoring entity one - this may be done with different ways (a specific method, etc.)
 );
}
}

In a controller :

$normalizerService = $this->get('normalizer.service'); //you will have to configure services.yml
$json = $normalizerService->serialize($myobject, 'json');
return new Response($json);

The complete code is here : https://github.com/progracqteur/WikiPedale/tree/master/src/Progracqteur/WikipedaleBundle/Resources/Normalizer

likeitlikeit
5,6585 gold badges45 silver badges57 bronze badges
answered Aug 11, 2012 at 12:07
6

in Symfony 2.3

/app/config/config.yml

framework:
 # сервис конвертирования объектов в массивы, json, xml и обратно
 serializer:
 enabled: true
services:
 object_normalizer:
 class: Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer
 tags:
 # помечаем к чему относится этот сервис, это оч. важно, т.к. иначе работать не будет
 - { name: serializer.normalizer }

and example for your controller:

/**
 * Поиск сущности по ИД объекта и ИД языка
 * @Route("/search/", name="orgunitSearch")
 */
public function orgunitSearchAction()
{
 $array = $this->get('request')->query->all();
 $entity = $this->getDoctrine()
 ->getRepository('IntranetOrgunitBundle:Orgunit')
 ->findOneBy($array);
 $serializer = $this->get('serializer');
 //$json = $serializer->serialize($entity, 'json');
 $array = $serializer->normalize($entity);
 return new JsonResponse( $array );
}

but the problems with the field type \DateTime will remain.

answered Nov 28, 2013 at 15:04
6

This is more an update (for Symfony v:2.7+ and JmsSerializer v:0.13.*@dev), so to avoid that Jms tries to load and serialise the whole object graph ( or in case of cyclic relation ..)

Model:

use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation\ExclusionPolicy; 
use JMS\Serializer\Annotation\Exclude; 
use JMS\Serializer\Annotation\MaxDepth; /* <=== Required */
/**
 * User
 *
 * @ORM\Table(name="user_table")
///////////////// OTHER Doctrine proprieties //////////////
 */
 public class User
{
 /**
 * @var integer
 *
 * @ORM\Column(name="id", type="integer")
 * @ORM\Id
 * @ORM\GeneratedValue(strategy="AUTO")
 */
 protected $id;
 /**
 * @ORM\ManyToOne(targetEntity="FooBundle\Entity\Game")
 * @ORM\JoinColumn(nullable=false)
 * @MaxDepth(1)
 */
 protected $game;
 /*
 Other proprieties ....and Getters ans setters
 ......................
 ......................
 */

Inside an Action:

use JMS\Serializer\SerializationContext;
 /* Necessary include to enbale max depth */
 $users = $this
 ->getDoctrine()
 ->getManager()
 ->getRepository("FooBundle:User")
 ->findAll();
 $serializer = $this->container->get('jms_serializer');
 $jsonContent = $serializer
 ->serialize(
 $users, 
 'json', 
 SerializationContext::create()
 ->enableMaxDepthChecks()
 );
 return new Response($jsonContent);
answered Sep 7, 2014 at 16:42
5

If you are using Symfony 2.7 or above, and don't want to include any additional bundle for serializing, maybe you can follow this way to seialize doctrine entities to json -

  1. In my (common, parent) controller, I have a function that prepares the serializer

    use Symfony\Component\Serializer\Encoder\JsonEncoder;
    use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
    use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
    use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
    use Symfony\Component\Serializer\Serializer;
    // -----------------------------
    /**
     * @return Serializer
     */
    protected function _getSerializer()
    { 
     $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
     $normalizer = new ObjectNormalizer($classMetadataFactory);
     return new Serializer([$normalizer], [new JsonEncoder()]);
    }
    
  2. Then use it to serialize Entities to JSON

    $this->_getSerializer()->normalize($anEntity, 'json');
    $this->_getSerializer()->normalize($arrayOfEntities, 'json');
    

Done!

But you may need some fine tuning. For example -

answered Jan 6, 2016 at 9:07
0
4

When you need to create a lot of REST API endpoints on Symfony, the best way is to use the following stack of bundles:

  1. JMSSerializerBundle for the serialization of Doctrine entities
  2. FOSRestBundle bundle for response view listener. Also, it can generate definitions of routes based on controller/action name.
  3. NelmioApiDocBundle to auto-generate online documentation and Sandbox(which allows testing endpoint without any external tool).

When you configure everything properly, you entity code will look like this:

use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as JMS;
/**
 * @ORM\Table(name="company")
 */
class Company
{
 /**
 * @var string
 *
 * @ORM\Column(name="name", type="string", length=255)
 *
 * @JMS\Expose()
 * @JMS\SerializedName("name")
 * @JMS\Groups({"company_overview"})
 */
 private $name;
 /**
 * @var Campaign[]
 *
 * @ORM\OneToMany(targetEntity="Campaign", mappedBy="company")
 * 
 * @JMS\Expose()
 * @JMS\SerializedName("campaigns")
 * @JMS\Groups({"campaign_overview"})
 */
 private $campaigns;
}

Then, code in controller:

use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use FOS\RestBundle\Controller\Annotations\View;
class CompanyController extends Controller
{
 /**
 * Retrieve all companies
 *
 * @View(serializerGroups={"company_overview"})
 * @ApiDoc()
 *
 * @return Company[]
 */
 public function cgetAction()
 {
 return $this->getDoctrine()->getRepository(Company::class)->findAll();
 }
}

The benefits of such a set up are:

  • @JMS\Expose() annotations in the entity can be added to simple fields, and to any type of relations. Also, there is the possibility to expose the result of some method execution (use annotation @JMS\VirtualProperty() for that)
  • With serialization groups, we can control exposed fields in different situations.
  • Controllers are very simple. The action method can directly return an entity or array of entities, and they will be automatically serialized.
  • And @ApiDoc() allows testing the endpoint directly from the browser, without any REST client or JavaScript code
Dhia Djobbi
1,2882 gold badges21 silver badges38 bronze badges
answered Sep 13, 2016 at 15:32
1

Now you can also use Doctrine ORM Transformations to convert entities to nested arrays of scalars and back

answered Nov 20, 2016 at 4:29
0

The accepted answer is correct but if You'll need to serialize a filtered subset of an Entity , json_encode is enough:

Consider this example:

class FileTypeRepository extends ServiceEntityRepository 
{
 const ALIAS = 'ft';
 const SHORT_LIST = 'ft.name name';
 public function __construct(ManagerRegistry $registry)
 {
 parent::__construct($registry, FileType::class);
 }
 public function getAllJsonFileTypes() 
 {
 return json_encode($this->getAllFileTypes());
 } 
 /**
 * @return array
 */
 public function getAllFileTypes()
 {
 $query = $this->createQueryBuilder(self::ALIAS);
 $query->select(self::SHORT_LIST);
 return $query->getQuery()->getResult();
 }
}
/** THIS IS ENOUGH TO SERIALIZE AN ARRAY OF ENTITIES SINCE the doctrine SELECT will remove complex data structures from the entities itself **/
json_encode($this->getAllFileTypes()); 

Short note: Tested at least on Symfony 5.1

answered Mar 9, 2021 at 15:02

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.