JMSSerializerBundle serialization groups in entities with relations
Asked Answered
K

3

9

I have a problem with serializing entity with many relations using groups. I have a problem with serializing related entities this way.

Let's say I have two entities: Product and related Element.

/**
 *
 * @Serializer\ExclusionPolicy("none")
 */
class Product {

    /**
     * Primary key
     * @var integer $id
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     * 
     * @Serializer\Groups({"list","details"})
     * @Serializer\Type("integer")
     */
    protected $id;

    /**
     * @Serializer\Groups({"list","details"})
     * @Serializer\Type("string")
     */
    protected $name;

    /**
     * @ORM\Column(name="description", type="string", length=4096, nullable=true)
     * 
     * @Serializer\Groups({"details"})
     * @Serializer\Type("string")
     */
    protected $description;

    /**
     * @var ArrayCollection
     * 
     * @ORM\OneToMany(targetEntity="Madden\ProjectBundle\Entity\ProjectResource", mappedBy="project")
     * @Serializer\Groups({"details"})
     * @Serializer\Type("ArrayCollection<Element>")
     */
    protected $details1;

    /**
     * Relation to project tasks
     * @ORM\OneToMany(targetEntity="Madden\ProjectBundle\Entity\ProjectTask", mappedBy="project")
     * @Serializer\Exclude()
     * @Serializer\Type("ArrayCollection<Element>")
     */
    protected $details2;

    ...

}

Element entity has a similar structure:

/**
 *
 * @Serializer\ExclusionPolicy("none")
 */
class Element {

    /**
     * Primary key
     * @var integer $id
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     * 
     * @Serializer\Groups({"list","details"})
     * @Serializer\Type("integer")
     */
    protected $id;

    /**
     * @Serializer\Groups({"list","details"})
     * @Serializer\Type("string")
     */
    protected $name;

    /**
     * @ORM\Column(name="description", type="string", length=4096, nullable=true)
     * 
     * @Serializer\Groups({"details"})
     * @Serializer\Type("string")
     */
    protected $description;

    ...
}

My problem is that when I'm serializing Product with 'details' group entity I want to serialize only id's of Elements but as you see entity has defined same groups as Product (in case that I would need details of element object) because I want have unified groups on all my entities and prevent making hundreds of groups like 'product_details','element_details', and so on.

Is there a way to eventualy change serialization group when I visit relation or something like that? Handler maybe or something like that?

Regards and thanks for any help

Kershner answered 5/10, 2012 at 12:39 Comment(4)
I've used the product_detail/product_list etc solution, and it was pretty nice, because you always have full control of what is serialized. The downside is obviously how verbose the code gets when serializing multiple classes ... I'd also use xxx_partial/xxx_full, instead of xxx_list/xxx_details.Mcinerney
@Mcinerney Thanks for reply. Yes im using that solution right now but it has disadvantage - that groups has to be defined in every related entity in case i have many many entities in common used entities dictionaries entity groups annotation will be enormousKershner
Thats about VirtualProperty?Manhandle
it was VirtualProperty that solved my problem, to include relation id-s into serialized objects. But that's only partial solution for this problem since there can are scenarios where you want to nest related objects for performance reasonsRadiate
I
7

Unfortunately, you can't really (but keep reading ;-)), well at least not without changes to the serializer library. The culprit is that the list of groups is fixed within a GroupExclusionStrategy (which is referenced by the Context) the minute you start the serialization process. There is actually an assertion within the code that prevents modification of the exclusion strategy once the (de-)serialization is running.

But as it happens, I had the exact same problem in a project of mine as well, and I hacked the necessary changes into the serializer code. I have cleaned the code up a bit and uploaded it to Github (https://github.com/andreasferber/serializer/tree/recursion-groups).

It adds new property metadata with which you can add, remove or override the groups when descending into subobjects. With annotations it looks like this:

/**
 * @Serializer\RecursionGroups(set={"foo", "bar"}, add={"baz"}, remove={"Default"})
 */
private $myProperty;

You should be able to use XML or Yaml metadata as well, however this is untested since I don't use them and I haven't added test cases yet. Have a look at the reference documentation. Since I haven't done any optimizations yet either, if your entities are really large and deeply nested, it might have a noticable performance impact.

Please let me know if you find this useful, or if you have any suggestions, because if this isn't only needed by me, I will add some tests and try to submit it upstream.

Inlier answered 15/10, 2013 at 13:26 Comment(5)
at first glance it seems really good, I will try it out today or tomorrowRadiate
well, IMHO forking the official serializer shouldn't be the best, "general" solution for this common problem, but haven't found a good enough solution yet, apart using the VirtualProperty solutionRadiate
You should consider pull request to upstreamSelfpreservation
@Inlier This looks really useful. Any chance of getting a PR submitted to try and get this into master?Rimini
This is a feature currently cruelly lacking to the serializer and you should go for a MR. I, for one, would love to see this in the component.Crushing
C
2

A solution for this is actually described in the official documentation.

That being said the solution proposed by @aferber seems better on many points: easier to maintain, less verbose, more flexible...

Crushing answered 17/7, 2017 at 14:4 Comment(1)
for some stupid reason that solution in the official documentation doesn't workEpiphora
W
0

You need to use setGroups.

The _group suffix used in the official documentation is not needed.

$context->setGroups([
    'Default', //if you want

    // use this linked entity but show only its id
    'group_of_linked_field',
    'group_of_linked_field' => [
        'id' // you will need to define this group first
    ],

    // use this linked entity and show fields as described
    'group_of_other_linked_field',
    'group_of_other_linked_field' => [
        // just as an example
        'Default',
        'details',
    ],
]);

This does not work with addGroup or addGroups! Both of them won't accept associative arrays. setGroups is your (only?) solution.

Westfall answered 11/6, 2020 at 10:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.