Best way to handle dirty state in an ORM model

后端 未结 3 2003
星月不相逢
星月不相逢 2020-12-03 13:06

I don\'t want anyone saying \"you should not reinvent the wheel, use an open source ORM\"; I have an immediate requirement and cannot switch.

I\'m doing a l

相关标签:
3条回答
  • 2020-12-03 13:26

    Attention!
    My opinion on the subject has somewhat changed in the past month. While the answer where is still valid, when dealing with large object graphs, I would recommend using Unit-of-Work pattern instead. You can find a brief explanation of it in this answer

    I'm kinda confused how what-you-call-Model is related to ORM. It's kinda confusing. Especially since in MVC the Model is a layer (at least, thats how I understand it, and your "Models" seem to be more like Domain Objects).

    I will assume that what you have is a code that looks like this:

      $model = new SomeModel;
      $mapper = $ormFactory->build('something');
    
      $model->setId( 1337 );
      $mapper->pull( $model );
    
      $model->setPayload('cogito ergo sum');
    
      $mapper->push( $model );
    

    And, i will assume that what-you-call-Model has two methods, designer to be used by data mappers: getParameters() and setParameters(). And that you call isDirty() before mapper stores what-you-call-Model's state and call cleanState() - when mapper pull data into what-you-call-Model.

    BTW, if you have a better suggestion for getting values from-and-to data mappers instead of setParameters() and getParameters(), please share, because I have been struggling to come up with something better. This seems to me like encapsulation leak.

    This would make the data mapper methods look like:

      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
      }
    

    In the code snippet Parametrized is a name of interface, which object should be implementing. In this case the methods getParameters() and setParameters(). And it has such a strange name, because in OOP, the implements word means has-abilities-of , while the extends means is-a.

    Up to this part you should already have everything similar...


    Now here is what the isDirty() and cleanState() methods should do:

      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;
      }
    
    0 讨论(0)
  • 2020-12-03 13:28

    though this post is old BUT how about using events to notify listeners when isDirty() happens? I would approach the solution with events.

    0 讨论(0)
  • 2020-12-03 13:35

    I would make a proxy to set for example:

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

    Then each child class would implemnt its setter by calling _set() and never set the property directly. Further, you can always inject more class specific code into each sub class's _set and just call parent::set($attr, $processedValue) if needed. Then if you want to use magic methods you make those proxy to property method that proxies to _set. I suppose this isnt very POPO though.

    0 讨论(0)
提交回复
热议问题