【问题标题】:symfony/serializer usage with object collectionsymfony/序列化器与对象集合的使用
【发布时间】:2020-12-24 18:13:31
【问题描述】:

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

例子:

我们在属性中有一些带有集合的实体(我使用的是 Doctrine ArrayCollection,但它会为任何实现 PHP Iterator 接口的集合复制)

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

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并替换为phparray

我只找到了一个解决方案:添加my own Symfony normalizer。但是我在这里看到非常糟糕的开发人员体验。对于我项目中的每个实体,我应该提供样板规范化器:(

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

复制立场:

  1. Symfony Framework 5.2 wit common flex configuration
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 类型提示,但没有真正帮助:

  1. @var ArrayCollection&lt;\App\Entity\Item&gt;ArrayCollection&lt;Item&gt;@var Item[] - 结果与原始主题相同:
^ App\Entity\Entity^ {#192
  -rewardItems: array:2 [
    0 => App\Entity\Item^ {#343
      -type: 1
    }
    1 => App\Entity\Item^ {#325
      -type: 2
    }
  ]
}
  1. @var ArrayCollection&lt;&gt; 结果有点不同:
App\Entity\Entity {#192
  -rewardItems: Doctrine\Common\Collections\ArrayCollection {#307
    -elements: []
  }
}

【问题讨论】:

  • 我会在这里跳出一个肢体,假设 jms 使用属性注释来正确猜测属性上的内容,而 symfony 序列化程序会注意到它有一个数组并且会用它调用 setRewardItems。我想知道如果你用ArrayCollection 输入参数会发生什么。无论如何,您都可以添加支票。另外,使用加法器而不是设置器可能会更好......我不喜欢在同样的意义上返回集合(因为它可能会微妙地导致对原始集合的修改),我不喜欢直接设置集合属性。
  • @Jakumi 感谢您的回答。我已经尝试了 PHPDoc 的所有可能组合(请参阅原始主题中的 UPD0)。它没有帮助

标签: php symfony serialization doctrine-orm


【解决方案1】:

我认为有多种简单的解决方案可以解决这个问题。我不太确定是否有更好的方法。使用 Serializer 效果更好。

最可取 - 如果您想积极避免添加者和删除者

我怀疑这是否可行。

因为您的原始帖子没有显示任何迹象,但它可能是相关的,因为属性的类型可能与预期的参数类型非常不同,并且使用了最明智的假设(这将是数组,因为您不应该暴露你的内部实现):

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

使用真实的类型提示

如果你不想要加法器/去除器(我一般更喜欢,但这只是偏好)

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 时。

加法器优于设置器

The PropertyAccessor will prioritize adders over setters - 你必须也添加卸妆剂

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

完全替换

如果提供了不同于集合的东西,这可能会失败

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

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

其中一种方法肯定可以工作,但我不太确定是否有更优雅的解决方案可以与您期望 ArrayCollection 的序列化程序进行通信

评论

我更喜欢加法器和删除器,因为它保留了原始的 Collection 对象,从而使教义更容易注意到变化。您可以使用 setter 实现相同的目标,但它更复杂。我不喜欢将rewardItems 的内部处理暴露给“外部”世界,因为基本上当有人调用“getRewardItems”并且你将你的集合交给调用者时,它可能会以它喜欢的任何方式改变集合——调用getter 应该不会导致对象的内部状态被更改/更改。我更喜欢返回$this-&gt;rewardItems-&gt;toArray(),因为它不会公开任何有关如何在内部处理rewardItems 的信息。无论出于何种原因,一些代码更改集合都没有危险。是的,它是防御性的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-06-23
    • 2010-11-30
    • 2020-02-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-23
    相关资源
    最近更新 更多