sonata-project/entity-audit-bundle
Doctrine 2 entity versioning for Symfony, inspired by Hibernate Envers. Tracks changes to audited entities and associations, storing revision history you can browse and compare for full audit trails.
composer require sonata-project/entity-audit-bundle
config/bundles.php:
SimpleThings\EntityAudit\SimpleThingsEntityAuditBundle::class => ['all' => true],
config/packages/entity_audit.yaml:
simple_things_entity_audit:
audited_entities:
- App\Entity\YourEntity
php bin/console doctrine:schema:update --dump-sql
Annotate your entity (no extra annotations needed; just include it in audited_entities).
Example:
// src/Controller/YourController.php
use SimpleThings\EntityAudit\AuditReader;
class YourController extends AbstractController {
public function showAudit(AuditReader $auditReader, int $id) {
$revisions = $auditReader->findRevisions(YourEntity::class, $id);
return $this->render('audit/show.html.twig', ['revisions' => $revisions]);
}
}
Automatic Logging
The bundle hooks into Doctrine events, so no manual code is needed for create, update, or delete operations.
Example:
// Automatically logs changes to $user when saved
$user = new User();
$user->setName('John Doe');
$entityManager->persist($user);
$entityManager->flush(); // Audit log created
Querying Audit Data
$revisions = $auditReader->findRevisions(User::class, 1);
$rev1 = $auditReader->find(User::class, 1, 5);
$rev2 = $auditReader->find(User::class, 1, 10);
$changes = $auditReader->findEntitiesChangedAtRevision(10);
Integrating with Forms Use audit data to populate forms with historical values:
{# Display entity state at revision 5 #}
<input type="text" value="{{ auditEntity.name }}">
Custom Username Logic Override the default username resolution (e.g., for API users):
simple_things_entity_audit:
service:
username_callable: App\Service\CustomUsernameResolver
// App/Service/CustomUsernameResolver.php
public function __invoke() {
return 'api_user_' . request()->ip();
}
Excluding Fields Ignore non-critical fields (e.g., timestamps):
simple_things_entity_audit:
global_ignore_columns:
- createdAt
- updatedAt
Symfony Commands
Use AuditReader in console commands to log audit data:
use SimpleThings\EntityAudit\AuditReader;
class AuditCommand extends Command {
public function __construct(private AuditReader $auditReader) {}
protected function execute(InputInterface $input, OutputInterface $output) {
$revisions = $this->auditReader->findRevisions(User::class, 1);
$output->writeln('Revisions: ' . count($revisions));
}
}
Event Subscribers Trigger actions on audit events (e.g., notify admins of critical changes):
use SimpleThings\EntityAudit\Event\RevisionEvent;
class AuditSubscriber implements EventSubscriber {
public function onRevision(RevisionEvent $event) {
if ($event->getRevisionType() === 'DEL') {
// Send alert
}
}
}
API Endpoints Expose audit data via API:
#[Route('/audit/{id}', name: 'api_audit_history')]
public function getAuditHistory(AuditReader $auditReader, int $id): JsonResponse {
return new JsonResponse($auditReader->findRevisions(User::class, $id));
}
Schema Mismatches
*_audit) may not sync with entity changes if the schema is manually altered.doctrine:schema:update after entity changes. Use --dump-sql to preview changes:
php bin/console doctrine:schema:update --dump-sql
Performance Overhead
global_ignore_columns.disable_foreign_keys if foreign keys cause performance issues:
simple_things_entity_audit:
disable_foreign_keys: true
ManyToMany Associations
ManyToMany may throw NoRevisionFoundException.Username Resolution
null.username_callable to handle edge cases:
$auditConfig->setUsernameCallable(function () {
return auth()->check() ? auth()->user()->email : 'system';
});
Joined Inheritance
JoinedTableInheritance.SingleTableInheritance or avoid auditing inherited entities.Debugging Missing Revisions
audited_entities.global_ignore_columns isn’t excluding critical fields.flush()).Selective Auditing Dynamically enable/disable auditing for specific entities:
$auditConfig->setAuditedEntityClasses([User::class]); // Override config
Custom Audit Tables
Override table names in AuditConfiguration:
$auditConfig->setAuditTableName('custom_audit_table');
Bulk Operations Disable auditing for bulk operations to improve performance:
$entityManager->getConnection()->getConfiguration()->setSQLLogger(null);
// Perform bulk operations
$entityManager->getConnection()->getConfiguration()->setSQLLogger($logger);
Testing
Use AuditReader in tests to verify audit logs:
$this->assertCount(1, $auditReader->findRevisions(User::class, 1));
Symfony 8+ Routing
Use the new audit.php routing file (deprecated audit.xml):
# config/routes.yaml
simple_things_entity_audit:
resource: "@SimpleThingsEntityAuditBundle/Resources/config/routing/audit.php"
prefix: /audit
Extension Points
Revision class (though deprecated in v1.19.0+, consider forking).onRevision or onFlush events for custom logic.Large Datasets Paginate audit queries to avoid memory issues:
$revisions = $auditReader->findRevisions(User::class, 1, 10, 20); // Limit 20
How can I help you explore Laravel packages today?