# 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 **Note:** - 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: **2012-01-02** **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: ``` php 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: ``` php newDefaultAnnotationDriver( '/path/to/library/DoctrineExtensions/lib/Gedmo/Loggable/Entity' // Document for ODM ); $chainDriverImpl->addDriver($yourDefaultDriverImpl, 'Entity'); $chainDriverImpl->addDriver($loggableDriverImpl, 'Gedmo\Loggable'); $doctrineOrmConfig->setMetadataDriverImpl($chainDriverImpl); ``` **Note:** there can be many annotation drivers in driver chain **Note:** 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} ``` php 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} **Note:** 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 ``` php id; } public function setTitle($title) { $this->title = $title; } public function getTitle() { return $this->title; } } ``` ## Loggable Document example: {#document} ``` php 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 ``` ## Basic usage examples: {#basic-examples} ``` php 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: ``` php 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: ``` php 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