Лучший способ обработки грязного состояния в модели ORM

Я не хочу, чтобы кто-то говорил: "Вы не должны изобретать велосипед, использовать ORM с открытым исходным кодом"; У меня есть неотложное требование и не могу переключиться.

Я делаю немного ORM, который поддерживает кеширование. Даже не поддерживая кеширование, мне нужна эта функция в любом случае, чтобы знать, когда писать объект на хранение или нет. Шаблон - DataMapper.

Вот мой подход:

  • Я хочу избежать интроспекции времени выполнения (т.е. атрибутов угадывания).
  • Я не хочу использовать генератор кода CLI для генерации getters и seters (действительно, я использую NetBeans один, используя ALT + INSERT).
  • Я хочу, чтобы модель была самой близкой к POPO (простой старый объект PHP). Я имею в виду: частные атрибуты, "жестко закодированные" геттеры и сеттеры для каждого атрибута.

У меня есть класс Abstract, называемый AbstractModel, который наследуют все модели. Он имеет открытый метод isDirty() с приватным (может быть защищен, если необходимо) атрибутом is_dirty. Он должен возвращать true или false в зависимости от изменения данных объекта или нет с момента его загрузки.

Проблема заключается в следующем: есть способ поднять внутренний флаг "is_dirty" без кодирования в каждом сеттере $this->is_dirty = true? Я имею в виду: я хочу, чтобы сеттеры были $this->attr = $value большую часть времени, за исключением необходимости изменения кода для бизнес-логики.

Другим ограничением является то, что я не могу полагаться на __set, потому что в конкретном классе модели атрибуты уже существуют как частные, поэтому __set никогда не вызывается в сеттерах.

Любые идеи? Приводятся примеры кода из других ORM.

Одна из моих идей заключалась в том, чтобы изменить шаблон сеттеров NetBeans, но я думаю, что должен быть способ сделать это, не полагаясь на среду IDE.

Еще одна моя мысль заключалась в создании сеттеров, а затем изменении имени частного атрибута с подчеркиванием или чем-то еще. Таким образом, сеттер будет звонить на __set и иметь некоторый код для работы с флагом "is_dirty", но это немного нарушает концепцию POPO, и это уродливо.

Ответ 1

Attantion!
Мое мнение по этому вопросу несколько изменилось в прошлом месяце. Хотя ответ, который по-прежнему действителен, при работе с крупными объектными графами, я бы рекомендовал вместо этого использовать шаблон Unit-of-Work. Вы можете найти краткое объяснение этого в этом ansewer

Я немного смущен, как то, что вы называете-Model, связано с ORM. Это смущает. Тем более, что в MVC модель представляет собой слой (по крайней мере, то, как я его понимаю, и ваши "Модели" кажутся мне более похожими на Объекты домена).

Я предполагаю, что у вас есть код, который выглядит так:

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

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

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

  $mapper->push( $model );

И, я буду предполагать, что у того, что вы вызывают-модель, есть два метода: конструктор, который будет использоваться картами данных: getParameters() и setParameters(). И что вы вызываете isDirty(), прежде чем mapper сохранит состояние "вы-вызов-модель" и вызовите cleanState() - когда транслятор переводит данные в модель "вы-вызов".

BTW, если у вас есть лучшее предложение для получения значений от-и-to-data mappers вместо 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 означает is-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;
  }

Ответ 2

Я бы сделал прокси для установки, например:

class BaseModel {

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

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

Затем каждый дочерний класс будет реализовывать свой сеттер, вызывая _set() и никогда не устанавливая свойство напрямую. Кроме того, вы всегда можете вводить более классный код в каждый подкласс _set и просто звонить parent::set($attr, $processedValue), если это необходимо. Затем, если вы хотите использовать магические методы, вы создаете прокси-сервер для метода свойств, который проксирует до _set. Я полагаю, что это не очень POPO, хотя.

Ответ 3

хотя это сообщение устарело, но как насчет использования событий для уведомления слушателей, когда isDirty() происходит? Я бы подошел к решению с событиями.