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

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

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

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

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

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

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

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

Есть идеи? Примеры кода от других ORM принимаются.

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

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

3 ответа

Решение

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

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

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

  $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() до того, как mapper сохранит состояние и вызов модели cleanState() - когда картограф вытащил данные в модель, которую вы называете.

Кстати, если у вас есть лучшее предложение для получения значений от и к картографическим данным вместо 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() , И у него такое странное имя, потому что в ООП implements слово означает " способности", в то время как extends значит есть.

До этой части у вас уже должно быть все подобное...


Теперь вот что 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;
  }

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

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, Я предполагаю, что это не очень ПОПО, хотя.

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

Другие вопросы по тегам