Versioning support

PHPCR-ODM natively supports versioning documents, using the power of the PHPCR Version feature. This is an optional feature of the PHPCR specification, so make sure your PHPCR implementation supports it.

PHPCR-ODM does not replicate the complete PHPCR Version API (PHPCR\VersionManager, PHPCR\VersionHistory and PHPCR\Version). For the full flexibility, you would have access the PHPCR session and interact with the VersionManager directly.

But PHPCR-ODM provides simple methods for the common operations, covering the most common use cases.

Philosophy

PHPCR has 2 levels: simpleVersionable and (full) versionable. Simple versioning consists of a linear version history and the checkin/checkout methods to create new versions. Checking in a node creates a new version and makes the node readonly. You need to check it out again to write to it (or just do a checkpoint to do both in one call).

Full versioning additionally offers branches for non-linear versioning (which the PHPCR-ODM does not provide any helper methods for) and version labels (which we plan to support once Jackalope supports them). For each node, you can add labels to a version, but one specific label may only occur once per version history of a node (meaning if you want to label another version, you need to remove the label from the first version before you add the label).

The full versioning corresponds to the PHPCR mix:versionable type. that allows to branch versions. If you need some of the full versionable features, you will have to access PHPCR directly for those operations.

Version names are generated by PHPCR and can not be controlled by the client application. There is no concept of commit messages for PHPCR. We decided to not build something like that into the core of the ODM versioning system to avoid unnecessary overhead if the user does not need it. It is however doable with having a field on your document that you set to your commit message and flush the document manager before calling checkin().

For more background, read the Versioning section in the PHPCR Tutorial and refer to the JCR 2.0 specification, Chapter 15.

PHPCR-ODM

For the PHPCR-ODM layer, the following applies: Contrary to translations, getting an old version does not change the document representing the current version. An old version can’t be modified and can’t be persisted. (Except with the special restoreVersion and removeVersion methods.)

What you get is a detached instance of the document which is ignored by flush and can not be persisted.

Mappings

To version documents, you need to set the versionable attribute on the document mapping. You can choose between “full” and “simple” versionable. For PHPCR-ODM, both are equivalent, so if you do not plan to use the underlying PHPCR session to use full versioning, you should use simple versioning to be compatible with as many implementations as possible.

Due to implementation limitations, the Locale field is required on all translatable documents.

  • PHP
    <?php
    /**
     * @Document(versionable="simple")
     */
    class MyPersistentClass
    {
        /** @VersionName */
        private $versionName;
    
        /** @VersionCreated */
        private $versionCreated;
    }
    
  • XML
    <doctrine-mapping>
        <document class="MyPersistentClass" versionable="simple">
            <version-name fieldName="versionName"/>
            <version-created fieldName="versionCreated"/>
        </document>
    </doctrine-mapping>
    
  • YAML
    MyPersistentClass:
        versionable: simple
        versionName: versionName
        versionCreated: versionCreated
    

Be aware that these two are different things:

  • The document that is versionable. This is the document and you can take snapshots of this document with the checkin() / checkpoint() methods.
  • The detached document that represents an old version of your document (a PHPCR frozen node). You get this document with the findVersionByName method. It is read-only. The document class you use needs not be the same. You can define a version document that is the same as your base document, but all fields are read only and you use the VersionName and VersionCreated annotations on it. It also does not need the versionable document attribute. (You do not create versions of old versions, you only create versions of the main document.)

You can track some information about old versions in PHPCR-ODM. Both are only populated when you load an old version of a document, and both are read only. The VersionName tracks the version identifier that PHPCR assigned the version you created, VersionCreated the DateTime when the version was created.

Note that all fields of a document are automatically versioned, you can not exclude anything from being versioned. Referenced documents are not versioned at the same time, but it is stored to which document the reference pointed at this time. Children and parents are not versioned by default. Children can be versioned by defining a PHCPR node type that specifies to cascade versioning. This feature however is untested with PHPCR-ODM, if you have feedback please tell us.

Warning

Referencial integrity with hard references is only ensured for live documents, not for versions. It is thus possible that a reference that is declared “hard” can be null nontheless. Make sure to always check for null values in version documents.

Interacting with versions

See the Phpdoc for full details on those methods.

Reading:

  • DocumentManager::find() works as normal, always gives you the current latest version.
  • DocumentManager::getAllLinearVersions($document) returns an array with all version names for this document, ordered from most recent to oldest version. You can specify an optional limit to only get that many most recent versions.
  • DocumentManager::findVersionByName($id, $versionName) get a detached read-only document for a specific version.

Modify the version history:

  • DocumentManager::checkin create new version of a flushed document and make it readonly
  • DocumentManager::checkout make a document that was checked in writable again
  • DocumentManager::checkpoint create a new version without making the document read-only, aka checkin followed by checkout
  • DocumentManager::restoreVersion restore the document to an old version
  • DocumentManager::removeVersion completely remove an old version from the history

Full Example

<?php
$article = new Article();
$article->id = '/test';
$article->topic = 'Test';
$dm->persist($article);
$dm->flush();

// generate a version snapshot of the document as currently stored
$dm->checkpoint($article);

$article->topic = 'Newvalue';
$dm->flush();

// get the version information
$versioninfos = $dm->getAllLinearVersions($article);
$firstVersion = reset($versioninfos);
// and use it to find the snapshot of an old version
$oldVersion = $dm->findVersionByName(null, $article->id, $firstVersion['name']);

echo $oldVersion->topic; // "Test"

// find the head version
$article = $dm->find('/test');
echo $article->topic; // "Newvalue"

// restore the head to the old version
$dm->restoreVersion($oldVersion);

// the article document is refreshed
echo $article->topic; // "Test"

// create a second version to demo removing a version
$article->topic = 'Newvalue';
$dm->flush();
$dm->checkpoint($article);

// remove the old version from the history (not allowed for the last version)
$dm->removeVersion($oldVersion);
Fork me on GitHub