Important
You are browsing documentation for version 5.1 of OroCommerce, supported until March 2027. Read the documentation for the latest LTS version to get up-to-date information.
See our Release Process documentation for more information on the currently supported and upcoming releases.
Data Audit
The OroDataAuditBundle leverages the Loggable Doctrine extension (StofDoctrineExtension) to provide changelogs for your entities.
Entity Configuration
DataAudit can only be enabled for Configurable entities. To add an entity property to the changelog, enable the audit on the entity itself and specify the fields to log. Use the Oro\Bundle\EntityConfigBundle\Metadata\Annotation\Config and Oro\Bundle\EntityConfigBundle\Metadata\Annotation\ConfigField annotations on the entity.
Caution
This annotation is read-only on installation. On platform updates, it is read, but saved to the configuration only for new entities, entities that were not Configurable before, or entities not changed via the configuration UI.
Note
You can enable or disable an audit for an entire entity or for individual fields in the UI under System / Entities / EntityManagement (attribute Auditable).
Example of annotation configuration:
<?php
namespace Acme\Bundle\DemoBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Oro\Bundle\DataAuditBundle\Entity\AuditAdditionalFieldsInterface;
use Oro\Bundle\EntityBundle\EntityProperty\DatesAwareInterface;
use Oro\Bundle\EntityBundle\EntityProperty\DatesAwareTrait;
use Oro\Bundle\EntityConfigBundle\Metadata\Annotation\Config;
use Oro\Bundle\EntityConfigBundle\Metadata\Annotation\ConfigField;
use Oro\Bundle\OrganizationBundle\Entity\OrganizationAwareInterface;
use Oro\Bundle\UserBundle\Entity\Ownership\AuditableUserAwareTrait;
use Oro\Bundle\EntityExtendBundle\Entity\ExtendEntityInterface;
use Oro\Bundle\EntityExtendBundle\Entity\ExtendEntityTrait;
/**
* ORM Entity Question.
*
* @ORM\Entity(
* repositoryClass="Acme\Bundle\DemoBundle\Entity\Repository\QuestionRepository"
* )
* @ORM\Table(
* name="acme_demo_question"
* )
* @Config(
* routeName="acme_demo_question_index",
* routeView="acme_demo_question_view",
* routeCreate="acme_demo_question_create",
* routeUpdate="acme_demo_question_update",
* defaultValues={
* "form"={
* "form_type"="Acme\Bundle\DemoBundle\Form\Type\QuestionCreateOrSelectType",
* "grid_name"="acme-demo-question-grid-select"
* },
* "grid"={
* "default"="acme-demo-question-grid-select"
* },
* "entity"={
* "icon"="fa-question"
* },
* "ownership"={
* "owner_type"="USER",
* "owner_field_name"="owner",
* "owner_column_name"="user_owner_id",
* "organization_field_name"="organization",
* "organization_column_name"="organization_id"
* },
* "security"={
* "type"="ACL",
* "group_name"="",
* "category"=""
* },
* "dataaudit"={
* "auditable"=true
* }
* }
* )
*/
class Question implements
DatesAwareInterface,
OrganizationAwareInterface,
AuditAdditionalFieldsInterface,
ExtendEntityInterface
{
use DatesAwareTrait;
use AuditableUserAwareTrait;
use ExtendEntityTrait;
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(
* name="subject",
* type="string",
* length=255,
* nullable=false
* )
* @ConfigField(
* defaultValues={
* "dataaudit"={
* "auditable"=true
* },
* "importexport"={
* "identity"=true
* }
* }
* )
*/
private $subject;
}
Every time a product’s price changes, the change is logged in the database. The logging manager stores the modified data along with a set of related information:
The action corresponding to the operation performed by the Doctrine ORM (one of create, update and delete);
The modified entity’s class name
The current date and time
The user performing the change
A string representation of the modified entity. If the entity class implements a
__toString()method, the return value of this method is used. Otherwise, the class name is used.
Each entity object gets its own history, so changesets are numbered starting from 1. Each new changeset increments the entity’s highest existing version number by one.
Additional Fields
You can store additional fields in every audit log entry, with no restrictions on the data type. If the object is passed to an array, it is sanitized and converted to the supported format. The following example shows when additional fields are useful:
Suppose you create an extension that integrates Oro application with an external System A, synchronizing Question entities between the two. The Question identifier differs between them: id in Oro application and subject in System A.
System A tracks changes in Oro application by calling the API audit endpoint and matches Questions on its side by subject, so attaching that field to every response is helpful (for example, when a Question is removed). To do this, use “additional fields”. The entity must implement AuditAdditionalFieldsInterface.
In our example, it can look like this:
<?php
namespace Acme\Bundle\DemoBundle\Entity;
class Question implements
DatesAwareInterface,
OrganizationAwareInterface,
AuditAdditionalFieldsInterface,
ExtendEntityInterface
{
/**
* @inheritDoc
*/
public function getAdditionalFields(): array
{
return ['subject' => $this->getSubject()];
}
}
Segment
DataAuditBundle extends OroSegmentBundle by a new filter type “Data audit”.
Use this filter to select records that:
had a field changed to a value (e.g., Contact who changed job position to “Director”)
had a field changed to a value in a period of time (e.g., Contact who changed job position to “Director” within last week)
To filter by a specific field, these conditions must be met:
the entity has to be auditable
the field has to be auditable
Add New Auditable Types
To add new auditable types, register a new type in your bundle’s boot method:
<?php
// AcmeDemoBundle.php
namespace Acme\Bundle\DemoBundle;
use Oro\Bundle\DataAuditBundle\Model\AuditFieldTypeRegistry;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Acme\Bundle\DemoBundle\DependencyInjection\Compiler\ImagePlaceholderProviderPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Acme\Bundle\DemoBundle\DependencyInjection\Compiler\AcmeExtendValidationPass;
class AcmeDemoBundle extends Bundle
{
/**
* {@inheritdoc}
*/
public function build(ContainerBuilder $container): void
{
parent::build($container);
$container->addCompilerPass(new ImagePlaceholderProviderPass());
$container->addCompilerPass(new AcmeExtendValidationPass());
/**
* You can also use AuditFieldTypeRegistry::overrideType to replace existing type
* But make sure you move old data into new columns
*/
AuditFieldTypeRegistry::addType($doctrineType = 'datetimenew', $auditType = 'datetimenew');
}
}
Next, create a migration that will add columns to the AuditField entity:
<?php
namespace Acme\Bundle\DemoBundle\Migrations\Schema\v1_7;
use Oro\Bundle\DataAuditBundle\Migration\Extension\AuditFieldExtension;
use Oro\Bundle\DataAuditBundle\Migration\Extension\AuditFieldExtensionAwareInterface;
use Oro\Bundle\MigrationBundle\Migration\Migration;
use Doctrine\DBAL\Schema\Schema;
use Oro\Bundle\MigrationBundle\Migration\QueryBag;
/**
* New audit field type creation.
*/
class AddNewAuditFieldType implements Migration, AuditFieldExtensionAwareInterface
{
private AuditFieldExtension $auditFieldExtension;
/**
* @inheritDoc
*/
public function setAuditFieldExtension(AuditFieldExtension $extension)
{
$this->auditFieldExtension = $extension;
}
public function up(Schema $schema, QueryBag $queries)
{
$this->auditFieldExtension->addType($schema, $doctrineType = 'datetimetz', $auditType = 'datetimenew');
}
}
To see the auditable option in the entity configuration, make sure your field type is in the allowed types here: DataAuditBundle/Resources/config/oro/entity_config.yml.
To make sure your column is displayed correctly in the grids (segments, reports), create a new column options guesser with tag oro_datagrid.column_options_guesser and set frontend_type property.
Browsing the Change History
The DataAuditBundle ships with a controller that gives you access to a particular entity’s history through your web browser. By default, the controller’s route path is /audit/history/{entity}/{id}/{_format}. For example, to view the history of the product with id 5, use /audit/history/product/5. If you do not specify a format, the bundle defaults to HTML. To override the path, provide your own definition for a route with id oro_dataaudit_history.
API
Besides browsing the audit history in your web browser, you can also access the stored data through an API, which provides methods to retrieve your results via REST API.
Both variants provide methods to retrieve:
A list of all audit log entries
A single audit log entry
To retrieve a single entry, you need its id, which must be extracted from the list of log entries.
Note
The audit log entry id is not related to any of the entities being watched.
REST API
The two REST API endpoints are controlled by the oro_api_get_audit and
oro_api_get_audits routes:
Route |
Path |
Use case |
|---|---|---|
|
/api/rest/{version}/audits.{_format} |
Retrieve all audit log entries |
|
/api/rest/{version}/audits/{id}.{_format} |
Retrieve an audit log entry |
JSON is the only supported format, and the API controller uses it when you omit the format. Use the latest value to access the most recent version of the API; this currently equals v1, the only available version.