【问题标题】:How to distinguish between "unset" and "no-assignment" for NULL values?如何区分 NULL 值的“未设置”和“未赋值”?
【发布时间】:2023-03-30 11:20:01
【问题描述】:

在这个例子中的 php - 但实际上在一般编程中,有没有办法区分 "no-assignment""unset-value" 命令 @ 987654321@ 合并 2 个相同类型的不可变数据对象时的值?

考虑这个 php 类是一个不可变的数据对象。它在其构造函数中接受一个字符串和一个整数,并且只提供值的访问器:

class Data
{
    protected $someNumber;
    protected $someString;

    public function __construct(?int $someNumber, ?string $someString)
    {
        $this->someNumber = $someNumber;
        $this->someString = $someString;
    }

    public function getSomeNumber(): ?int
    {
        return $this->someNumber;
    }

    public function getSomeString(): ?string
    {
        return $this->someString;
    }
}

这些值始终可以是null 或其各自的数据类型stringinteger。此外,构造函数接受null 值而不是string 和/或int:UNSET 操作。

现在我希望能够合并Data 的2 个实例,类似于接受$first$second 的简化工厂方法,其中$second 中的数据会覆盖$first 中的数据(如果存在)。

class DataFactory
{
    public function merge(Data $first, Data $second): Data
    {
        // Uses data from $first if corresponding data from
        // $second is (strictly) null
        return new Data(
            $second->getSomeNumber() ?? $first->getSomeNumber(),
            $second->getSomeString() ?? $first->getSomeString(),
    }
}

在上面的例子中,$second 的访问器返回的null 值被解释为 NO UPDATE 操作:当遇到null 时,保持$first 的对应值。问题是我希望能够区分merge 中对 NO UPDATE 操作或 UNSET 操作的请求。

Data 类中的严格类型不允许使用诸如"DATA_UNSET_FIELD" 之类的字符串常量作为要标记的值,因此直接在数据本身上实现这一点似乎是不可能的。更重要的是,即使为任何值传递构造函数 null 肯定意味着 SET NULL。

我正在考虑某种更新镜头,它明确指定在合并时应取消设置的属性,以便$second 中的null 值仅意味着没有更新(保留$first)。

解决这个问题的紧凑型面向对象模式是什么?随着数据的增长,我已经可以想象像爆炸普通数组模式或策略类的类爆炸这样的问题。此外,我有点担心Data 对象的“移动性”,因为新对象必须在某些时候与它们相关联。

提前致谢!


编辑

我希望能够区分不覆盖当前值和取消设置一个值——即分配null——当合并@987654347的2个实例时@,其中$first 是基数,$second 覆盖$first 的数据。作为细节,合并会产生第三个新对象,即合并结果。

查看$second中的DataFactorysn-p null值目前被解释为“保留$first的对应值”。但是我如何为每个字段携带另一个标志,指示在结果对象中应该将哪些字段设置为null,以一种干净的方式,并且不会过多地弄乱数据类?

【问题讨论】:

  • 在考虑 html 表单时,NO UPDATE“标志”是否类似于“原始”的使用?换句话说(只是为了确保我理解)您想区分未触及的财产和已“摧毁”的财产?
  • @FélixGagnon-Grenier 这并不是我想要做的。我会用更多信息更新我的问题。谢谢!
  • @JeffreyWesterkamp,为什么您需要“能够区分对 NO UPDATE 操作或 UNSET 操作的请求”?你会将这些知识用于什么目的?
  • @sevavietl 在我当前的merge 实现中,如果任一对应的源值不是null,则返回对象的任何属性都没有null 值。我的目标是一些扩展的实现,允许我确定是否在返回的对象内显式地将属性设置为 null。这将需要改变当前 merge 函数的方法——这很好——但我不完全确定最干净/最小的方法。

标签: php oop design-patterns php-7.1


【解决方案1】:

PHP 无法区分未赋值变量和空变量。 这使得跟踪哪些属性应该被覆盖是不可避免的。

我看到你有两个顾虑:

  • 保持Data 不可变
  • 保持Data 的接口干净(例如强制执行严格类型)

能够跟踪“已定义”和“未定义”属性的最简单的数据结构之一是 \stdClass 对象(但数组也非常好)。 通过将 merge() 方法移动到 Data 类中,您将能够隐藏任何实现细节 - 保持接口清洁。

实现可能如下所示:

final class Data {

    /** @var \stdClass */
    protected $props;

    // Avoid direct instantiation, use ::create() instead
    private function __construct()
    {
        $this->props = new \stdClass();
    }

    // Fluent interface
    public static function create(): Data
    {
        return new self();
    }

    // Enforce immutability
    public function __clone()
    {
        $this->props = clone $this->props;
    }

    public function withSomeNumber(?int $someNumber): Data
    {
        $d = clone $this;
        $d->props->someNumber = $someNumber;
        return $d;
    }

    public function withSomeString(?string $someString): Data
    {
        $d = clone $this;
        $d->props->someString = $someString;
        return $d;
    }

    public function getSomeNumber(): ?int
    {
        return $this->props->someNumber ?? null;
    }

    public function getSomeString(): ?string
    {
        return $this->props->someString ?? null;
    }

    public static function merge(...$dataObjects): Data
    {
        $final = new self();

        foreach ($dataObjects as $data) {
            $final->props = (object) array_merge((array) $final->props, (array) $data->props);
        }

        return $final;
    }
}


$first = Data::create()
    ->withSomeNumber(42)
    ->withSomeString('foo');

// Overwrite both someNumber and someString by assigning null
$second = Data::create()
    ->withSomeNumber(null)
    ->withSomeString(null);

// Overwrite "someString" only
$third = Data::create()
    ->withSomeString('bar');

$merged = Data::merge($first, $second, $third); // Only "someString" property is set to "bar"
var_dump($merged->getSomeString()); // "bar"

【讨论】:

  • 这绝对解决了我在这里描述的问题。然而,实际上Data 类在通过工厂管理的递归数据树中是可修饰和可组合的。这就是为什么我不能过多地扩展数据的接口和实现。此外,数据对象必须没有附加状态。无论如何感谢您的回答! +1
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-10-03
  • 2016-11-20
  • 2017-03-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-05
相关资源
最近更新 更多