Propel 1.6 Gets Versionable Behavior - With A Twist

Propel 1.6 ships with a great new behavior. Once enabled on a table, the versionable behavior stores a copy of the ActiveRecord object in a separate table each time it is saved. This allows to keep track of the changes made on an object, whether to review modifications, or revert to a previous state.

The classic Wiki example is a good illustration:

<table name="wiki_page">
  <column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
  <column name="title" type="VARCHAR" required="true" />
  <column name="body" type="LONGVARCHAR" />
  <behavior name="versionable" />
</table>

After rebuild, the WikiPage model has versioning abilities:

$page = new WikiPage();

// automatic version increment
$page->setTitle('Propel');
$page->setBody('Propel is a CRM built in PHP');
$page->save(); 
echo $page->getVersion(); // 1
$page->setBody('Propel is an ORM built in PHP5');
$page->save();
echo $page->getVersion(); // 2

// reverting to a previous version
$page->toVersion(1);
echo $page->getBody(); // 'Propel is a CRM built in PHP'
// saving a previous version creates a new one
$page->save();
echo $page->getVersion(); // 3

// checking differences between versions
print_r($page->compareVersions(1, 2));
// array(
//   'Body' => array(
//      1 => 'Propel is a CRM built in PHP',
//      2 => 'Propel is an ORM built in PHP5'
//    ),
// );

// deleting an object also deletes all its versions
$page->delete();

The versionable behavior offers audit log functionality, so you can track who made a modification, when, and why:

$page = new WikiPage();
$page->setTitle('PEAR');
$page->setBody('PEAR is a framework and distribution system for reusable PHP components');
$page->setVersionCreatedBy('John Doe');
$page->setVersionComment('First draft');
$page->save();
// do more modifications...

// list all modifications
foreach ($page->getAllVersions() as $pageVersion) {
  echo sprintf("'%s', Version %d, updated by %s on %s (%s)\n",
    $pageVersion->getTitle(),
    $pageVersion->getVersion(),
    $pageVersion->getVersionCreatedBy(),
    $pageVersion->getVersionCreatedAt(),
    $pageVersion->getVersionComment(),
  );
}
// 'PEAR', Version 1, updated by John Doe on 2010-12-21 22:53:02 (First draft)
// 'PEAR', Version 2, updated by ...

If it was just for that, the versionable behavior would already be awesome. Versioning is a very common feature, and there is no doubt that this behavior will replace lots of boilerplate code. Consider the fact that it’s very configurable, fully documented, and unit tested, and there is no reason to develop your own versioning layer.

But there is more.

The versionable behavior also works on relationships.

If the WikiPage has one Category, and if the Category model also uses the versionable behavior, then each time a WikiPage is saved, it saves the version of the related Category it is related to, and it is able to restore it:

$category = new Category();
$category->setName('Libraries');
$page = new WikiPage();
$page->setTitle('PEAR');
$page->setBody('PEAR is a framework and distribution system for reusable PHP components');
$page->setCategory($category);
$page->save(); // version 1

$page->setTitle('PEAR - PHP Extension and Application Repository');
$page->save(); // version 2

$category->setName('PHP Libraries');
$page->save(); // version 3

$page->toVersion(1);
echo $page->getTitle(); // 'PEAR'
echo $page->getCategory()->getName(); // 'Libraries'
$page->toVersion(3);
echo $page->getTitle(); // 'PEAR - PHP Extension and Application Repository'
echo $page->getCategory()->getName(); // 'PHP Libraries'

Now the versioning is not limited to a single class anymore. You can even design a fully versionable "application" - it all depends on your imagination.

This feature is unique to Propel, and that’s our very Christmas gift to you.

Posted by Francois Zaninotto 

16 comments

Dec 22, 2010
Geoffrey said...
Hmm, in the relationships example, when you make a change in $category, do you have to save $page as well or is saving $category sufficient to propagate the versioning to $page?

I mean, if I have several pages sharing one category, do you have to save each of those pages each time you make a change in their category?

Dec 22, 2010
@Geoffrey: If you save the category first, it will also save the page(s). This is because "related object versioning" works both for one-to-many, and for many-to-one relationships.
Dec 22, 2010
Geoffrey said...
Awesome !

I must say propel is getting really really nice since 1.5 and I like the direction your heading it to. keep up the good work :)

Dec 22, 2010
pawel_k said...
when can we expect sfPropel16Plugin ? :)
Dec 22, 2010
Dec 22, 2010
Guilherme said...
"works both for one-to-many, and for many-to-one relationships" wow, that's awesome. Thank you Francois and Propel. I'm so glad I chose Propel to my project some months ago. :)
Dec 22, 2010
pawel_k said...
@Francois thats great, but can you write a simple tutorial how can i change Propel version in plugin ?
Dec 22, 2010
dereistee said...
This feature looks very nice. Thanks for the great Christmas gift! :-)
Dec 27, 2010
Oscar J. Baeza liked this post.
Dec 30, 2010
Marius Ghita liked this post.
Jan 07, 2011
nibsirahsieu said...
Thanks you for this great behavior.

i've followed your instructions above, but when i'm rebuilding the model, i got the following errors

Some problems occurred when executing the task:

build-propel.xml:538:20: Table "sf_blog_post" contains a foreign key to nonexistent table "sf_guard_user"


build-propel.xml:525:22: Execution of the target buildfile failed. Aborting.

If the exception message is not clear enough, read the output of the task for
more information

Jan 09, 2011
@nibsirahsieu: Please use the propel-developers mailing-list for support.
Jan 14, 2011
sachindurge said...
Its simply gr8 man.....
Dec 19, 2011
Sylvio said...
Is it possible to version not "automaticaly" each time the object is save but "manualy" with a specific method, example $object->saveNewVersion() and then save() method do not create new version but update current version.

Then it could be usefull to have a version label field. The we can do :
$object->saveNewVersion("John Doe version of the page with XWZ product information, have to be publish fo 3 feb 2012").

It could be usefull because sometime we prefer to have version management for a page which is not used for "automatic backup" but for "content workflow process".

Thanks for all this fantastic features !

May 09, 2012
Erdal G. said...
Thank you very much but this feature is kind of useless as it cascade deletes all the versions on delete... (and it is not compatible with 'archivable' but this is an issue already posted on propel's bugtracker)

Leave a comment...