Index of /plugins/sfPropelVersionableBehaviorPlugin/
| Name | Last Modified | Size |
|---|---|---|
| 2026-04-29 19:53 | - | |
| 2026-04-29 19:53 | - | |
| 2026-04-29 19:53 | 2k |
= sfPropelVersionableBehaviorPlugin plugin =
The `sfPropelVersionableBehaviorPlugin` is a symfony plugin that provides versioning capabilities to any Propel object.
== Features ==
* Revert objects to previous versions easily
* Track and browse history of modifications on every object
* Conditional versioning
* Fully unit tested
== Installation ==
* Install the plugin
{{{
#!sh
> php symfony plugin-install http://plugins.symfony-project.com/sfPropelVersionableBehaviorPlugin
}}}
* Enable Propel behavior support in `propel.ini`:
{{{
propel.builder.AddBehaviors = true
}}}
* Add a `version` field to each of the model tables that you want to make versionable:
{{{
#!xml
<!-- schema.xml -->
<column name="version" type="INTEGER" />
or
# config/schema.yml
version: { type: integer }
}}}
Alternatively, you can choose another name that `version` and declare it in the behavior initialization. Even though, the behavior will still provide a `getVersion` and `setVersion` method for your versionable models.
* Rebuild your model and sql, insert the plugin tables to your database, and the new version column to your versionable tables:
{{{
#!sh
> php symfony propel-build-model
> php symfony propel-build-sql
> mysql -uroot -p mydb < data/sql/plugins.sfPropelVersionableBehaviorPlugin.lib.model.schema.sql
> mysql -uroot -p mydb -e 'ALTER TABLE `Article` ADD `version` INTEGER NOT NULL;'
}}}
* Enable the behavior for the Propel models that you want to extend. For instance, to extend an `Article` Propel class:
{{{
#!php
<?php
// lib/model/Article.php
class Article
{
}
sfPropelBehavior::add('Article', array('versionable'));
}}}
If the model uses a version column name diffeent than `version`, declare it here as the 'version' parameter of the behavior initialization:
{{{
#!php
sfPropelBehavior::add('Article', array('versionable' => array('columns' => array('version' => 'my_version_column'))));
}}}
* Clear the cache
{{{
#!sh
> php symfony cc
}}}
== Usage ==
=== Reverting to a previous version ===
{{{
#!php
<?php
$article = new Article();
$article->setTitle('First version of article');
$article->save(); // $article->getVersion() == 1
$article->setTitle('Second version of article');
$article->save(); // $article->getVersion() == 2
$article->toVersion(1); // $article->getTitle() == 'First version of article'
$article->save(); // $article->getVersion() == 3
}}}
=== Conditional versioning ===
You may not want to have a new version of resource created each time it is saved.
Just add a `versionConditionMet()` method to your stub class. It is called each time object's `save()`.
No version is created if it returns false.
Example :
{{{
#!php
<?php
// lib/model/Article.php
public function versionConditionMet()
{
return $this->getTitle() != 'do not version me';
}
}}}
{{{
#!php
<?php
$article = new Article();
$article->setTitle('New article');
$article->save(); // article is saved and a new version is created
$article->setTitle('do not version me');
$article->save(); // article is saved, no new version is created
}}}
It is possible to specify a different `versionConditionMet()` method name by defining it when registring behavior :
{{{
#!php
<?php
sfPropelBehavior::add('Article', array('versionable' => array('columns' => $columns_map, 'conditional' => 'myMethod')));
}}}
It is possible to change this method at runtime :
{{{
#!php
<?php
$previous_method = sfPropelVersionableBehavior::setVersionConditionMethod('myMethod');
}}}
Alternativley, you can choose to disable the automated creation of a new version at each save for all models by changing the application configuration:
{{{
# config/app.yml
all:
sfPropelVersionableBehaviorPlugin:
auto_versioning: false
}}}
In this case, you still have the way to manually create a new version of an object:
{{{
#!php
<?php
$article->setTitle('Please version me even though auto_versioning is false');
$article->addVersion();
$article->save(); // article is saved and a new version is created
}}}
=== Customizing the behavior ===
During initialization, you can define the name of the 'version' column if different from 'version:
{{{
#!php
sfPropelBehavior::add('Article', array('versionable' => array('columns' => array(
'version' => 'my_version_column'
))));
}}}
If your model contains a 'title' column, the behavior will automatically copy it for reference into its internal `ResourceVersion` objects. But you can specify that the revision object title can come from another column:
{{{
#!php
sfPropelBehavior::add('Article', array('versionable' => array('columns' => array(
'title' => 'my_title_column'
))));
}}}
=== Giving details about each revision ===
For future reference, you probably need to record who edited an object, as well as when and why. While editing your object, you can define an author name and a comment via the `setVersionCreatedBy()` and `setVersionComment()` methods, as follows:
{{{
#!php
<?php
$article = new Article();
$article->setTitle('Original title');
$article->setVersionCreatedBy('John Doe');
$article->setVersionComment('Article creation');
$article->save();
$article->setTitle('A much better title');
$article->setVersionCreatedBy('John Doe');
$article->setVersionComment('I didn\'t like the previous title so much');
$article->save();
}}}
=== Retrieving a resource version history ===
Details about each revision are available in the object via the `getVersionCreatedBy` and `getVersionComment` methods. For instance, if you want to display a history of modifications, you can do as follows:
{{{
#!php
<?php
foreach ($article->getAllVersions() as $history_article)
{
echo sprintf("'%s', Version %d, updated by %s on %s (%s)\n",
$history_article->getTitle(),
$history_article->getVersion(),
$history_article->getVersionCreatedBy(),
$history_article->getVersionCreatedAt(),
$history_article->getVersionComment(),
);
}
/*
* Outputs:
*
* 'Original title', Version 1, updated by John Doe on 2008-02-08 09:25:12 (Article Creation)
* 'A much better title', Version 2, updated by John Doe on 2008-02-08 09:25:15 (I didn't like the previous title so much)
*/
}}}
Note: In the above example, the `getAllVersions()` method hydrates a list of `Article` objects while all you need is information about the revisions alone. A lighter way to do the same thing would consist of manipulating the `ResourceVersion` objects that you can get via `getAllResourceVersions()`:
{{{
#!php
<?php
foreach ($article->getAllResourceVersions() as $resourceVersion)
{
echo sprintf("'%s', Version %d, updated by %s on %s (%s)\n",
$resourceVersion->getTitle(),
$resourceVersion->getNumber(),
$resourceVersion->getCreatedBy(),
$resourceVersion->getCreatedAt(),
$resourceVersion->getComment(),
);
}
}}}
Tip: If you have `auto_versioning` set to off and use the manual `addVersion()` process, you can pass the author of the revision and the comment as parameters to the method call, as follows:
{{{
#!php
<?php
$article = new Article();
$article->setTitle('Original title');
$article->addVersion('John Doe', 'Article creation');
$article->save();
}}}
=== Versioning Related objects ===
You can specify that you want related objects to be versioned together with the main object. For instance, imagine an `Article` model with a many-to-one relationship to a `Category` model:
{{{
#!php
// Explicitly ask to include the `Category` object in the versioning process
sfPropelBehavior::add('Article', array('versionable' => array(
'with' => array('Category')
)));
$article = new Article();
$article->setTitle('Original title');
$category = new Category();
$category->setName('Category1');
$article->setCategory($category);
$article->save();
$article->setTitle('Modified title');
$category = new Category();
$category->setName('Category1');
$article->setCategory($category);
$article->save();
$article->toVersion(1);
echo $article->getCategory()->getName(); // 'Category1'
$article->toVersion(2);
echo $article->getCategory()->getName(); // 'Category2'
}}}
You can choose to include different related objects if you use the `addVersion()` method. Specify which objects to include in the versioning process in an array, and use that array as a third argument of `addVersion()`. For instance, if you didn't add any `with` parameter during the behavior declaration, you can still save the related `Category` objects in the example above by calling, before each `save()`:
{{{
#!php
...
$article->addVersion('author1', 'comment1', array('Category'));
$article->save();
...
$article->addVersion('author2', 'comment2', array('Category'));
$article->save();
}}}
The same works for one-to-many relationhips, with a trick. For instance, if the `Article` model can have many `Comments`:
{{{
#!php
<?php
// Explicitly ask to include the `Comment` objects in the versioning process
// Note that `Comment` is declared as a plural
sfPropelBehavior::add('Article', array('versionable' => array(
'with' => array('Comments')
)));
$article = new Article();
$article->setTitle('Original title');
$comment1 = new Comment();
$comment1->setContent('Comment1');
$article->addComment($comment1);
$comment2 = new Comment();
$comment2->setContent('Comment2');
$article->addComment($comment2);
$article->save();
$article->setTitle('Modified title');
$comment1->setContent('Comment1 Modified');
$comment1->save();
$comment2->setContent('Comment2 Modified');
$comment2->save();
$article->save();
$comments = $article->toVersion(1)->getComments();
echo $comments[0]->getContent(); // 'Comment1'
echo $comments[1]->getContent(); // 'Comment2'
$comments = $article->toVersion(2)->getComments();
echo $comments[0]->getContent(); // 'Comment1 Modified'
echo $comments[1]->getContent(); // 'Comment2 Modified'
}}}
Note: For the one-to-many versioning to work, you need to override the base object `initXXX()` method. In the above example, you must override the `Article::initComments()` method from:
{{{
// in BaseArticle.php
public function initComments()
{
if ($this->collComments === null) {
$this->collComments = array();
}
}
}}}
to:
{{{
// in Article.php
public function initComments($force = false)
{
if ($this->collComments === null || $force) {
$this->collComments = array();
}
}
}}}
This modification should not affect the rest of your model.
Alternatively, if you prefer to let the Propel generator modify your `initXXX()` methods automatically for all models, you just need to change one line in your `propel.ini` and rebuild your model:
{{{
propel.builder.object.class = plugins.sfPropelVersionableBehaviorPlugin.lib.SfVersionableObjectBuilder
}}}
Note: If you use the [http://trac.symfony-project.com/wiki/sfPropelAlternativeSchemaPlugin sfPropelAlternativeSchemaPlugin] plugin, you don't need to change the Propel object builder, since the alternative schema's builder includes this modification.
== Public API ==
=== Object API ===
Enabling the behaviors adds / modifies the following method to the Propel objects :
* `void save()`: Adds a new version to the object version history and increments the `version` property
* `void delete()`: Deletes the object version history
* `void toVersion(integer $version_number)`: Populates the properties of the current object with values from the requested version. Beware that saving the object afterwards will create a new version (and not update the previous version).
* `boolean isLastVersion()`: Returns true if the current object is the last available version
* `array getAllVersions()`: Returns all versions of the object in an ordered array
* `void addVersion(string $updatedBy, string $comment, array $withObjects)`: Increments the object's version number (without saving it) and creates a new ResourceVersion record. To be used when versionConditionMet() is false
* `ResourceVersion getLastResourceVersion()`: Returns the object's last version object
* `ResourceVersion getCurrentResourceVersion()`: Returns the object's current version object
* `ResourceVersion getResourceVersion(integer $version_number)`: Returns the object's numbered version object
* `array getAllResourceVersions()`: Returns all version objects in an array
* `void setResourceCreatedBy(string $createdBy)`: Defines the author name for the revision
* `string getResourceCreatedBy()`: Gets the author name for the revision
* `mixed getResourceCreatedAt()`: Gets the creation date for the revision (but you'd better have an `updated_at` column in your model)
* `void setResourceComment(string $comment)`: Defines the comment for the revision
* `string getResourceComment()`: Gets the comment for the revision
=== !ResourceVersion API ===
* `BaseObject getResourceInstance()`: Returns resource instance populated with attributes from the revision
* `int getNumber()`: Returns the version number of the object revision
* `string getCreatedBy()`: Returns the author of the object revision
* `string getTitle()`: Returns the title of the object revision
* `string getComment()`: Returns the comment of the object revision
* `mixed getCreatedAt(string $format)`: Returns the date of the object revision
=== sfPropelVersionableBehavior API ===
* (static) `string setVersionConditionMethod(string $method_name)`: Sets object method used to decide if a new version should be created
* (static) `string getVersionConditionMethod()`: Returns version condition method name
== Roadmap ==
=== 0.5 ===
* Make plugin compatible with sfPropel's i18n capabilities
* Change the calls to `getPrimaryKey` by calls to a [wiki:sfPropelActAsRatableBehaviorPlugin]-style `getReferenceKey` to allow extending objects with multiple primary keys.
== Changelog ==
=== 2008-04-05 | Trunk ===
=== 2008-04-05 | 0.4.0 beta ===
* francois: Made incremental storage rely on a real version comparison, rather than the array of modified columns. Fixes modified columns not being saved when using `toVersion`.
* francois: Added the ability to declare related objects to save at behavior declaration
* francois: Fixed `ResourceVersion::getResourceInstance()` creates new objects and saving these objects creates a new row in the resource table (#3229)
* francois: Added `isLastVersion()` method
* francois: Avoid saving unchanged records to save database space (refs #3150)
* francois: Added `ResourceAttributeVersion::getResourceVersions()` method
* francois: Added `ResourceVersion::getResourceAttributeVersions()` method
* francois: Avoid saving unchanged columns to save database space
* francois: [BC Break] Added a `resource_attribute_version_hash` table, now middle table between versions and attributes
=== 2008-03-21 | 0.3.0 beta ===
* francois: [BC Break] Added a `title` column to the `resource_version` table and support for resource title
* francois: Added support for related objects versioning (only via `addVersion` for now)
* francois: [BC Break] Added a `resource_version_id` column to the `resource_version` table
* francois: Fixed error when using 'addVersion' on a new object (primary and foreign keys were not saved)
* francois: Added `getCurrentResourceVersion`, `setResourceCreatedBy`, `getResourceCreatedBy`, `setResourceComment` and `getResourceComment` methods to the public API
* francois: Fixed error when trying to add a version to an unsaved object
* francois: Fixed error when using a version column different than 'version'
* francois: [BC Break] Added `comment`, `created_by` and `created_at` columns to the `ResourceVersion` class.
* francois: Added `addVersion` method and refactored the behavior to keep D.R.Y.
* francois: More explicit documentation on installation
* francois: Added a few unit tests
* francois: Added a `getAllVersions` method returning an array of origin objects in a single query
* francois: [BC Break] Renamed `getAllVersions` to `getAllResourceVersions`
* francois: [BC Break] Renamed `getLastVersions` to `getLastResourceVersion`
* francois: [BC Break] Replaced uuid by a simple composite key class name + id
=== 2008-02-08 | 0.2.3 alpha ===
* francois: Fixed error when using a version column different than 'version'
=== 2008-02-06 | 0.2.2 alpha ===
* francois: Made the doc more explicit about multiple models (fixes #1820)
* francois: Removed need for hardcoded foreign key (fixes #1562)
* francois: Switched plugin schema to YAML
* francois: Made the unit tests more adaptable
=== 2007-16-03 | 0.2.1 alpha ===
* #1563 : does not create a version if YourClass::versionConditionMet() is not found (madman)
* #1564 : crashes while creating a new version if no prior version exists (madman)
* Syntax highlighting in README
* added `sfPropelVersionableBehavior::getVersionConditionMethod()` and `sfPropelVersionableBehavior::setVersionConditionMethod()` methods
* enhanced inner management of conditional versioning
* updated unit tests and docs accordingly
=== 2007-02-17 | 0.2.0 alpha ===
* made version number management more reliable
* new `getLastVersion()` method
* implemented conditional versioning
* updated docs and unit tests accordingly
=== 2007-02-17 | 0.1.0 alpha ===
Initial public release.
Proudly Served by LiteSpeed Web Server at iphix.fergumix.com Port 443