Здесь показаны различия между двумя версиями данной страницы.
| Следущая версия | Предыдущая версия | ||
|
eaze:samples:одновременное_редактирование_объектов [2012/11/23 17:19] sergeyfast создано |
eaze:samples:одновременное_редактирование_объектов [2014/03/23 13:36] (текущий) sergeyfast |
||
|---|---|---|---|
| Строка 1: | Строка 1: | ||
| ====== Отслеживание одновременного редактирования объектов в VT ====== | ====== Отслеживание одновременного редактирования объектов в VT ====== | ||
| - | По умолчанию в VT при сохранении объектов действует правило - "кто последний нажал сохранить, того и форма :)". Попробуем исправить это, поставив следующую задачу: | + | По умолчанию в VT при сохранении объектов действует правило: "кто последний нажал сохранить, того и форма :)". Попробуем исправить это, поставив следующую задачу: |
| >В VT при редактировании объекта давать сохранять именно ту версию объекта, которая была открыта на тот момент. | >В VT при редактировании объекта давать сохранять именно ту версию объекта, которая была открыта на тот момент. | ||
| - | >Если редактирование одновременно было открыто два раза, то при нажатии кнопки "сохранить" во второй раз система не должна позволить сделать это, так как в первый раз уже было произведено сохранение. | + | >Если страница редактирования одновременно была открыта два раза, то при нажатии кнопки "сохранить" во второй раз система не должна позволить сохранить изменения, так как в первый раз уже было произведено сохранение. |
| Эта схема называется [[https://en.wikipedia.org/wiki/Optimistic_concurrency_control|"Optimistic Concurrency Control"]] | Эта схема называется [[https://en.wikipedia.org/wiki/Optimistic_concurrency_control|"Optimistic Concurrency Control"]] | ||
| Строка 9: | Строка 9: | ||
| ===== Реализация ===== | ===== Реализация ===== | ||
| Решение данной задачи можно рассматривать в различных вариантах, но мы, как программисты, определим свои ограничения заранее. | Решение данной задачи можно рассматривать в различных вариантах, но мы, как программисты, определим свои ограничения заранее. | ||
| - | У объекта могут быть связанные объекты (листы), обычно они редактируются на одной странице, поэтому нам важен сам факт нажатия кнопки "Сохранить и закрыть" или "Применить". | + | У объекта могут быть связанные объекты (листы), обычно они редактируются на одной странице, поэтому нам важен сам факт нажатия кнопки "Сохранить и закрыть" или "Применить", а не то, что изменилось в объекте. |
| Если в объекте ничего не поменялось, мы не учитываем это. По своей сути задача сводится к записи времени последнего редактирования объекта. | Если в объекте ничего не поменялось, мы не учитываем это. По своей сути задача сводится к записи времени последнего редактирования объекта. | ||
| Строка 38: | Строка 38: | ||
| ==== ObjectHistoryUtility.php ==== | ==== ObjectHistoryUtility.php ==== | ||
| - | Утилита {{:eaze:samples:objecthistoryutility.phps|}} будет работать только с теми объектами, у которых уже есть Factory. | + | Утилита {{:eaze:samples:objecthistoryutility.phps|ObjectHistoryUtility.phps}} будет работать только с теми объектами, у которых уже есть Factory. |
| Переменная ''$InsertsOnly'' отвечает за режим работы. Если значение ''true'', то в ''objectHistory'' будет хронология всех изменений, иначе - только последнее изменение. | Переменная ''$InsertsOnly'' отвечает за режим работы. Если значение ''true'', то в ''objectHistory'' будет хронология всех изменений, иначе - только последнее изменение. | ||
| Строка 44: | Строка 44: | ||
| ==== Save<Class>Action.php ==== | ==== Save<Class>Action.php ==== | ||
| + | === beforeAction() === | ||
| <code php> | <code php> | ||
| /** | /** | ||
| Строка 65: | Строка 65: | ||
| // initialize ohCreatedAt | // initialize ohCreatedAt | ||
| if ( !$this->action && !$this->ohCreatedAt && $this->originalObject ) { | if ( !$this->action && !$this->ohCreatedAt && $this->originalObject ) { | ||
| + | $this->ohCreatedAt = DateTimeWrapper::Now(); | ||
| + | } else if ( $this->action == BaseSaveAction::DeleteAction ) { | ||
| $this->ohCreatedAt = DateTimeWrapper::Now(); | $this->ohCreatedAt = DateTimeWrapper::Now(); | ||
| } | } | ||
| Строка 72: | Строка 74: | ||
| </code> | </code> | ||
| - | В данном куске кода мы определили переменные ''user''(текущий пользователь VT) , ''ohCreatedAt'' (дата открытия страницы редактирования) | + | В данном куске кода мы определили переменные ''user'' (текущий пользователь в VT) , ''ohCreatedAt'' (дата открытия страницы редактирования, инициализируем её при первом открытии). |
| + | Не забудьте добавить в массив опций ''options'' : ''WithReturningKeys => true'' в конструкторе. | ||
| + | |||
| + | === validate() === | ||
| + | |||
| + | Добавим в ''validate()'' следующую проверку перед ''return $errors'': | ||
| + | <code php> | ||
| + | ObjectHistoryUtility::ValidateObject( $this->originalObject, $this->ohCreatedAt, $errors ); | ||
| + | </code> | ||
| + | Метод ''ObjectHistoryUtility::ValidateObject'' модифицирует массив ''$errors'' в случае необходимости, проверка идет при заполненном поле ''$this->originalObject''. | ||
| + | |||
| + | === add() === | ||
| + | |||
| + | <code php> | ||
| + | protected function add( $object ) { | ||
| + | ConnectionFactory::BeginTransaction(); | ||
| + | |||
| + | $result = parent::$factory->Add( $object, $this->options ); | ||
| + | $result = $result && ObjectHistoryUtility::Save( $object, $this->user ); | ||
| + | |||
| + | ConnectionFactory::CommitTransaction( $result ); | ||
| + | return $result; | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | === update() === | ||
| + | <code php> | ||
| + | protected function update( $object ) { | ||
| + | ConnectionFactory::BeginTransaction(); | ||
| + | $result = parent::$factory->Update( $object ); | ||
| + | |||
| + | $result = $result && ObjectHistoryUtility::Save( $object, $this->user ); | ||
| + | if ( $result ) { | ||
| + | Response::setParameter( 'ohCreatedAt', ObjectHistoryUtility::$LastCreatedAt ); | ||
| + | } | ||
| + | |||
| + | ConnectionFactory::CommitTransaction( $result ); | ||
| + | return $result; | ||
| + | } | ||
| + | </code> | ||
| + | Обновление ''ohCreatedAt'' необходимо для кнопки "Применить". | ||
| + | code php | ||
| + | ==== edit.tmpl.php ==== | ||
| + | |||
| + | Перед подключением шаблона ''data.tmpl.php'' добавьте: | ||
| + | <code php> | ||
| + | <?= FormHelper::FormHidden( 'ohCreatedAt', !empty( $ohCreatedAt ) ? $ohCreatedAt->format('c') : '' ); ?> | ||
| + | </code> | ||
| + | |||
| + | |||
| + | ==== data.tmpl.php ==== | ||
| + | После ''$errors["fatal"]'' добавьте: | ||
| + | <code php> | ||
| + | // Render Errors from Object History | ||
| + | echo ObjectHistoryUtility::RenderErrors( $errors ); | ||
| + | </code> | ||
| + | |||
| + | ==== ru.xml ==== | ||
| + | Перед закрытием тега ''errors'' добавьте: | ||
| + | |||
| + | <code xml> | ||
| + | <objectHistory> | ||
| + | <alreadyUpdated><![CDATA[Объект уже был обновлен ранее в %s. Вы не можете применить свои изменения. Снова <a href="%s">переоткройте</a> эту страницу.]]></alreadyUpdated> | ||
| + | <empty><![CDATA[Не удалось определить время открытия страницы. Вы не можете применить свои изменения. Снова <a href="%s">переоткройте</a> эту страницу.]]></empty> | ||
| + | </objectHistory> | ||
| + | </code> | ||
| + | |||
| + | ===== Результат ===== | ||
| + | При одновременном сохранении объекта второй получит вот такую ошибку: | ||
| + | |||
| + | {{ :eaze:samples:objecthistory-tv.png? |}} | ||
| + | |||
| + | ==== Плюсы ==== | ||
| + | * Универсальное решение для любого объекта в VT. | ||
| + | * Минимальные правки в код. | ||
| + | * Простая интеграция в существующие проекты. | ||
| + | |||
| + | ==== Минусы ==== | ||
| + | * Если открыть страницу, ничего не менять, и нажать кнопку "сохранить изменения", то дата последнего изменения обновится, хотя по логике вещей не должна - мы же ничего не меняли:). | ||
| + | |||
| + | ==== Дальнейшее развитие ==== | ||
| + | * Можно добавить кнопку на яваскрипте - "Все равно сохранить мою версию" (кнопка меняет значение ohCreatedAt на now() и делает submit формы). | ||
| + | * Можно добавить в таблицу ''objectHistory'' поле ''changes'', в котором в сериализованном виде хранить изменившиеся значения полей у объекта (и его листов). После создания такой функциональности берегитесь своей бурной фантазии :). | ||