有没有办法在Propel 1中组合Archivable和Versionable行为?

时间:2015-08-02 18:41:06

标签: php propel behavior

我在一个相当大的项目中使用Propel 1,而实时版本目前使用Archivable行为。因此,当删除行时,行为会透明地拦截调用并将行移动到存档表中。这很好。

我希望更改此表的工作方式,以便对所有保存进行版本控制。因此,在功能分支上,我删除了Archivable并添加了Versionable行为。这会丢弃(table)_archive自动生成的表,而是添加(table)_version表。

然而,有趣的是,版本表的PK为(id, version),其中包含从idid的实时表的外键。这意味着没有实时行就不能存在版本,这不是我想要的:我希望能够删除一行并保留版本。

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

  

void delete():删除对象版本历史记录

我尝试混合使用ArchivableVersionable,但这似乎会生成在Query API中崩溃的代码:它尝试调用不archive()的方法存在。我希望这种行为混合从来没有打算工作(理想情况下它应该在架构构建时捕获,也许这将在Propel 2中修复)。

一种解决方案是尝试SoftDelete行为而不是Archivable - 这只是将记录标记为已删除而不是将其移动到另一个表。但是,这可能会有问题,因为使用此行为连接到表可能会为未删除的行提供错误的计数(并且Propel团队因此而决定弃用它)。它也感觉就像一个兔子洞,我不想失望,因为重构的数量可能会失控。

因此,我不得不寻求一种更好的方法来实现在删除实时副本时不删除旧版本的版本控制系统。我可以通过拦截模型类中的保存和删除方法来手动执行此操作,但是当Versionable几乎完成我想要的操作时,这似乎是浪费。我可以调整相关参数,还是编写自定义行为有价值?快速浏览核心行为的模板生成代码会让我想要逃避后者!

1 个答案:

答案 0 :(得分:1)

这是我提出的解决方案。我的记忆相当朦胧,但看起来我已经采用现有的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