Я использую Propel 1 в довольно крупном проекте, и в настоящее время живая версия использует Archivable
поведение. Таким образом, когда строка удаляется, поведение прозрачно перехватывает вызов и перемещает строку в архивную таблицу. Это отлично работает.
Я пытаюсь изменить работу этой таблицы, чтобы все сохранения были версионными. Поэтому в ветви функций я удалил Archivable
и добавил Versionable
поведение. Это падает (table)_archive
автоматически сгенерированная таблица и добавляет (table)_version
стол вместо.
Однако, что интересно, таблица версий имеет PK (id, version)
с внешним ключом к живому столу из id
в id
, Это означает, что версии не могут существовать без активной строки, а это не то, чего я хочу: я хочу иметь возможность удалить строку и сохранить версии.
Я думал, что это поведение будет вести себя как Archivable
то есть delete()
метод будет перехвачен и изменен по сравнению с обычным подходом. К сожалению, что подтверждается документация, этот метод удаляет живую строку а также любые предыдущие версии:
void delete()
: Удаляет историю версий объекта
Я пытался смешать оба Archivable
а также Versionable
, но это, кажется, генерирует код, который падает в Query
API: он пытается вызвать archive()
метод, который не существует. Я ожидаю, что это сочетание поведения никогда не предназначалось для работы (в идеале оно должно быть перехвачено во время сборки схемы, и, возможно, это будет исправлено в Propel 2).
Одним из решений является попытка SoftDelete
поведение вместо Archivable
— это просто помечает записи как удаленные, а не перемещает их в другую таблицу. Однако это может быть проблематично, потому что присоединение к таблице с таким поведением может дать неправильный счет для не удаленных строк (и команда Propel решила отказаться от него по этой причине). Это также похоже на кроличью нору, которую я не хочу закрывать, так как количество рефакторинга может выйти из-под контроля.
Таким образом, мне остается искать лучший подход для реализации системы управления версиями, которая не удаляет старые версии при удалении действующей копии. Я могу сделать это вручную, перехватывая методы сохранения и удаления в классе модели, но это кажется пустой тратой, когда Versionable
почти делает то, что я хочу. Существуют ли соответствующие параметры, которые я могу настроить, или есть смысл в написании собственного поведения? Беглый взгляд на код генерации шаблонов для поведения ядра заставляет меня убегать от последнего!
Вот решение, которое я придумал. Моя память довольно туманна, но похоже, что я взял VersionableBehaviour
и вывел из него новое поведение, которое я назвал HistoryVersionableBehaviour
, Таким образом, он использует все особенности поведения ядра, а затем просто переопределяет сгенерированное удаление своим собственным кодом.
Вот само поведение:
<?php
// This is how the versionable behaviour works
require_once dirname(__FILE__) . '/HistoryVersionableBehaviorObjectBuilderModifier.php';
class HistoryVersionableBehavior extends VersionableBehavior
{
/**
* Reset the FKs from CASCADE ON DELETE to no action
*
* (I expect all future migration diffs will incorrectly try to re-add the constraint
* I manually removed from the migration that introduced versioning, may try to fix
* that another time. 'Tis fine for now).
*/
public function addVersionTable()
{
parent::addVersionTable();
$this->swapAllForeignKeysToNoDeleteAction();
$this->addVersionArchivedColumn();
}
protected function swapAllForeignKeysToNoDeleteAction()
{
$versionTable = $this->lookupVersionTable();
$fks = $versionTable->getForeignKeys();
foreach ($fks as $fk)
{
$fk->setOnDelete(null);
}
}
protected function addVersionArchivedColumn()
{
$versionTable = $this->lookupVersionTable();
$versionTable->addColumn(array(
'name' => 'archived_at',
'type' => 'timestamp',
));
}
protected function lookupVersionTable()
{
$table = $this->getTable();
$versionTableName = $this->getParameter('version_table') ?
$this->getParameter('version_table') :
($table->getName() . '_version');
$database = $table->getDatabase();
return $database->getTable($versionTableName);
}
/**
* Point to the custom object builder class
*
* @return HistoryVersionableBehaviorObjectBuilderModifier
*/
public function getObjectBuilderModifier()
{
if (is_null($this->objectBuilderModifier)) {
$this->objectBuilderModifier = new HistoryVersionableBehaviorObjectBuilderModifier($this);
}
return $this->objectBuilderModifier;
}
}
Для этого требуется нечто под названием модификатор, который запускается во время генерации для создания базовых классов экземпляров:
<?php
class HistoryVersionableBehaviorObjectBuilderModifier extends \VersionableBehaviorObjectBuilderModifier
{
/**
* Don't do any version deletion after the main deletion
*
* @param \PHP5ObjectBuilder $builder
*/
public function postDelete(\PHP5ObjectBuilder $builder)
{
$this->builder = $builder;
$script = "// Look up the latest version
\$latestVersion = {$this->getVersionQueryClassName()}::create()->
filterBy{$this->table->getPhpName()}(\$this)->
orderByVersion(\Criteria::DESC)->
findOne(\$con);
\$latestVersion->
setArchivedAt(time())->
save(\$con);
";
return $script;
}
}
Родительский класс имеет 798 строк, поэтому мой подход, похоже, сохранил много кода, а не создавал его с нуля!
Вам нужно будет указать поведение в вашем XML-файле для каждой таблицы, для которой вы хотите его активировать:
<table name="job">
<!--- your columns... -->
<behavior name="timestampable" />
<behavior name="history_versionable" />
</table>
Я не уверен, требует ли мое поведение присутствия timestampable
поведение — мое предположение нет, так как похоже, что родительское поведение просто добавляет столбцы в версионную таблицу, а не в саму таблицу. Если вы можете попробовать это без timestampable
поведение, дайте мне знать, как вы поживаете, так что я могу обновить этот пост.
Наконец, вам нужно указать местоположение вашего класса, чтобы пользовательский автозагрузчик Propel 1 знал, где его найти. Я использую это в моем build.properties
:
# Declare a custom behaviour
propel.behavior.history_versionable.class = ${propel.php.dir}.WebScraper.Behaviours.HistoryVersionable.HistoryVersionableBehavior
Других решений пока нет …