symfony/serializer与对象集合一起使用 [英] symfony/serializer usage with object collection

查看:68
本文介绍了symfony/serializer与对象集合一起使用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Symfony序列化程序在非规范化时丢失了集合对象.

Symfony serializer lost collection object while denormalizing.

示例:

我们在属性中有一个带有collection的实体(我使用的是Doctrine ArrayCollection ,但是对于任何实现PHP Iterator 接口的Collection而言,它都是复制的)

We have some entity with collection in the property (I'm using Doctrine ArrayCollection but it is reproduced for any Collection which implement PHP Iterator interface)

让我们使用Symfony和JMS序列化器对其进行序列化和反序列化,并比较结果:

Let's serialize and deserialize it with Symfony and JMS serializer and compare results:

Origin:
^ App\Entity\Entity^ {#195
  -rewardItems: Doctrine\Common\Collections\ArrayCollection^ {#179
    -elements: array:2 [
      0 => App\Entity\Item^ {#224
        -type: 1
      }
      1 => App\Entity\Item^ {#180
        -type: 2
      }
    ]
  }
}

Symfony deserialized:
^ App\Entity\Entity^ {#192
  -rewardItems: array:2 [
    0 => App\Entity\Item^ {#343
      -type: 1
    }
    1 => App\Entity\Item^ {#325
      -type: 2
    }
  ]
}

Jms Deserialized
^ App\Entity\Entity^ {#369
  -rewardItems: Doctrine\Common\Collections\ArrayCollection^ {#357
    -elements: array:2 [
      0 => App\Entity\Item^ {#374
        -type: 1
      }
      1 => App\Entity\Item^ {#289
        -type: 2
      }
    ]
  }
}

请注意,原始实体和JMS反序列化实体都具有内部 ArrayCollection ,而Symfony反序列化实体丢失了 ArrayCollection 并将其替换为php array .

Pay attention, that both original entity and JMS deserialized entity has internal ArrayCollection, while Symfony deserialized entity lost ArrayCollection and replace it with php array.

我只有一个解决方案:添加我自己的Symfony规范化器.但是我在这里看到非常差的开发人员经验.对于我项目中的每个实体,我都应提供样板规范化器:(

I found only one solution: add my own Symfony normalizer. But I see very poor developer experience here. For each Entity in my project I should provide boilerplate normalizer :(

symfony是否有更简单的Collections解决方案(例如JMS)?

Does symfony have simpler solution for Collections (f.e. like JMS) ?

复制支架:

  1. Symfony Framework 5.2和常见的flex配置

symfony new serializer-demo
composer.phar require serializer doctrine jms\serializer

  1. 非常简单的实体

class Entity
{
    /**
     * @var ArrayCollection<\App\Entity\Item>
     *
     * @Type("ArrayCollection<App\Entity\Item>")
     *
     */
    private $rewardItems;

    public function __construct()
    {
        $this->rewardItems = new ArrayCollection();
    }

    public function getRewardItems(): ArrayCollection
    {
        return $this->rewardItems;
    }

    public function setRewardItems($rewardItems): void
    {
        $this->rewardItems = $rewardItems;
    }
}

class Item
{
    /**
     * @var int
     * @Type("int")
     */
    private $type;


    public function __construct(int $type)
    {
        $this->type = $type;
    }

    /**
     * @return int
     */
    public function getType(): int
    {
        return $this->type;
    }

    /**
     * @param int $type
     */
    public function setType(int $type): void
    {
        $this->type = $type;
    }

}

  1. 在控制台命令中运行

        $a = new Entity();
        $a->getRewardItems()->add(new Item(1));
        $a->getRewardItems()->add(new Item(2));

        $output->writeln("Origin:");
        VarDumper::dump($a);

        $this->symfony($a, $output);
        $this->jms($a, $output);

// Symfony
        $serializer = $this->serializer; // injected by DI

        $serialized = $serializer->serialize($a, 'json');

        $d = $serializer->deserialize($serialized, Entity::class, 'json');
        $output->writeln("<warning>Symfony deserialized:</warning>");
        VarDumper::dump($d);

// JMS
        $serializer = SerializerBuilder::create()->build();

        $jsonContent = $serializer->serialize($a, 'json');

        $des = $serializer->deserialize($jsonContent, Entity::class, 'json');
        $output->writeln("Jms Deserialized");
        VarDumper::dump($des);```

UPD0:为$ rewardItems属性更改结果提供PHPDoc类型提示,但并没有真正帮助:

UPD0: Providing PHPDoc type-hints for $rewardItems property changes result, but don't really helped:

  1. @var ArrayCollection< \ App \ Entity \ Item> ArrayCollection< Item> @var Item [] -结果相同就像原始主题一样:
  1. @var ArrayCollection<\App\Entity\Item>, ArrayCollection<Item>, @var Item[] - result the same as in original topic:

^ App\Entity\Entity^ {#192
  -rewardItems: array:2 [
    0 => App\Entity\Item^ {#343
      -type: 1
    }
    1 => App\Entity\Item^ {#325
      -type: 2
    }
  ]
}

  1. @var ArrayCollection<> 结果有些不同:
  1. @var ArrayCollection<> Result a bit different:

App\Entity\Entity {#192
  -rewardItems: Doctrine\Common\Collections\ArrayCollection {#307
    -elements: []
  }
}

推荐答案

我认为有多种简单的解决方案可以解决此问题.我不确定是否有更好的方法.搭配序列化程序效果更好.

I think there are multiple simple solutions to tackle this. I'm not quite certain if there is a better method. That works better with the Serializer.

最可取的-如果您要积极避免添加和去除

我不知道这是否行得通.

I somehow doubt this will work.

因为您的原始帖子未显示任何迹象,但可能与之相关,因为属性的类型可能与预期的参数类型大不相同,并且使用了最明智的假设(将是数组,因为您不应该公开您的内部实现):

since your original post shows no sign of it but it might be relevant, since the type of a property might be very different from the type of the parameter expected and the most sensible assumption is used (which would be array, since you shouldn't expose your internal implementation):

/**
 * @param ArrayCollection<\App\Entity\Item> $rewardItems
 */
public function setRewardItems($rewardItems): void
{
    $this->rewardItems = $rewardItems;
}

使用实型提示

,如果您不想要加法器/移除器(我通常希望这样做,但这只是首选项)

and if you don't want adders/removers (which I would prefer in general, but that's just preference)

use Doctrine\Common\Collections\Collection; // <-- or use more specific ones ...

// adding real type-hint:
public function setRewardItems(Collection $rewardItems): void 
{
    $this->rewardItems = $rewardItems;
}

这可能会导致symfony序列化器在未提供Collection的情况下失败.

This could lead to the symfony serializer just failing, when it doesn't provide a Collection.

添加多于设置者

PropertyAccessor会将加法器优先于设置器a>-您也必须添加卸妆剂

public function addRewardItem(Item $item): void
{
    if (!$this->rewardItems->contains($item)) {
        $this->rewardItems[] = $item;
    }
}

完全替换

如果提供的内容与集合不同,则可能会失败

this can fail, if something different than a collection is provided

use Doctrine\Common\Collections\Collection; // <--!

public function setRewardItems($rewardItems): void
{
    $this->rewardItems = ($rewardItems instanceof Collection) ? $rewardItems : new ArrayCollection($rewardItems);
}

其中一种方法肯定应该有效,而且我不确定是否有更好的解决方案与序列化程序进行通信,而您希望使用ArrayCollection

one of those approaches should definitely work, and I'm not quite sure, if there is a more elegant solution to communicate to the serializer that you expect an ArrayCollection

评论

我更喜欢加法器和卸除器,因为它可以将原始Collection对象保留在原处,从而使学说更容易注意到更改.您可以使用二传手实现相同的功能,但是更加复杂.我不喜欢将rewardItem的内部处理暴露给外部"消息.世界,因为从本质上讲,当有人呼叫"getRewardItems"时,并且将集合交给调用者,它可能会以任何喜欢的方式更改集合-调用getter不应导致对象的内部状态可更改/更改.我更喜欢返回 $ this-> rewardItems-> toArray(),因为它没有公开有关内部如何处理rewardItem的任何信息.而且无论出于何种原因,某些代码都不会更改集合.是的,这很防御.

I prefer adders and removers, because it leaves the original Collection object in place, thus making it easier for doctrine to notice changes. You could achieve the same thing with a setter, but it's more complicated. I don't like exposing the internal handling of rewardItems to the "outside" world, because essentially when someone calls "getRewardItems" and you hand your collections to the caller, it might change the collection in any way it likes - calling a getter should not lead to the internal state of an object being changeable/changed.I prefer returning $this->rewardItems->toArray() because it doesn't expose anything about how the rewardItems are handled internally. And there is no danger in some code changing the collection for whatever reason. Yes, it's defensive.

这篇关于symfony/serializer与对象集合一起使用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆