在ORM模型中处理脏状态的最佳方法

时间:2012-06-07 21:38:57

标签: php model-view-controller oop orm

我不希望任何人说“你不应该重新发明轮子,使用开源ORM”;我有立即要求,无法切换。

我正在做一些支持缓存的ORM。即使不支持缓存,我仍然需要此功能,以便知道何时将对象写入存储。模式是DataMapper。

这是我的方法:

  • 我想避免运行时内省(即猜测属性)。
  • 我不想使用CLI代码生成器来生成getter和setter(实际上我使用的是NetBeans,使用ALT + INSERT)。
  • 我希望模型最接近POPO(普通的旧PHP对象)。我的意思是:私有属性,每个属性的“硬编码”getter和setter。

我有一个名为AbstractModel的抽象类,所有模型都继承。它有一个名为isDirty()的公共方法,它带有一个名为is_dirty的私有(如果需要也可以保护)属性。它必须返回true或false,具体取决于对象数据是否有变化,因为它已被加载。

问题是:有没有办法在每个setter "is_dirty"中无需编码的情况下引发内部标记$this->is_dirty = true?我的意思是:我想在大多数情况下将setter设置为$this->attr = $value,除非业务逻辑需要更改代码。

其他限制是我不能依赖__set,因为在具体的模型类中,属性已经作为私有存在,因此永远不会在setter上调用__set

有什么想法吗?其他ORM的代码示例被接受。

我的一个想法是修改NetBeans setter模板,但我认为应该有一种不依赖IDE的方法。

我的另一个想法是创建setter,然后使用下划线或其他内容更改private属性的名称。这样setter就会调用__set并在那里有一些代码来处理"is_dirty"标志,但是这会破坏POPO概念,而且很难看。

3 个答案:

答案 0 :(得分:8)

  

Attantion!
我对这个问题的看法在过去一个月有所改变。虽然答案仍然有效,但在处理大型对象图时,我建议使用工作单元模式。您可以在this ansewer

中找到它的简要说明

我很困惑你叫什么模型与ORM有关。这有点令人困惑。特别是因为在MVC中,模型是一个层(至少thats how i understand it,而你的“模型”在我看来更像Domain Objects)。

我会假设你拥有的是一个看起来像这样的代码:

  $model = new SomeModel;
  $mapper = $ormFactory->build('something');

  $model->setId( 1337 );
  $mapper->pull( $model );

  $model->setPayload('cogito ergo sum');

  $mapper->push( $model );

而且,我将假设 what-you-call-Model 有两种方法,设计者可供数据映射器使用:getParameters()setParameters()。并且您在映射器存储调用模型的状态并调用isDirty()之前调用cleanState() - 当映射器将数据拉入您调用的内容时-model

  

BTW,如果你有更好的建议来获取数据映射器的值而不是setParameters()getParameters(),请分享,因为我一直在努力想出更好的东西。在我看来,这就像封装泄漏一样。

这会使数据映射器方法看起来像:

  public function pull( Parametrized $object )
  {
      if ( !$object->isDirty() )
      {
          // there were NO conditions set on clean object
          // or the values have not changed since last pull
          return false; // or maybe throw exception
      }

      $data = // do stuff which read information from storage

      $object->setParameters( $data );
      $object->cleanState();

      return $true; // or leave out ,if alternative as exception
  }

  public static function push( Parametrized $object )
  {
      if ( !$object->isDirty() )
      {
          // there is nothing to save, go away
          return false; // or maybe throw exception
      }

      $data = $object->getParameters();
      // save values in storage
      $object->cleanState();

      return $true; // or leave out ,if alternative as exception
  }
  

在代码片段Parametrized中是接口的名称,该对象应该实现。在这种情况下,方法getParameters()setParameters()。它有一个奇怪的名字,因为在OOP中,implements一词意味着 有能力 - ,而extends意味着 是-A

到目前为止,您应该已经拥有了类似的一切......


现在,这是isDirty()cleanState()方法应该做的事情:

  public function cleanState()
  {
      $this->is_dirty = false;
      $temp = get_object_vars($this);
      unset( $temp['variableChecksum'] );
      // checksum should not be part of itself
      $this->variableChecksum = md5( serialize( $temp ) );
  }

  public function isDirty()
  {
      if ( $this->is_dirty === true )
      {
          return true;
      }

      $previous = $this->variableChecksum;

      $temp = get_object_vars($this);
      unset( $temp['variableChecksum'] );
      // checksum should not be part of itself
      $this->variableChecksum = md5( serialize( $temp ) );

      return $previous !== $this->variableChecksum;
  }

答案 1 :(得分:1)

我会设置一个代理来设置例如:

class BaseModel {

   protected function _set($attr, $value) {
      $current = $this->_get($attr);
      if($value !== $current) {
         $this->is_dirty = true;
      }

      $this->$attr = $value;
   }
}

然后,每个子类都将通过调用_set()来实现其setter,并且永远不会直接设置该属性。此外,您始终可以将更多特定于类的代码注入每个子类的_set,并在需要时调用parent::set($attr, $processedValue)。然后,如果要使用魔术方法,则将这些代理应用于代理_set的属性方法。我想这不是POPO。

答案 2 :(得分:0)

虽然这篇文章很老但是当isDirty()发生时如何使用事件来通知监听器? 我会用事件来解决问题。