loggable.md 8.5 KB

Loggable behavioral extension for Doctrine2

Loggable behavior tracks your record changes and is able to manage versions.

Features:

  • Automatic storage of log entries in database
  • ORM and ODM support using same listener
  • Can be nested with other behaviors
  • Objects can be reverted to previous versions
  • Annotation, Yaml and Xml mapping support for extensions

Update 2011-04-04

  • Made single listener, one instance can be used for any object manager and any number of them

Notice:

Portability:

  • Loggable is now available as Bundle ported to Symfony2 by Christophe Coevoet, together with all other extensions

This article will cover the basic installation and functionality of Loggable behavior

Content:

Setup and autoloading {#including-extension}

If you using the source from github repository, initial directory structure for the extension library should look like this:

...
/DoctrineExtensions
    /lib
        /Gedmo
            /Exception
            /Loggable
            /Mapping
            /Sluggable
            /Timestampable
            /Translatable
            /Tree
    /tests
        ...
...

First of all we need to setup the autoloading of extensions:

$classLoader = new \Doctrine\Common\ClassLoader('Gedmo', "/path/to/library/DoctrineExtensions/lib");
$classLoader->register();

This behavior requires an additional metadata path to be specified in order to have a logEntry table for log entries. To configure it correctly you need to add new annotation driver into driver chain with a specific location and namespace

Loggable metadata Annotation driver mapped into driver chain:

$chainDriverImpl = new \Doctrine\ORM\Mapping\Driver\DriverChain();
$yourDefaultDriverImpl = new \Doctrine\ORM\Mapping\Driver\YamlDriver('/yml/mapping/files');
$loggableDriverImpl = $doctrineOrmConfig->newDefaultAnnotationDriver(
    '/path/to/library/DoctrineExtensions/lib/Gedmo/Loggable/Entity' // Document for ODM
);
$chainDriverImpl->addDriver($yourDefaultDriverImpl, 'Entity');
$chainDriverImpl->addDriver($loggableDriverImpl, 'Gedmo\Loggable');
$doctrineOrmConfig->setMetadataDriverImpl($chainDriverImpl);

Notice: there can be many annotation drivers in driver chain

Notice: Loggable Entity or Document is required for storing all logs.

If you need a log entry table per single Entity or Document, we will cover how to setup it later

Attaching the Loggable Listener to the event manager {#event-listener}

$evm = new \Doctrine\Common\EventManager();
// ORM and ODM
$loggableListener = new \Gedmo\Loggable\LoggableListener();

$loggableListener->setUsername('currently_loggedin_user');
// in real world app the username should be loaded from session, example:
// Session::getInstance()->read('user')->getUsername();
$evm->addEventSubscriber($loggableListener);
// now this event manager should be passed to entity manager constructor

Loggable annotations:

  • @Gedmo\Mapping\Annotation\Loggable(logEntryClass="my\class") this class annotation will use store logs to optionaly specified logEntryClass
  • @Gedmo\Mapping\Annotation\Versioned tracks annotated property for changes

Loggable Entity example: {#entity}

Notice: that Loggable interface is not necessary, except in cases there you need to identify entity as being Loggable. The metadata is loaded only once when cache is active

namespace Entity;

use Gedmo\Mapping\Annotation as Gedmo;
use Doctrine\ORM\Mapping as ORM;

/**
 * @Entity
 * @Gedmo\Loggable
 */
class Article
{
    /**
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;

    /**
     * @Gedmo\Versioned
     * @ORM\Column(name="title", type="string", length=8)
     */
    private $title;

    public function getId()
    {
        return $this->id;
    }

    public function setTitle($title)
    {
        $this->title = $title;
    }

    public function getTitle()
    {
        return $this->title;
    }
}

Loggable Document example: {#document}

namespace Document;

use Gedmo\Mapping\Annotation as Gedmo;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;

/**
 * @ODM\Document(collection="articles")
 * @Gedmo\Loggable
 */
class Article
{
    /** @ODM\Id */
    private $id;

    /**
     * @ODM\String
     * @Gedmo\Versioned
     */
    private $title;

    public function __toString()
    {
        return $this->title;
    }

    public function getId()
    {
        return $this->id;
    }

    public function setTitle($title)
    {
        $this->title = $title;
    }

    public function getTitle()
    {
        return $this->title;
    }
}

Yaml mapping example {#yaml}

Yaml mapped Article: /mapping/yaml/Entity.Article.dcm.yml

---
Entity\Article:
  type: entity
  table: articles
  gedmo:
    loggable:
# using specific personal LogEntryClass class:
      logEntryClass: My\LogEntry
  id:
    id:
      type: integer
      generator:
        strategy: AUTO
  fields:
    title:
      type: string
      length: 64
      gedmo:
        - versioned
    content:
      type: text

Xml mapping example {#xml}

<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
                  xmlns:gedmo="http://gediminasm.org/schemas/orm/doctrine-extensions-mapping">

    <entity name="Mapping\Fixture\Xml\Loggable" table="loggables">

        <id name="id" type="integer" column="id">
            <generator strategy="AUTO"/>
        </id>

        <field name="title" type="string" length="128">
            <gedmo:versioned/>
        </field>
        <many-to-one field="status" target-entity="Status">
            <join-column name="status_id" referenced-column-name="id"/>
            <gedmo:versioned/>
        </many-to-one>

        <gedmo:loggable log-entry-class="Gedmo\Loggable\Entity\LogEntry"/>

    </entity>

</doctrine-mapping>

Basic usage examples: {#basic-examples}

$article = new Entity\Article;
$article->setTitle('my title');
$em->persist($article);
$em->flush();

This inserted an article and inserted the logEntry for it, which contains all new changeset. In case if there is OneToOne or ManyToOne relation, it will store only identifier of that object to avoid storing proxies

Now lets update our article:

// first load the article
$article = $em->find('Entity\Article', 1 /*article id*/);
$article->setTitle('my new title');
$em->persist($article);
$em->flush();

This updated an article and inserted the logEntry for update action with new changeset Now lets revert it to previous version:

// first check our log entries
$repo = $em->getRepository('Gedmo\Loggable\Entity\LogEntry'); // we use default log entry class
$article = $em->find('Entity\Article', 1 /*article id*/);
$logs = $repo->getLogEntries($article);
/* $logs contains 2 logEntries */
// lets revert to first version
$repo->revert($article, 1/*version*/);
// notice article is not persisted yet, you need to persist and flush it
echo $article->getTitle(); // prints "my title"
$em->persist($article);
$em->flush();
// if article had changed relation, it would be reverted also.

Easy like that, any suggestions on improvements are very welcome