有没有办法在 Propel 1 中结合 Archivable 和 Versionable 行为?
Is there a way to combine Archivable and Versionable behaviours in Propel 1?
我在一个相当大的项目中使用 Propel 1,而实时版本目前使用 Archivable
行为。因此,当一行被删除时,该行为会透明地拦截调用并将该行移动到存档 table 中。这很好用。
我想改变 table 的工作方式,以便对所有保存进行版本控制。因此,在功能分支上,我删除了 Archivable
并添加了 Versionable
行为。这会删除 (table)_archive
自动生成的 table 并添加一个 (table)_version
table。
然而,有趣的是,table 版本的 PK 为 (id, version)
,外键为实时 table 从 id
到 id
。这意味着没有活动行就不能存在版本,这不是我想要的:我希望能够删除行并保留版本。
我认为这种行为会像 Archivable
一样,即 delete()
方法会被拦截并从其通常的方法修改。不幸的是,正如 the documentation 确认的那样,此方法会删除活动行 和 任何先前版本:
void delete()
: Deletes the object version history
我尝试混合使用 Archivable
和 Versionable
,但这似乎生成了在 Query
API 中崩溃的代码:它试图调用 archive()
不存在的方法。我希望这种行为组合从来没有打算起作用(理想情况下,它应该在模式构建时被捕获,也许这将在 Propel 2 中得到修复)。
一个解决方案是尝试 SoftDelete
行为而不是 Archivable
- 这只是将记录标记为已删除而不是将它们移动到另一个 table。然而,这可能会有问题,因为使用这种行为加入 table 可能会对未删除的行给出错误的计数(因此 Propel 团队决定弃用它)。它也感觉像一个我不想下去的兔子洞,因为重构的数量可能会失控。
因此,我只能寻求一种更好的方法来实现版本控制系统,该系统在删除 Live Copy 时不会删除旧版本。我可以通过拦截模型 class 中的保存和删除方法来手动执行此操作,但是当 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;
}
}
这需要一种叫做修饰符的东西,它在生成时是 运行 来生成基础实例 classes:
<?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;
}
}
父 class 有 798 行,所以我的方法似乎确实节省了大量代码,而不是从头开始构建它!
您需要在 XML 文件中为每个要激活它的 table 指定行为:
<table name="job">
<!--- your columns... -->
<behavior name="timestampable" />
<behavior name="history_versionable" />
</table>
我不确定我的行为是否需要 timestampable
行为的存在 - 我的猜测是不需要,因为看起来父行为只是向版本化 table 添加列而不是table 本身。如果您能够在没有 timestampable
行为的情况下尝试此操作,请告诉我您的进展情况,以便我更新此 post。
最后,您需要指定 class 的位置,以便 Propel 1 自定义自动加载器知道在哪里可以找到它。我在 build.properties
:
中使用它
# Declare a custom behaviour
propel.behavior.history_versionable.class = ${propel.php.dir}.WebScraper.Behaviours.HistoryVersionable.HistoryVersionableBehavior
我在一个相当大的项目中使用 Propel 1,而实时版本目前使用 Archivable
行为。因此,当一行被删除时,该行为会透明地拦截调用并将该行移动到存档 table 中。这很好用。
我想改变 table 的工作方式,以便对所有保存进行版本控制。因此,在功能分支上,我删除了 Archivable
并添加了 Versionable
行为。这会删除 (table)_archive
自动生成的 table 并添加一个 (table)_version
table。
然而,有趣的是,table 版本的 PK 为 (id, version)
,外键为实时 table 从 id
到 id
。这意味着没有活动行就不能存在版本,这不是我想要的:我希望能够删除行并保留版本。
我认为这种行为会像 Archivable
一样,即 delete()
方法会被拦截并从其通常的方法修改。不幸的是,正如 the documentation 确认的那样,此方法会删除活动行 和 任何先前版本:
void delete()
: Deletes the object version history
我尝试混合使用 Archivable
和 Versionable
,但这似乎生成了在 Query
API 中崩溃的代码:它试图调用 archive()
不存在的方法。我希望这种行为组合从来没有打算起作用(理想情况下,它应该在模式构建时被捕获,也许这将在 Propel 2 中得到修复)。
一个解决方案是尝试 SoftDelete
行为而不是 Archivable
- 这只是将记录标记为已删除而不是将它们移动到另一个 table。然而,这可能会有问题,因为使用这种行为加入 table 可能会对未删除的行给出错误的计数(因此 Propel 团队决定弃用它)。它也感觉像一个我不想下去的兔子洞,因为重构的数量可能会失控。
因此,我只能寻求一种更好的方法来实现版本控制系统,该系统在删除 Live Copy 时不会删除旧版本。我可以通过拦截模型 class 中的保存和删除方法来手动执行此操作,但是当 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;
}
}
这需要一种叫做修饰符的东西,它在生成时是 运行 来生成基础实例 classes:
<?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;
}
}
父 class 有 798 行,所以我的方法似乎确实节省了大量代码,而不是从头开始构建它!
您需要在 XML 文件中为每个要激活它的 table 指定行为:
<table name="job">
<!--- your columns... -->
<behavior name="timestampable" />
<behavior name="history_versionable" />
</table>
我不确定我的行为是否需要 timestampable
行为的存在 - 我的猜测是不需要,因为看起来父行为只是向版本化 table 添加列而不是table 本身。如果您能够在没有 timestampable
行为的情况下尝试此操作,请告诉我您的进展情况,以便我更新此 post。
最后,您需要指定 class 的位置,以便 Propel 1 自定义自动加载器知道在哪里可以找到它。我在 build.properties
:
# Declare a custom behaviour
propel.behavior.history_versionable.class = ${propel.php.dir}.WebScraper.Behaviours.HistoryVersionable.HistoryVersionableBehavior