我似乎对PHP的类型化属性和uninitialized状态有一点误解。
假设类似REST的服务获得以下JSON对象:
{
"firstName": "some name",
"lastName": "some last name",
"groupId": 0,
"dateOfBirth": "2000-01-01"
}我更希望DTO看起来像这样:
class Person {
private string $firstName;
private string $lastName;
private int $groupId;
private \DateTime $dateOfBirth;
// All the getters/setters, cannot have __construct due to serializer limitation
}但是,由于这些消息属性中的任何一个都可能被省略(由于错误而不是有效的情况),因此反序列化将使某些字段处于unintialized状态。
所以这太糟糕了。我想我有几个选择:
(yuck)
null,并将它们初始化为null标量属性以各自的默认值(0、'‘等),对象属性声明为null (为此目的宣布它们为null可实现)。假设我选择了选项2:
class Person {
private string $firstName = '';
private string $lastName = '';
private int $groupId = 0;
private ?\DateTime $dateOfBirth = null;
// The rest
}在代码的另一部分中,我有这样的内容:
function doSomethingWithDate(\DateTime $dateTime): string{
return ...; // does not really matter
}
...
doSomethingWithDate($person->getDateOfBirth());
...我的IDE尖叫着发出警告:
Expected parameter of type '\DateTime', '\DateTime|null' provided 很明显,为什么- getter说“嘿,我可以空”,但方法说“不,不”。
但我该怎么做才能“说服”这是一个有效的方案呢?
我是否应该有一组单独的DTOs -一组用于不安全状态,另一组用于保险箱?似乎不太可能..。
你会如何处理这个“问题”?
更新
虽然我的问题听起来很含糊和奇怪(我知道它是:D),但我想详细说明一下。
我的两个内部系统通过内部Redis流进行通信。其中一个是遗留代码(基于php56 56),另一个是php81-based
failed数后开始在试图将它推送到failed队列中处理
发布于 2022-10-08 16:01:45
(这个答案是特定于Symfony序列化器组件和PHP 8.1+的。)
确保输入的数据遵守某一契约当然是一件好事。我也喜欢我的财产类型尽可能严格,我也讨厌PhpStorm对我大喊大叫。
问题所在
想象一下我们会有这样的DTO:
class Dto1 {
private string $foo;
public function setFoo(string $foo): void { $this->foo = $foo; }
public function getFoo(): string { return $this->foo; }
}您可能会像这样反序列化它:
$dto = $serializer->deserialize($json, Dto1::class, 'json', [
AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => false,
]);(将ALLOW_EXTRA_ATTRIBUTES设置为false可以确保遗留系统不能引入额外的属性)
正如您注意到的,当JSON缺少$foo属性时,我们将遇到一个问题:
$json = '{}';
$dto = $serializer->deserialize($json, Dto1::class, 'json', [
AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => false,
]);
var_dump($dto->foo); // Oops! Uninitialized property access不幸的是,似乎没有一种方法可以让Symfony序列化程序检查反序列化后未初始化的属性。这就留给我们解决这个问题的另外两种选择。
解决方案1
确保在反序列化之后初始化所有属性。这可能需要编写一个类似于以下内容的函数:
function ensureInitialized(object $o): void {
// There are probably more robust ways to do this,
// this is just an example.
$reflectionClass = new ReflectionClass($o);
foreach ($reflectionClass->getProperties() as $reflectionProperty) {
if (!$reflectionProperty->isInitialized($o)) {
throw new RuntimeException('Uninitialized properties!');
}
}
}我们可以使用这个函数来确保反序列化DTO是有效的:
$json = '{}';
$dto = $serializer->deserialize($json, Dto1::class, 'json', [
AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => false,
]);
ensureInitialized($dto); // <-- throws exception但是,我更愿意避免检查每一个反序列化的DTO。我更喜欢下一个解决方案。
解决方案2
由于您提到您使用的是PHP8.1,所以我们可以为DTO使用构造函数属性提升和只读属性。
class Dto2 {
public function __construct(
public readonly string $foo,
) {}
}我们仍然可以像普通一样反序列化JSON:
$json = '{"foo": "bar"}';
$dto = $serializer->deserialize($json, Dto2::class, 'json', [
AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => false,
]);
var_dump($dto->foo); // string(3) "bar"但如果遗留系统再次试图欺骗我们:
$json = '{}';
$dto = $serializer->deserialize($json, Dto2::class, 'json', [
AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => false,
]);
// ^ Will throw: Uncaught Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException: Cannot create an instance of "B" from serialized data because its constructor requires parameter "foo" to be present现在您可以简单地捕获此异常并酌情返回4XX错误。
https://stackoverflow.com/questions/73993226
复制相似问题