serlo-org/athene2-versioning

Zend Framework 2 Module that provides versioning components for Athene2


README

Build Status Scrutinizer Code Quality Code Coverage Latest Stable Version Latest Unstable Version License Total Downloads

Installation

athene2-versioning only officially supports installation through Composer. For Composer documentation, please refer to getcomposer.org.

Install the module:

$ php composer.phar require serlo-org/athene2-versioning:~2.0

Using the versioning module

The versioning module enables you to manage repositories which contain revisions. Each repository has n revisions and one or zero HEAD revision. The HEAD revision is the current revision of that repository. The default implementation of the VersioningManager is Doctrine friendly!

Features

  • Doctrine implementation (using the ObjectManager)
  • Bundled with zfc-rbac for authorization
  • Events

Understanding how it works

The versioning module consists of one Versioning\Manager\VersioningManager, who implements the Versioning\Manager\VersioningManagerInterface. He manages models or entities which implement the Versioning\Entity\RepositoryInterface and the Versioning\Entity\RevisionInterface.

Let's implement those entity interfaces!

You can find example implementations here!

RevisionInterface

<?php

use Athene2\Versioning\Entity\RepositoryInterface;
use Athene2\Versioning\Entity\RevisionInterface;
use ZfcRbac\Identity\IdentityInterface;

/**
 * Class Revision
 *
 * @author Aeneas Rekkas
 */
class Revision implements RevisionInterface
{
    /**
     * @var mixed
     */
    protected $id;

    /**
     * @var RepositoryInterface
     */
    protected $repository;

    /**
     * @var
     */
    protected $author;

    /**
     * @var bool
     */
    protected $trashed = false;

    /**
     * @var array
     */
    protected $data = [];

    /**
     * @param mixed $id
     * @return void
     */
    public function setId($id)
    {
        $this->id = $id;
    }

    /**
     * {@inheritDoc}
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * {@inheritDoc}
     */
    public function getRepository()
    {
        return $this->repository;
    }

    /**
     * {@inheritDoc}
     */
    public function setRepository(RepositoryInterface $repository)
    {
        $this->repository = $repository;
    }

    /**
     * {@inheritDoc}
     */
    public function setAuthor(IdentityInterface $author)
    {
        $this->author = $author;
    }

    /**
     * {@inheritDoc}
     */
    public function getAuthor()
    {
        return $this->author;
    }

    /**
     * {@inheritDoc}
     */
    public function setTrashed($trash)
    {
        $this->trashed = (bool)$trash;
    }

    /**
     * {@inheritDoc}
     */
    public function isTrashed()
    {
        return $this->trashed;
    }

    /**
     * {@inheritDoc}
     */
    public function set($key, $value)
    {
        $this->data[$key] = $value;
    }

    /**
     * {@inheritDoc}
     */
    public function get($key)
    {
        return isset($this->data[$key]) ? $this->data[$key] : null;
    }
}

RepositoryInterface

<?php

use Athene2\Versioning\Entity\RepositoryInterface;
use Athene2\Versioning\Entity\RevisionInterface;

class Repository implements RepositoryInterface
{
    /**
     * @var array|RevisionInterface[]
     */
    protected $revisions = [];

    /**
     * @var null|RevisionInterface
     */
    protected $head = null;

    /**
     * @var mixed
     */
    protected $id;

    /**
     * {@inheritDoc}
     */
    public function addRevision(RevisionInterface $revision)
    {
        $this->revisions[$revision->getId()] = $revision;
    }

    /**
     * {@inheritDoc}
     */
    public function createRevision()
    {
        return new Revision();
    }

    /**
     * {@inheritDoc}
     */
    public function getCurrentRevision()
    {
        return $this->head;
    }

    /**
     * {@inheritDoc}
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * {@inheritDoc}
     */
    public function getRevisions()
    {
        return $this->revisions;
    }

    /**
     * {@inheritDoc}
     */
    public function hasCurrentRevision()
    {
        return null !== $this->head;
    }

    /**
     * {@inheritDoc}
     */
    public function removeRevision(RevisionInterface $revision)
    {
        unset($this->revisions[$revision->getId()]);
    }

    /**
     * {@inheritDoc}
     */
    public function setCurrentRevision(RevisionInterface $revision)
    {
        $this->head = $revision;
    }
}

Well, that wasn't so hard, was it?

Using the RepositoryManager

The default RepositoryManager implementation is bundled with Doctrine, ZF2 EventManager and zfc-rbac.

Setting up permissions

Not everyone should be allowed to commit, reject and accept revisions, right? Therefore, the RepositoryManager is able to handle permissions via zfc-rbac!

To set up permissions, you will need to add some config data to your module.config.php:

return [
    // ...
    'versioning'       => [
        'permissions'  => [
            // Use the classname of the revision class
            // In the example above the namespace is missing, therefore the classname is only "Revision".
            // This could be also "MyModule\Entity\Revision"
            'Revision' => [
            
                // There are three actions which need authentication:
                
                // 'commit' gets checked when you call "commitRevision"
                ModuleOptions::KEY_PERMISSION_COMMIT  => 'revision.create',
                
                // 'checkout' gets checked when you call "checkoutRevision"
                ModuleOptions::KEY_PERMISSION_CHECKOUT => 'revision.checkout',
                
                // 'reject' gets checked when you call "rejectRevision"
                ModuleOptions::KEY_PERMISSION_REJECT   => 'revision.trash'
                
                // Name the permissions whatever you like. Just be aware that they are registered in zfc-rbac!
                // ModuleOptions::KEY_PERMISSION_COMMIT   => 'mymodule.entity.revision.commit',
            ]
        ]
    ]
    // ...
];

Important: The revision is always passed to zfc-rbac as a context object for usage with e.g. Assertions!

Create a new Revision and fill it with data!

// Let's create a repository first
$repository = new Repository();

// Now we need the RepositoryManager
$repositoryManager = $serviceManager->get('Athene2\Versioning\Manager\VersioningManager');

// Let's create our first revision!
$revision = $repositoryManager->commitRevision($repository, ['foo' => 'bar'], 'I added some stuff');

// And check it out (set as HEAD / current revision)
// We can also add a short message, why we checked out this revision!
$repositoryManager->checkoutRevision($repository, $revision, 'That\'s a nice reason, isn\'t it?');

// Now, let's make those changes persistent!
$repositoryManager->flush();

Trash a revision

Someone made a mistake? Just reject the revision!

// Now we need the RepositoryManager
$repositoryManager = $serviceManager->get('Athene2\Versioning\Manager\VersioningManager');

$revision = $repositoryManager->rejectRevision($repository, 5, 'Sorry but there are too many mistakes!');

// Now, let's make those changes persistent!
$repositoryManager->flush();

Check out a revision

Do you approve of a certain revision? Go ahead and check it out!

// Now we need the RepositoryManager
$repositoryManager = $serviceManager->get('Athene2\Versioning\Manager\VersioningManager');

$revision = $repositoryManager->checkoutRevision($repository, 5, 'Fine job!');

// Now, let's make those changes persistent!
$repositoryManager->flush();

Hooking into Events

The VersioningManager fires events for both failure and success:

$eventManager = $repositoryManager->getEventManager();

$eventManager->attach(VersioningEvent::COMMIT, function(VersioningEvent $event) {
    echo "I just committed a new revision with a cool message: " . $event->getMessage();
});

$eventManager->attach(VersioningEvent::COMMIT_UNAUTHORIZED, function(VersioningEvent $event) {
    echo "I just committed a new revision but didn't have the rights to do so!";
});

$repositoryManager->commitRevision($repository, $data, $message);

There are also other events available, like:

  • VersioningEvent::COMMIT and VersioningEvent::COMMIT_UNAUTHORIZED
  • VersioningEvent::REJECT and VersioningEvent::REJECT_UNAUTHORIZED
  • VersioningEvent::CHECKOUT and VersioningEvent::CHECKOUT_UNAUTHORIZED

To be done

There are a more things to come:

  • A beautiful UI to manage your repositories
  • A RESTful API
  • Better docs