JMSSerializer 分组属性的交集

JMSSerializer intersection of grouped properties

我有如下实体:

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

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

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

我想让序列化器为两个组中都存在的属性生成输出,所以用更简单的话来说,我需要分组属性的交集。

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

上面的代码应该只输出变量 $varA 因为它定义了两个组。

如何实现?我唯一想到的是扩展来自 JMSSerializer 的 GroupExclusionStategy,但也许有更好的方法?

我仔细研究了jms的代码,发现setGroups使用了GroupsExclusionStrategy,但也有不同的策略和ExclusionStrategyInterface。所以我已经将这个接口实现到我自己的

<?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;
    }
}

当序列化而不是使用 setGroups 我使用

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

其中 $groups 包含值 ['writable' ,'other']

效果很好。

如果有人需要,我还为它创建了测试。

<?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));
    }
} 

也许一个简单的解决方案是将另一个组名 ("exclusive") 添加到 $varA 属性:

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

其次是:

$context = SerializationContext::create()->setGroups('exclusive');

但这可能只是一个示例用例。换句话说,应该创建一个 CustomGroupsExclusionStrategy().


默认情况下 GroupsExclusionStrategy() 检查是否有任何 属性 组被包含在请求的组中:

private function shouldSkipUsingGroups(PropertyMetadata $property, $groups)
{
    foreach ($property->groups as $group) {
        if (in_array($group, $groups)) {
            return false;
        }
    }

    return true;
}

因此,要做到这一点,您需要将此更改为:

private function shouldSkipUsingGroups(PropertyMetadata $property, $groups)
{
    foreach ($groups as $group) {
        if (!in_array($group, $property->groups)) {
            return true;
        }
    }

    return false;
}

因此所有请求的组都必须包含在 属性 组中。

解决方案:

https://gist.github.com/yceruto/90b1ac46c8e33d51ec21079725949f77

我在这里留下一个使用标志 "strict" 策略的实现:

$context = SerializationContext::create();
$context->addExclusionStrategy(
    new CustomGroupsExclusionStrategy(['writable', 'other'], true)
);

$json = $this->get('serializer')->serialize(new A(), 'json', $context);

输出:

{"varA":"foo"}