首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >PHP未初始化对象属性

PHP未初始化对象属性
EN

Stack Overflow用户
提问于 2022-10-07 22:55:29
回答 1查看 108关注 0票数 3

我似乎对PHP的类型化属性和uninitialized状态有一点误解。

假设类似REST的服务获得以下JSON对象:

代码语言:javascript
复制
{
    "firstName": "some name",
    "lastName": "some last name",
    "groupId": 0,
    "dateOfBirth": "2000-01-01"
}

我更希望DTO看起来像这样:

代码语言:javascript
复制
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)

  • Initialize
  1. 将它们都声明为null,并将它们初始化为null标量属性以各自的默认值(0、'‘等),对象属性声明为null (为此目的宣布它们为null可实现)。

假设我选择了选项2:

代码语言:javascript
复制
class Person {
    private string $firstName = '';
    private string $lastName = '';
    private int $groupId = 0;
    private ?\DateTime $dateOfBirth = null;

    // The rest
}

在代码的另一部分中,我有这样的内容:

代码语言:javascript
复制
function doSomethingWithDate(\DateTime $dateTime): string{
    return ...; // does not really matter
}

...

doSomethingWithDate($person->getDateOfBirth());
...

我的IDE尖叫着发出警告:

代码语言:javascript
复制
Expected parameter of type '\DateTime', '\DateTime|null' provided 

很明显,为什么- getter说“嘿,我可以空”,但方法说“不,不”。

但我该怎么做才能“说服”这是一个有效的方案呢?

我是否应该有一组单独的DTOs -一组用于不安全状态,另一组用于保险箱?似乎不太可能..。

你会如何处理这个“问题”?

更新

虽然我的问题听起来很含糊和奇怪(我知道它是:D),但我想详细说明一下。

我的两个内部系统通过内部Redis流进行通信。其中一个是遗留代码(基于php56 56),另一个是php81-based

  • Since,通信是内部的,我完全忽略了验证aspect

  • During的测试,遗留系统错误地发送了一个格式错误的对象(另一边没有包含任何properties)

  • The反序列化器,但是得到了一个完全未初始化的对象,而我的Redis使用者在达到failed数后开始在试图将它推送到failed队列中处理

  • ),但是,由于未初始化的properties.

  • My Redis用户的串行化,它不能永远停留在这条消息上。
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-10-08 16:01:45

(这个答案是特定于Symfony序列化器组件和PHP 8.1+的。)

确保输入的数据遵守某一契约当然是一件好事。我也喜欢我的财产类型尽可能严格,我也讨厌PhpStorm对我大喊大叫。

问题所在

想象一下我们会有这样的DTO:

代码语言:javascript
复制
class Dto1 {
    private string $foo;

    public function setFoo(string $foo): void { $this->foo = $foo; }
    public function getFoo(): string { return $this->foo; }
}

您可能会像这样反序列化它:

代码语言:javascript
复制
$dto = $serializer->deserialize($json, Dto1::class, 'json', [
    AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => false,
]);

(将ALLOW_EXTRA_ATTRIBUTES设置为false可以确保遗留系统不能引入额外的属性)

正如您注意到的,当JSON缺少$foo属性时,我们将遇到一个问题:

代码语言:javascript
复制
$json = '{}';
$dto = $serializer->deserialize($json, Dto1::class, 'json', [
    AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => false,
]);
var_dump($dto->foo); // Oops! Uninitialized property access

不幸的是,似乎没有一种方法可以让Symfony序列化程序检查反序列化后未初始化的属性。这就留给我们解决这个问题的另外两种选择。

解决方案1

确保在反序列化之后初始化所有属性。这可能需要编写一个类似于以下内容的函数:

代码语言:javascript
复制
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是有效的:

代码语言:javascript
复制
$json = '{}';
$dto = $serializer->deserialize($json, Dto1::class, 'json', [
    AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => false,
]);

ensureInitialized($dto); // <-- throws exception

但是,我更愿意避免检查每一个反序列化的DTO。我更喜欢下一个解决方案。

解决方案2

由于您提到您使用的是PHP8.1,所以我们可以为DTO使用构造函数属性提升和只读属性。

代码语言:javascript
复制
class Dto2 {
    public function __construct(
        public readonly string $foo,
    ) {}
}

我们仍然可以像普通一样反序列化JSON:

代码语言:javascript
复制
$json = '{"foo": "bar"}';
$dto = $serializer->deserialize($json, Dto2::class, 'json', [
    AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => false,
]);

var_dump($dto->foo); // string(3) "bar"

但如果遗留系统再次试图欺骗我们:

代码语言:javascript
复制
$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错误。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/73993226

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档