Best Practices for Implementing Field Whitelisting with Symfony / FosRestBundle / JMS Serializer

I am currently learning how to implement a relatively simple API using Symfony 3 (with FOSRestBundle) and JMS Serializer. Recently, I tried to realize the ability to indicate as a consumer client which fields should be returned in response (both fields in the requested entity and relationship). For instance:

  • /posts without an include query string will return all properties of the Post object (e.g. title, body, published_at, etc.), but without links .
  • /posts?fields[]=id&fields[]=title will only return the id and title for posts (but again, without links )
  • /posts?include[]=comment will include the above, but with the Comment relation (and all its properties)
  • /posts?include[]=comment&include[]=comment.author will return as above, but will also include the author in each comment

Is that a reasonable thing to try and implement? Recently, I have done quite a lot of research, and I do not see that I can: 1) limit the search for individual fields and 2) return only related objects if they were explicitly requested.

I had some initial games with this concept, however, even if my repository returns a Post object (i.e. no comment), the JMS Serializer seems to cause lazy loading of all related objects, and I can't seem to stop it . I saw several links, such as this example , however the corrections do not seem to work (for example, in this link, the commented call to $object->__load() never reached in any way in the source code.

I applied a relationship-based example using the JMSSerializer Group functionality , but it's weird when I need to do this, when I could ideally create an instance of Doctrine Querybuilder by dynamically adding andWhere() calls and so that the serializer simply returns accurate data without loading in the relationship .

I apologize for this, but I was stuck with this for a while, and I would appreciate any input! Thanks.

+5
source share
1 answer

You can achieve what you want with the Groups exclusion strategy.

For example, your Post object might look like this:

 use JMS\Serializer\Annotation as JMS; /** * @JMS\ExclusionPolicy("all") */ class Post { /** * @ORM\Id * @ORM\GeneratedValue(strategy="IDENTITY") * @ORM\Column(type="integer") * * @JMS\Expose * @JMS\Groups({"all", "withFooAssociation", "withoutAssociations"}) */ private $id; /** * @ORM\Column(type="string") * * @JMS\Expose * @JMS\Groups({"all", "withFooAssociation", "withoutAssociations"}) */ private $title; /** * @JMS\Expose * @JMS\Groups({"all", "withFooAssociation"}) * * @ORM\OneToMany(targetEntity="Foo", mappedBy="post") */ private $foos; } 

Similarly, if your controller action returns a View using serializerGroups={"all"} , the response will contain all the fields of your entity.

If it uses serializerGroups={"withFooAssociation"} , the response will contain foos[] records and their affected fields.

And if it uses serializerGroups={"withoutAssociation"} , the foos association foos be excluded by the serializer, and therefore it will not be displayed.

To exclude properties from the association target ( Foo object), use the same Groups for the properties of the target entity to get a serialization strategy.

When your serialization structure is good, you can dynamically install serializerGroups in your controller to use different groups depending on the include and fields parameters (i.e. /posts?fields[]=id&fields[]=title ). Example:

 // PostController::getAction use JMS\Serializer\SerializationContext; use JMS\Serializer\SerializerBuilder; $serializer = SerializerBuilder::create()->build(); $context = SerializationContext::create(); $groups = []; // Assuming $request contains the "fields" param $fields = $request->query->get('fields'); // Do this kind of check for all fields in $fields if (in_array('foos', $fields)) { $groups[] = 'withFooAssociation'; } // Tell the serializer to use the groups previously defined $context->setGroups($groups); // Serialize the data $data = $serializer->serialize($posts, 'json', $context); // Create the view $view = View::create()->setData($data); return $this->handleView($view); 

I hope that I understood your question correctly and that this will be enough to help you.

+2
source

Source: https://habr.com/ru/post/1245740/


All Articles