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
- Attributes, Annotation and Xml mapping support for extensions
This article will cover the basic installation and functionality of Loggable behavior
Content:
Read the documentation or check the example code on how to setup and use the extensions in most optimized way.
- @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
- #[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
In order to set the username, when adding the loggable listener you need to set it this way:
<?php
use Gedmo\Loggable\LoggableListener;
$loggableListener = new LoggableListener();
$loggableListener->setAnnotationReader($cachedAnnotationReader);
$loggableListener->setUsername('admin');
$evm->addEventSubscriber($loggableListener);
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
Note: this example is using annotations and attributes for mapping, you should use one of them, not both.
<?php
namespace Entity;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Loggable\Loggable;
use Gedmo\Mapping\Annotation as Gedmo;
/**
* @ORM\Entity
* @Gedmo\Loggable
*/
#[ORM\Entity]
#[Gedmo\Loggable]
class Article implements Loggable
{
/**
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
#[ORM\Id]
#[ORM\Column(name: 'id', type: Types::INTEGER)]
#[ORM\GeneratedValue(strategy: 'IDENTITY')]
private $id;
/**
* @Gedmo\Versioned
* @ORM\Column(name="title", type="string", length=8)
*/
#[Gedmo\Versioned]
#[ORM\Column(name: 'title', type: Types::STRING, length: 8)]
private $title;
public function getId()
{
return $this->id;
}
public function setTitle($title)
{
$this->title = $title;
}
public function getTitle()
{
return $this->title;
}
}
<?php
namespace Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Doctrine\ODM\MongoDB\Types\Type;
use Gedmo\Loggable\Loggable;
use Gedmo\Mapping\Annotation as Gedmo;
/**
* @ODM\Document(collection="articles")
* @Gedmo\Loggable
*/
#[Gedmo\Loggable]
#[ODM\Document(collection: 'articles')]
class Article implements Loggable
{
/** @ODM\Id */
#[ODM\Id]
private $id;
/**
* @ODM\Field(type="string")
* @Gedmo\Versioned
*/
#[Gedmo\Versioned]
#[ODM\Field(type: Type::STRING)]
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;
}
}
<?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>
<?php
namespace Entity;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Loggable\Entity\MappedSuperclass\AbstractLogEntry;
/**
* @ORM\Table(
* options={"row_format":"DYNAMIC"},
* indexes={
* @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"})
* }
* )
* @ORM\Entity()
*/
#[ORM\Entity]
#[ORM\Table(options: ['row_format' => '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
*/
}
<?php
use Entity\Article;
$article = new 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:
<?php
use Entity\Article;
// first load the article
$article = $em->find(Article::class, 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
use Entity\Article;
use Gedmo\Loggable\Entity\LogEntry;
// first check our log entries
$repo = $em->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.
Easy like that, any suggestions on improvements are very welcome