有没有办法在 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 从 idid。这意味着没有活动行就不能存在版本,这不是我想要的:我希望能够删除行并保留版本。

我认为这种行为会像 Archivable 一样,即 delete() 方法会被拦截并从其通常的方法修改。不幸的是,正如 the documentation 确认的那样,此方法会删除活动行 任何先前版本:

void delete(): Deletes the object version history

我尝试混合使用 ArchivableVersionable,但这似乎生成了在 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