Php 变异对象以避免副作用的正确方法是什么?

Php 变异对象以避免副作用的正确方法是什么?,php,unit-testing,architecture,immutability,Php,Unit Testing,Architecture,Immutability,我有下一个对象系统(简单示例): 免责声明:Grid类是一个遗留的ActiveRecord模型,独立的单元测试无法覆盖它,因为它会写入数据库并更改系统中的一些其他数据。所以我只对State类感兴趣 我需要一个用于状态的mutator类。它一定很容易测试。看起来是这样的: class StateMutator { public function mutate(State $state, array $changes):?State { // ...

我有下一个对象系统(简单示例):

免责声明:
Grid
类是一个遗留的ActiveRecord模型,独立的单元测试无法覆盖它,因为它会写入数据库并更改系统中的一些其他数据。所以我只对
State
类感兴趣

我需要一个用于
状态
的mutator类。它一定很容易测试。看起来是这样的:

class StateMutator
{
    public function mutate(State $state, array $changes):?State
    {
        // ...
        $state->isCompleted = true;
        // ...
        if(!$someCondition){
            return null;
        }
        // ...
        return $state;
    }
}
它是这样使用的:

/** @var Grid $grid */
/** @var array $changes */
$newState = (new StateMutator())->mutate($grid->state, $changes);
if($newState !== null){
    $grid->state = $newState;
}
// Some other changes in $grid
$grid->saveChanges();
看起来不错。但有件事让我困惑。如果mutator在获取的对象中做了一些更改,并在此之后返回
null
,那么调用代码将认为状态没有更改-对其进行一些其他更改并将其保存到数据库中。但是由于PHP通过引用传递对象,所以在state对象中所做的更改也将保存到数据库中。这是个问题

我应该怎么做来避免这个问题

我有两种方法来解决这个问题,但它们都有很大的问题

  • 如果mutator不能在其内部的任何位置更改state对象,它应该恢复已经完成的更改。但在某些情况下,这很难做到,甚至是不可能做到的
  • 变异者应该克隆状态,变异它的副本并返回它。但在这种情况下,该方法将需要更多的内存(该状态的属性中可以有1000多个对象)

  • 有人知道吗?

    我通常会避免对象进入无效状态。您的
    状态
    对象应该有方法来改变其状态或返回具有新状态的克隆。这些方法验证输入,并仅在生成的状态有效时才变异/返回克隆。无需跟踪更改和回滚。

    我认为这个“mutator”不应该修改
    $state
    ,如果它的任务只是返回更新的状态。您是否有理由希望在
    mutate()
    中修改
    $state
    ?是的,我同意您的看法。所以我问我该怎么做?为了返回一个新的状态,突变子应该克隆一个源状态。但是它需要大量的内存。我搜索了另一种方法来实现它。我不明白为什么需要
    mutate()
    来返回任何东西。它不能仅仅是无效的吗?你没有使用它的返回值,只是把它再次赋给
    $grid->state
    ,如果对象已经变异了,这是没有用的,对吧?克隆似乎是正确的方法。我还认为
    状态
    应该是不可变的,以保证一个给定的状态永远不会被改变。记忆有什么大不了的吗?你说的是“因为PHP通过引用传递对象”,但如果不是这样,克隆它们正是它要做的事情。还有一个想法——你能创建一个StateChangeHistory对象来跟踪状态发生了什么,这样你就可以恢复所做的任何更改吗?
    /** @var Grid $grid */
    /** @var array $changes */
    $newState = (new StateMutator())->mutate($grid->state, $changes);
    if($newState !== null){
        $grid->state = $newState;
    }
    // Some other changes in $grid
    $grid->saveChanges();