I'm currently learning how to implement a relatively simple API using Symfony 3 (with FOSRestBundle) and JMS Serializer. I've been trying recently to implement the ability to specify, as a consuming client, which fields should be returned within a response (both fields within the requested entity and relationships). For example;
/posts
with no include query string would return all Post
entity properties (e.g. title, body, posted_at etc) but no relationships./posts?fields[]=id&fields[]=title
would return only the id and title for posts (but again, no relationships)/posts?include[]=comment
would include the above but with the Comment
relationship (and all of its properties)/posts?include[]=comment&include[]=comment.author
would return as above, but also include the author within each commentIs this a sane thing to try and implement? I've been doing quite a lot of research on this recently and I can't see I can 1) restrict the retrieval of individual fields and 2) only return related entities if they have been explicitly asked for.
I have had some initial plays with this concept, however even when ensuring that my repository only returns the Post entity (i.e. no comments), JMS Serializer seems to trigger the lazy loading of all related entities and I can't seem to stop this. I have seen a few links such as this example however the fixes don't seem to work (for example in that link, the commented out $object->__load()
call is never reached anyway in the original code.
I have implemented a relationship-based example of this using JMSSerializer's Group
functionality but it feels weird having to do this, when I would ideally be able to build up a Doctrine Querybuilder instance, dynamically adding andWhere()
calls and have the serializer just return that exact data without loading in relationships.
I apologise for rambling with this but I've been stuck with this for some time, and I'd appreciate any input! Thank you.
You should be able to achieve what you want with the Groups
exclusion strategy.
For example, your Post
entity could 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;
}
Like this, if your controller action returns a View
using serializerGroups={"all"}
, the Response will contains all fields of your entity.
If it uses serializerGroups={"withFooAssociation"}
, the response will contains the foos[]
association entries and their exposed fields.
And, if it uses serializerGroups={"withoutAssociation"}
, the foos
association will be excluded by the serializer, and so it will not be rendered.
To exclude properties from the target entity of the association (Foo
entity), use the same Groups
on the target entity properties in order to get a chained serialisation strategy.
When your serialization structure is good, you can dynamically set the serializerGroups
in your controller, in order to use different groups depending on the include
and fields
params (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 correctly understood your question and that this will be sufficient for help you.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With