# 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 [blog_reference]: http://gediminasm.org/article/loggable-behavioral-extension-for-doctrine2 "Loggable extension for Doctrine2 tracks record changes and version management" [blog_test]: http://gediminasm.org/test "Test extensions on this blog" Update **2011-04-04** - Made single listener, one instance can be used for any object manager and any number of them **Notice:** - You can [test live][blog_test] on this blog - Public [Loggable repository](http://github.com/l3pp4rd/DoctrineExtensions "Loggable extension on Github") is available on github - Last update date: **2011-08-08** **Portability:** - **Loggable** is now available as [Bundle](http://github.com/stof/StofDoctrineExtensionsBundle) ported to **Symfony2** by **Christophe Coevoet**, together with all other extensions This article will cover the basic installation and functionality of **Loggable** behavior Content: - [Including](#including-extension) the extension - [Attaching](#event-listener) the **Loggable Listener** - Entity [example](#entity) - Document [example](#document) - [Yaml](#yaml) mapping example - [Xml](#xml) mapping example - Basic usage [examples](#basic-examples) ## 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} ## 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