diff --git a/doc/loggable.md b/doc/loggable.md index 806d4cf6b..c14519d9e 100644 --- a/doc/loggable.md +++ b/doc/loggable.md @@ -1,305 +1,240 @@ -# Loggable behavioral extension for Doctrine +# Loggable Behavior Extension for Doctrine -**Loggable** behavior tracks your record changes and is able to -manage versions. +The **Loggable** behavior adds support for logging changes to and restoring prior versions of your Doctrine objects. -Features: +> [!NOTE] +> The Loggable extension is NOT compatible with `doctrine/dbal` 4.0 or later -- 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 -- Attributes, Annotation and Xml mapping support for extensions +## Index -This article will cover the basic installation and functionality of **Loggable** -behavior +- [Getting Started](#getting-started) +- [Configuring Loggable Objects](#configuring-loggable-objects) +- [Customizing The Log Entry Model](#customizing-the-log-entry-model) +- [Object Repositories](#object-repositories) + - [Fetching a Model's Log Entries](#fetching-a-models-log-entries) + - [Revert a Model to a Previous Version](#revert-a-model-to-a-previous-version) -Content: +## Getting Started -- [Including](#including-extension) the extension -- Entity [example](#entity-mapping) -- Document [example](#document-mapping) -- [Xml](#xml-mapping) mapping example -- Basic usage [examples](#basic-examples) +The loggable behavior can be added to a supported Doctrine object manager by registering its event subscriber +when creating the manager. - +```php +use Gedmo\Loggable\LoggableListener; -## Setup and autoloading +$listener = new LoggableListener(); -Read the [documentation](./annotations.md#em-setup) -or check the [example code](../example) -on how to setup and use the extensions in most optimized way. +// The $om is either an instance of the ORM's entity manager or the MongoDB ODM's document manager +$om->getEventManager()->addEventSubscriber($listener); +``` -### Loggable annotations: +Then, once your application has it available (i.e. after validating the authentication for your user during an HTTP request), +you can set a reference to the user who performed actions on a loggable model by calling the listener's `setUsername` method. -- **@Gedmo\Mapping\Annotation\Loggable(logEntryClass="My\LoggableModel")** this class annotation will store logs to optionally - specified **logEntryClass**. The class provided in this annotation MUST implement ``Gedmo\Loggable\LogEntryInterface``. You will - still need to specify versioned fields with the following annotation. -- **@Gedmo\Mapping\Annotation\Versioned** tracks annotated property for changes +```php +// The $user can be either an object or a string +$listener->setUsername($user); +``` -### Loggable attributes: +## Configuring Loggable Objects -- **\#[Gedmo\Mapping\Annotation\Loggable(logEntryClass: My\LoggableModel::class]** this class attribute will store logs to optionally - specified **logEntryClass**. The class provided in this attribute MUST implement ``Gedmo\Loggable\LogEntryInterface``. You will - still need to specify versioned fields with the following attribute. -- **\#[Gedmo\Mapping\Annotation\Versioned]** tracks attributed property for changes +The loggable extension can be configured with [annotations](./annotations.md#loggable-extension), +[attributes](./attributes.md#loggable-extension), or XML configuration (matching the mapping of +your domain models). The full configuration for annotations and attributes can be reviewed in +the linked documentation. -### Loggable username: +The below examples show the simplest and default configuration for the extension, logging changes for defined fields. -In order to set the username, when adding the loggable listener you need to set it this way: +### Attribute Configuration ```php setAnnotationReader($cachedAnnotationReader); -$loggableListener->setUsername('admin'); -$evm->addEventSubscriber($loggableListener); +#[ORM\Entity] +#[Gedmo\Loggable] +class Article +{ + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column(type: Types::INTEGER)] + public ?int $id = null; + + #[ORM\Column(type: Types::BOOLEAN)] + public bool $published = false; + + #[ORM\Column(type: Types::STRING)] + #[Gedmo\Versioned] + public ?string $title = null; +} ``` - +### XML Configuration + +```xml + + + + + + + + + + + + + -## Loggable Entity example: + + + +``` -**Note:** that Loggable interface is not necessary, except in cases where -you need to identify an entity as being Loggable. The metadata is loaded only once when -cache is active +### Annotation Configuration -**Note:** this example is using annotations and attributes for mapping, you should use -one of them, not both. +> [!NOTE] +> Support for annotations is deprecated and will be removed in 4.0. ```php id; - } - - public function setTitle($title) - { - $this->title = $title; - } - - public function getTitle() - { - return $this->title; - } + public ?string $title = null; } ``` - +## Customizing The Log Entry Model + +When configuring loggable models, you are able to specify a custom model to be used for the log entries for objects +of that type using the `logEntryClass` parameter: -## Loggable Document example: +### Attribute Configuration ```php title; - } - - public function getId() - { - return $this->id; - } - - public function setTitle($title) - { - $this->title = $title; - } - - public function getTitle() - { - return $this->title; - } + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column(type: Types::INTEGER)] + public ?int $id = null; } ``` - - -## Xml mapping example +### XML Configuration ```xml - - + - - - - - - - - - - + ``` - +A custom model must implement `Gedmo\Loggable\LogEntryInterface`. For convenience, we recommend extending from +`Gedmo\Loggable\Entity\MappedSuperClass\AbstractLogEntry` for Doctrine ORM users or +`Gedmo\Loggable\Document\MappedSuperClass\AbstractLogEntry` for Doctrine MongoDB ODM users, which provides a default +mapping configuration for each object manager. -## Custom LogEntry class +## Object Repositories -```php - 'DYNAMIC'])] -#[ORM\Index(name: 'log_class_lookup_idx', columns: ['object_class'])] -#[ORM\Index(name: 'log_date_lookup_idx', columns: ['logged_at'])] -#[ORM\Index(name: 'log_user_lookup_idx', columns: ['username'])] -#[ORM\Index(name: 'log_version_lookup_idx', columns: ['object_id', 'object_class', 'version'])] -class ParameterHistory extends AbstractLogEntry -{ - /* - * All required columns are mapped through inherited superclass - */ -} -``` +The repository classes provide a `getLogEntries` method which allows fetching the list of log entries for a given model. - +```php +use App\Entity\Article; +use Doctrine\ORM\EntityManagerInterface; +use Gedmo\Loggable\Entity\LogEntry; +use Gedmo\Loggable\Entity\Repository\LogEntryRepository; +use Gedmo\Loggable\LoggableListener; -## Basic usage examples: +/** @var EntityManagerInterface $em */ -```php -find(Article::class, 1); -use Entity\Article; +// Next, get the LogEntry repository +/** @var LogEntryRepository $repo */ +$repo = $em->getRepository(LogEntry::class); -$article = new Article(); -$article->setTitle('my title'); -$em->persist($article); -$em->flush(); +// Lastly, get the article's log entries +$logs = $repo->getLogEntries($article); ``` -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 +### Revert a Model to a Previous Version -Now lets update our article: +The repository classes provide a `revert` method which allows reverting a model to a previous version. The repository +will incrementally revert back to the version specified (for example, a model is currently on version 5, and you want to +revert to version 2, it will restore the state of version 4, then version 3, and finally, version 2). ```php -find(Article::class, 1 /*article id*/); -$article->setTitle('my new title'); -$em->persist($article); -$em->flush(); -``` +// Load our loggable model +$article = $em->find(Article::class, 1); -This updated an article and inserted the logEntry for update action with new changeset -Now lets revert it to previous version: +// Next, get the LogEntry repository +/** @var LogEntryRepository $repo */ +$repo = $em->getRepository(LogEntry::class); -```php -getRepository(LogEntry::class); // we use default log entry class -$article = $em->find(Article::class, 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. +// We are now able to revert to an older version +$repo->revert($article, 2); ``` - -Easy like that, any suggestions on improvements are very welcome