Installation
composer require cris/avanaudit-bundle
(Note: The README mentions data-dog/audit-bundle, but the repo URL points to cris/avanaudit-bundle. Use the latter.)
Enable the Bundle
Add to config/bundles.php:
return [
// ...
Cris\AvanauditBundle\CrisAvanauditBundle::class => ['all' => true],
];
Database Migration Run:
php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate
This auto-generates the audit_entry table.
First Audit Log
Trigger an ORM operation (e.g., User::create() or EntityManager::persist()). Check logs or the demo route (/audit) to verify entries.
Automatic Tracking
User entity:
$user = $entityManager->find(User::class, 1);
$user->setEmail('new@example.com');
$entityManager->flush(); // Audit log captured here.
Relation Tracking
ASSOCIATED/DISSOCIATED actions.Role to a User:
$user->addRole($role);
$entityManager->flush(); // Logs role association.
User Context
Security bundle is enabled, logs link to the authenticated user via TokenStorage.Transaction Safety
Custom Fields
Extend the AuditEntry entity to add metadata (e.g., ip_address):
// src/Entity/AuditEntry.php
namespace App\Entity;
use Cris\AvanauditBundle\Entity\AuditEntry as BaseAuditEntry;
class AuditEntry extends BaseAuditEntry {
private $ipAddress;
// ...
}
Filtering Logs Use Doctrine queries to filter by entity, action, or user:
$auditEntries = $entityManager->getRepository(AuditEntry::class)
->findBy(['entityClass' => User::class, 'action' => 'UPDATE']);
Event Listeners
Subscribe to audit.log events for custom logic:
// src/EventListener/AuditListener.php
use Cris\AvanauditBundle\Event\AuditEvent;
class AuditListener {
public function onAudit(AuditEvent $event) {
if ($event->getAction() === 'DELETE') {
// Trigger cleanup logic.
}
}
}
Register in services.yaml:
services:
App\EventListener\AuditListener:
tags:
- { name: kernel.event_listener, event: audit.log, method: onAudit }
Demo Route
Use /audit (from the demo) for debugging or expose via API:
// src/Controller/AuditController.php
use Cris\AvanauditBundle\Entity\AuditEntry;
class AuditController extends AbstractController {
public function index(EntityManagerInterface $em): Response {
$audits = $em->getRepository(AuditEntry::class)->findAll();
return $this->render('audit/index.html.twig', ['audits' => $audits]);
}
}
No DQL/SQL Tracking
$em->createQuery('UPDATE ...') or raw SQL) bypass the audit.$entityManager->persist()) or wrap SQL in a custom listener.Performance Overhead
flush().$entityManager->getConnection()->getConfiguration()->setSQLLogger(null);
// ... bulk operations ...
$entityManager->flush();
Missing User Context
null for user_id if no authenticated user exists.Security bundle is configured and TokenStorage is available.Relation Diffs
getRelationDiff() in a custom AuditEntry:
public function getRelationDiff($field, $oldValue, $newValue) {
if ($field === 'orders') {
return "Orders changed from {$oldValue->count()} to {$newValue->count()} items.";
}
return parent::getRelationDiff($field, $oldValue, $newValue);
}
Enable SQL Logging
Add to .env:
DOCTRINE_DDL_AUTO_CREATE_SQL=true
DOCTRINE_DDL_AUTO_CREATE_SQL_LOG_FILE=audit_ddl.log
Check Event Dispatching
Verify audit.log events fire by adding a debug listener:
public function onAudit(AuditEvent $event) {
error_log('Audit triggered: ' . $event->getAction());
}
Inspect AuditEntry Table Query raw logs to debug:
SELECT * FROM audit_entry WHERE entity_class = 'App\Entity\User' ORDER BY created_at DESC LIMIT 10;
Custom Actions
Add new actions (e.g., SOFT_DELETE) by extending AuditEntry:
// src/Entity/AuditEntry.php
const SOFT_DELETE = 'SOFT_DELETE';
// ...
Pre/Post Audit Logic Use Doctrine lifecycle callbacks:
// src/Entity/User.php
use Doctrine\ORM\Mapping as ORM;
use Cris\AvanauditBundle\Event\AuditEvent;
#[ORM\Entity]
class User {
#[ORM\PrePersist]
public function prePersist() {
if (!$this->getEmail()) {
throw new \RuntimeException("Email required for audit.");
}
}
}
Exclude Entities
Skip auditing for specific entities by implementing AuditExclusionInterface:
use Cris\AvanauditBundle\Contract\AuditExclusionInterface;
class TempData implements AuditExclusionInterface {}
Custom Storage
Override the AuditEntryRepository to store logs in a queue or external service:
// src/Repository/AuditEntryRepository.php
use Cris\AvanauditBundle\Repository\AuditEntryRepository as BaseRepository;
class AuditEntryRepository extends BaseRepository {
public function save(AuditEntry $entry) {
// Send to RabbitMQ/Redis instead of DB.
}
}
How can I help you explore Laravel packages today?