Пересечение JMSSerializer сгруппированных свойств

У меня есть следующая сущность:

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, потому что в нем определены обе группы.

Как этого достичь? Единственное, что приходит мне в голову, это расширить GroupExclusionStategy, который исходит от JMSSerializer, но, может быть, есть лучший способ?


person Robert    schedule 23.11.2016    source источник
comment
вероятно, вы хотите объединение, а не пересечение, не так ли?   -  person Matteo    schedule 23.11.2016
comment
Нет, не знаю. Объединение — это когда переменная может быть либо в группе writeble, либо в группе other, а пересечение — когда она должна быть в обеих группах.   -  person Robert    schedule 23.11.2016


Ответы (2)


Возможно, простым решением может быть добавление другого имени группы («эксклюзивное») к свойству $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

Я оставляю здесь реализацию с использованием стратегии флага «строгая»:

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

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

Выход:

{"varA":"foo"}
person yceruto    schedule 23.11.2016
comment
Я знаю о создании групп varAVarB, но это грязное решение, которого я хотел избежать. Я знаю, что делает GroupsExclusionStrategy. Я сделал свой класс на основе этого. Чего я не знал, так это того, что вы можете просто использовать метод addExclusionStrategy в контексте, и я даже искал изменения контейнера в пакете, но это нельзя было заменить пакетом. В любом случае спасибо за помощь. - person Robert; 24.11.2016
comment
Также документация не очень глубокая, вам нужно часто заглядывать в код, чтобы понять, как он работает;/ - person Robert; 24.11.2016

Я копался в коде 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));
    }
} 
person Robert    schedule 23.11.2016