Installation:
composer require alli-govender/auditor-bundle
Ensure your composer.json aligns with the package's requirements (PHP ≥7.2, Symfony ≥3.4).
Enable the Bundle:
Add to config/bundles.php:
return [
// ...
AlliGovender\AuditorBundle\AlliGovenderAuditorBundle::class => ['all' => true],
];
Configure Basic Auditing:
Edit config/packages/auditor.yaml (auto-generated):
alli_govender_auditor:
connection: default # Your Doctrine connection name
logger: true # Enable logging to Doctrine audit logs
log_entry_class: AlliGovender\AuditorBundle\Entity\AuditEntry # Default entity
log_entry_manager: true # Auto-manage audit entries
Mark an Entity as Auditable:
Add @Auditable annotation to your entity:
use AlliGovender\AuditorBundle\Annotation\Auditable;
/**
* @Auditable
*/
class User
{
// ...
}
Run Migrations:
php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate
This creates the audit_entry table and sets up auditing for all @Auditable entities.
$user->setEmail('new@example.com');
$entityManager->persist($user);
$entityManager->flush();
SELECT * FROM audit_entry WHERE object_class = 'App\Entity\User' ORDER BY id DESC;
You’ll see a record of the email field change, including:
new@example.com → old@example.com).UPDATE).Create: Automatically logs the initial state of all fields for new entities.
$user = new User();
$user->setName('John Doe');
$entityManager->persist($user);
$entityManager->flush();
Audit log: Records the creation with all field values.
Update: Logs only changed fields (delta tracking).
$user->setName('Jane Doe');
$entityManager->flush();
Audit log: Shows name changed from John Doe to Jane Doe.
Delete: Logs the deletion with the last known state.
$entityManager->remove($user);
$entityManager->flush();
Audit log: Marks action as DELETE with pre-deletion data.
Custom Audit Entries:
Extend the default AuditEntry entity to add metadata (e.g., ipAddress, userAgent):
namespace App\Entity;
use AlliGovender\AuditorBundle\Entity\AuditEntry as BaseAuditEntry;
class CustomAuditEntry extends BaseAuditEntry
{
private $ipAddress;
// Add getters/setters and DB column (via ORM annotations).
}
Update auditor.yaml:
alli_govender_auditor:
log_entry_class: App\Entity\CustomAuditEntry
Conditional Auditing:
Use the on option to audit only specific actions (e.g., CREATE and UPDATE):
/**
* @Auditable(on={"CREATE", "UPDATE"})
*/
class Product {}
Excluding Fields: Skip auditing sensitive fields (e.g., passwords):
/**
* @Auditable(exclude={"password", "apiToken"})
*/
class User {}
Event Listeners: Attach custom logic to audit events (e.g., log to an external service):
namespace App\EventListener;
use AlliGovender\AuditorBundle\Event\AuditEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class CustomAuditSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
AuditEvent::PRE_AUDIT => 'onPreAudit',
];
}
public function onPreAudit(AuditEvent $event)
{
// Add custom logic (e.g., log to Slack).
}
}
Register the subscriber in services.yaml:
services:
App\EventListener\CustomAuditSubscriber:
tags: ['kernel.event_subscriber']
Querying Audits: Use Doctrine queries to fetch audit trails:
$auditEntries = $entityManager->getRepository(AuditEntry::class)
->findBy(['objectClass' => User::class], ['id' => 'DESC']);
Batch Processing: For bulk operations (e.g., imports), disable auditing temporarily:
$entityManager->getConnection()->getConfiguration()->setSQLLogger(null);
// Perform bulk operations...
$entityManager->getConnection()->getConfiguration()->setSQLLogger($logger);
Indexing:
Add indexes to the audit_entry table for frequently queried fields:
CREATE INDEX idx_audit_object_class ON audit_entry(object_class);
CREATE INDEX idx_audit_timestamp ON audit_entry(logged_at);
Asynchronous Logging: Offload audit logging to a queue (e.g., Symfony Messenger) for high-traffic apps:
alli_govender_auditor:
logger: false # Disable direct DB logging
Implement a custom AuditLogger service to handle async writes.
Schema Updates:
doctrine:schema:update --force without migrations may break audit tables.php bin/console make:migration
php bin/console doctrine:migrations:migrate
Circular References:
Auditing entities with bidirectional relationships (e.g., User ↔ Order) may log redundant data.
exclude or on to limit audited actions:
/**
* @Auditable(on={"CREATE"}, exclude={"orders"})
*/
class User {}
Transaction Rollbacks: Audit logs are committed after the main transaction.
Large Entities: Auditing entities with many fields (e.g., JSON columns) can bloat the database.
serialize for complex fields or exclude them:
/**
* @Auditable(exclude={"metadata"})
*/
class Config {}
Symfony Cache:
Clear the cache after changing auditor.yaml:
php bin/console cache:clear
Enable Verbose Logging:
Add to config/packages/dev/auditor.yaml:
alli_govender_auditor:
logger: true
debug: true # Logs audit events to Symfony's logger
Check logs with:
php bin/console debug:config alli_govender_auditor
Check Audit Events:
Listen for AuditEvent in development to inspect payloads:
use AlliGovender\AuditorBundle\Event\AuditEvent;
$eventDispatcher->addListener(AuditEvent::POST_AUDIT, function (AuditEvent $event) {
\Symfony\Component\Debug\Debug::dump($event->getAuditEntry());
});
Common Issues:
@Auditable is on the entity and the connection is correct in auditor.yaml.exclude or on annotations.AlliGovender\AuditorBundle\Strategy\AuditStrategyInterface to define custom logic (e.g., log only changes to specific fields):
namespace App\Strategy;
use AlliGovender\AuditorBundle\Strategy\AuditStrategyInterface;
class CustomFieldStrategy implements AuditStrategyInterface
{
public function shouldAudit($entity, $field, $action)
{
return in_array($field, ['name', 'email']) && $
How can I help you explore Laravel packages today?