Лучший способ справиться с грязным состоянием в модели 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()? Я бы подошел к решению с событиями.