Search code examples
phpsymfonyjmsserializerbundlejms-serializer

JMSSerializer intersection of grouped properties


I have entity as following:

class A
{
   /**
    * @JMS\Groups({"writable", "other"})
    */
   private $varA;

   /**
    * @JMS\Groups({"writable"})
    */
   private $varB;

   /**
    * @JMS\Groups({"other"})
    */
   private $varC;
}

I want to make serializer to generate output for properties that exists in BOTH groups so in simplier words I need an intersection of grouped properties.

$context = SerializationContext::create()->setGroups(['writable' ,'other']);
$serializer->serialize(new A(), 'json', $context);

The code above should output only variable $varA because it has both groups defined.

How to achieve it? The only thing that comes to my mind is to extend GroupExclusionStategy that comes from JMSSerializer but maybe there's better way?


Solution

  • I've dag into the code of jms and I've found that setGroups uses GroupsExclusionStrategy but there are also different strategies and ExclusionStrategyInterface. So I've implemented this interface into my own

    <?php
    
    namespace AppBundle\Jms\Serializer;
    
    use JMS\Serializer\Context;
    use JMS\Serializer\Exclusion\ExclusionStrategyInterface;
    use JMS\Serializer\Metadata\ClassMetadata;
    use JMS\Serializer\Metadata\PropertyMetadata;
    
    /**
     * Class IntersectGroupsExclusionStrategy
     * @package AppBundle\Jms
     */
    class IntersectGroupsExclusionStrategy implements ExclusionStrategyInterface
    {
        /**
         * @var array
         */
        private $groups;
    
        /**
         * IntersectGroupsExclusionStrategy constructor.
         * @param array $groups
         */
        public function __construct(array $groups)
        {
            $this->setGroups($groups);
        }
    
        /**
         * {@inheritDoc}
         */
        public function shouldSkipProperty(PropertyMetadata $property, Context $navigatorContext)
        {
             if (is_array($this->groups) && is_array($property->groups)) {
                return !(!empty($this->groups) && array_intersect($this->groups, $property->groups) === $this->groups);
             }
    
             return false;
        }
    
        /**
         * Whether the class should be skipped.
         *
         * @param ClassMetadata $metadata
         *
         * @return boolean
         */
        public function shouldSkipClass(ClassMetadata $metadata, Context $context)
        {
            return false;
        }
    
        /**
         * @param array $groups
         * @return $this
         */
        public function setGroups(array $groups)
        {
            $this->groups = $groups;
    
            return $this;
        }
    }
    

    When serializing instead of using setGroups I've used

    $intersectExclusionStrategy = new IntersectGroupsExclusionStrategy($groups);
    $serializationContext = SerializationContext::create();
    $serializationContext->addExclusionStrategy($intersectExclusionStrategy);
    

    Where $groups held values ['writable' ,'other'].

    It worked pretty well.

    I've also created test for it if someone needed.

    <?php
    use AppBundle\Jms\Serializer\IntersectGroupsExclusionStrategy;
    
    class IntersectGroupsExclusionStrategyTest extends PHPUnit_Framework_TestCase
    {
        public function testShouldSkipPropertyGroups()
        {
            $intersectExclusionStrategy = new IntersectGroupsExclusionStrategy(['group_a', 'group_b']);
    
            $propertyMetaData = $this->getMock('JMS\Serializer\Metadata\PropertyMetadata', [], [], '', false);
            $context = $this->getMock('JMS\Serializer\Context', [], [], '', false);
    
            $propertyMetaData->groups = ['group_a', 'group_b', 'group_c'];
    
            $this->assertNotTrue($intersectExclusionStrategy->shouldSkipProperty($propertyMetaData, $context));
    
            $propertyMetaData->groups = ['group_a', 'group_b'];
    
            $this->assertNotTrue($intersectExclusionStrategy->shouldSkipProperty($propertyMetaData, $context));
    
        }
    
        public function testShouldNotSkipPropertyGroups()
        {
            $intersectExclusionStrategy = new IntersectGroupsExclusionStrategy(['group_a', 'group_b']);
    
            $propertyMetaData = $this->getMock('JMS\Serializer\Metadata\PropertyMetadata', [], [], '', false);
            $context = $this->getMock('JMS\Serializer\Context', [], [], '', false);
    
            $propertyMetaData->groups = ['group_a', 'group_c'];
    
            $this->assertTrue($intersectExclusionStrategy->shouldSkipProperty($propertyMetaData, $context));
    
            $propertyMetaData->groups = ['group_d', 'group_e'];
    
            $this->assertTrue($intersectExclusionStrategy->shouldSkipProperty($propertyMetaData, $context));
    
            $intersectExclusionStrategy = new IntersectGroupsExclusionStrategy([]);
    
            $this->assertTrue($intersectExclusionStrategy->shouldSkipProperty($propertyMetaData, $context));
        }
    
        public function testShouldSkipClassReturnsFalse()
        {
            $intersectExclusionStrategy = new IntersectGroupsExclusionStrategy(['group_a', 'group_b']);
    
            $classMetaData = $this->getMock('JMS\Serializer\Metadata\ClassMetadata', [], [], '', false);
            $context = $this->getMock('JMS\Serializer\Context', [], [], '', false);
    
            $this->assertFalse($intersectExclusionStrategy->shouldSkipClass($classMetaData, $context));
        }
    }