Is there a way to combine Archivable and Versionable behavior in Propel 1?

I am using Propel 1 on a fairly large project and currently the live version uses the behavior Archivable

. Thus, when a row is deleted, the behavior transparently intercepts the call and moves the row to the archive table. This works great.

I want to change how this table works so that all saved versions are versions. So in the trait branch I removed Archivable

and added the behavior Versionable

. This drops the auto-generated table (table)_archive

and adds the table instead (table)_version

.

However, it is interesting that the version table has a PK (id, version)

with a foreign key for the live table from id

to id

. This means that versions cannot exist without a live string, which is what I don't want: I want to be able to delete the string and keep the versions.

I thought this behavior would act like Archivable

, i.e. the method delete()

will be intercepted and changed from its usual approach. Unfortunately, as documented , this method removes the straight line and any previous versions:

void delete()

: removes the object's version history

I've tried mixing both Archivable

and Versionable

but that seems to cause the code in the API to fail Query

: it tries to call a method archive()

that doesn't exist. I expect this combination of behaviors was never meant to work (ideally it should be captured in the schematic, and perhaps it will be captured in Propel 2).

One solution is to try the behavior SoftDelete

instead Archivable

- this just marks the records as deleted rather than moving them to another table. This can be problematic, however, because joining a table with this behavior can result in incorrect counts for unused rows (and the Propel team decided to opt out for this reason). It also feels like a rabbit hole that I don't want to let down as the amount of refactoring can get out of hand.

So I was left looking for a better approach to implementing a version control system that does not remove old versions when the live copy is removed. I can do this manually by intercepting the save and delete methods in the model class, but it seems like a waste when it Versionable

almost does what I want. Are there related options that I can tweak, or is there any point in writing custom behavior? A quick glance at the templating code for basic behaviors makes me want to get away from the latter!

+3


source to share


1 answer


Here is the solution I came across. My memory is rather hazy, but it looks like I took an existing one VersionableBehaviour

and got a new behavior from it, which I named HistoryVersionableBehaviour

. This way, it uses all the features of the main behavior and then just overrides the generated delete with its own code.

Here's the behavior:

<?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;
    }
}

      

This requires something called a modifier, which is run at generation time to create base instance 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;
    }
}

      

The parent class has 798 lines, so my approach seems to have saved a lot of code, built everything from scratch!



You need to specify the behavior in the XML file for each table for which you want to activate it:

<table name="job">
    <!--- your columns... -->
    <behavior name="timestampable" />
    <behavior name="history_versionable" />
</table>

      

I'm not sure if my behavior requires a behavior timestampable

- my guess is not, as it looks like the parent behavior is just adding columns to the versioned table, not to the table. If you can try this without behavior timestampable

, let me know how you handle so I can update this post.

Finally, you need to provide the location of your class so that Propel 1's custom autoloader knows where to find it. I use this in mine build.properties

:

# Declare a custom behaviour
propel.behavior.history_versionable.class = ${propel.php.dir}.WebScraper.Behaviours.HistoryVersionable.HistoryVersionableBehavior

      

+1


source







All Articles