auditor-bundle

repository·master·Indexed 19 days ago

https://github.com/damienharper/auditor-bundle

A Symfony bundle that automates the creation of audit logs for Doctrine ORM database changes. It tracks modifications to audited entities and supports configuration via YAML or PHP 8 attributes (#[Auditable], #[Ignore], #[Security], and #[DiffLabel]). The bundle supports single or multi-database storage setups, custom audit table naming, and an optional audit viewer UI.

Tokens
26.8K
Snippets
80
Records
115
Agent score
66%

What's inside auditor-bundle

  1. Overview of auditor-bundle

    master
    auditor-bundle (formerly DoctrineAuditBundle) integrates the auditor library into Symfony applications to create audit logs for all Doctrine ORM database-related changes. Once installed and configured, any database change affecting audited entities is automatically logged. Additionally, running schema updates will automatically set up audit logs for every new auditable entity.
  2. What is auditor-bundle?

    master

    auditor-bundle is a Symfony integration for the auditor library. It automates the process of creating audit logs for Doctrine ORM database changes within a Symfony application.

    Key Features

    • Automatic wiring: Services are automatically configured via Symfony's Dependency Injection (DI).
    • Security integration: Leverages Symfony's security component for user tracking and access control.
    • Built-in viewer: Provides a web interface to browse audit logs at the /audit route.
    • Console support: Automatically tracks changes made via Symfony console commands.
    • Internationalization: Supports translations for 9 languages.
    • YAML configuration: Configurable via dh_auditor.yaml.
    • Extra Data: Allows attaching custom contextual data to audit entries using event listeners.
  3. Overview of customizable providers in auditor-bundle

    master

    The auditor-bundle allows you to customize three core providers to control how audit entries are enriched and how access to them is managed. By default, the bundle uses the following services:

    ProviderPurposeDefault Service
    User ProviderReturns current user informationdh_auditor.user_provider
    Security ProviderReturns IP address and firewall namedh_auditor.security_provider
    Role CheckerChecks if user can view entity auditsdh_auditor.role_checker

    These providers interact with the audit lifecycle: the User Provider and Security Provider contribute data to the Audit Entry, while the Role Checker determines access for the Audit Viewer.

  4. Understand built-in providers

    master

    The bundle includes default implementations for its providers:

    UserProvider

    • Retrieves the current user from Symfony's TokenStorage.
    • Extracts the user ID using the getId() method (if available).
    • Extracts the username via getUserIdentifier().
    • Supports tracking impersonation (switch user).

    SecurityProvider

    • Retrieves the client IP from the current Request.
    • Retrieves the firewall name from the FirewallMap.

    RoleChecker

    • Utilizes Symfony's AuthorizationChecker.
    • Validates roles configured per entity.
    • Grants access if no roles are configured for an entity.
    • Grants access if no user is authenticated.
  5. Implement a Storage Mapper for Multi-Database Routing

    master

    When using multiple storage services, you must implement a StorageMapper to route audits to the correct database. A StorageMapper is a callable that receives the audited entity's FQCN and an array of available StorageServiceInterface instances, returning the appropriate service.

    Example: Routing by Entity Type

    In this example, specific high-security entities are routed to a secure_entity_manager, while all others go to the default_entity_manager.

    <?php
    
    namespace App\Audit;
    
    use App\Entity\HighSecurityEntity;
    use App\Entity\SensitiveData;
    use DH\Auditor\Provider\Service\StorageServiceInterface;
    
    class StorageMapper
    {
        private const SECURE_ENTITIES = [
            HighSecurityEntity::class,
            SensitiveData::class,
        ];
    
        public function __invoke(string $entity, array $storageServices): StorageServiceInterface
        {
            if (in_array($entity, self::SECURE_ENTITIES, true)) {
                return $storageServices['dh_auditor.provider.doctrine.storage_services.doctrine.orm.secure_entity_manager'];
            }
    
            return $storageServices['dh_auditor.provider.doctrine.storage_services.doctrine.orm.default_entity_manager'];
        }
    }

    Register the Mapper

    Register your mapper class in config/packages/dh_auditor.yaml under the storage_mapper key:

    dh_auditor:
        providers:
            doctrine:
                storage_services:
                    - '@doctrine.orm.default_entity_manager'
                    - '@doctrine.orm.secure_entity_manager'
                
                storage_mapper: 'App\Audit\StorageMapper'
  6. How auditor-bundle handles console commands

    master

    When running commands via the CLI, the bundle automatically switches to the ConsoleUserProvider via the ConsoleEventSubscriber. This requires no manual configuration.

    In this mode, the audit entry is populated with:

    • User ID: The name of the command being executed (e.g., app:import-users).
    • Username: The name of the command being executed.

    This behavior allows you to filter audit entries by specific CLI commands within the audit viewer.

  7. Understand the auditor-bundle architecture

    master

    The bundle acts as a bridge between your Symfony application and the core auditor library. It provides several integration layers:

    1. Symfony Integration: Bridges the core library with Symfony's TokenStorage (via UserProvider), RequestStack and FirewallMap (via SecurityProvider), and the AuthorizationChecker (via RoleChecker).
    2. Viewer: A ViewerController that provides a web interface for browsing logs.
    3. Console: A ConsoleEventSubscriber that ensures changes made via CLI commands are tracked.

    Provided Services

    ComponentService IDDescription
    UserProviderdh_auditor.user_providerGets the current user from TokenStorage
    SecurityProviderdh_auditor.security_providerGets the IP address and firewall from the Request
    RoleCheckerdh_auditor.role_checkerChecks access via the Symfony Security component
    ConsoleUserProvider(internal)Tracks console commands as the user
    ViewerController(internal)Web UI for audit logs
    RoutingLoader(internal)Loads viewer routes
  8. How Diff Label Resolvers work internally

    master

    Labels are resolved during the post-commit callback of the DBAL middleware. This occurs after the Doctrine transaction commits but before the audit rows are written to the database.

    Data Flow:

    1. flush() is called.
    2. Database transaction commits.
    3. Post-commit callback triggers.
    4. The system detects the #[DiffLabel] attribute on the property.
    5. The resolver is called with the raw value.
    6. The diff is stored in the JSON as {"old": {"value": x, "label": "y"}, "new": {"value": a, "label": "b"}}.

    Note on Data Access: The raw value is always preserved. When accessing $entry->getDiffs(), the field will return an array containing both value and label instead of a scalar.

  9. Update ConsoleUserProvider blame identifiers

    master

    In version 7.0, CLI commands now use the command name as the user identifier.

    • Before (6.x): blame_id was set to "command".
    • After (7.0): blame_id is set to the command name (e.g., "app:import-users").

    Note: Existing audit entries with blame_id = "command" will not be automatically migrated.

  10. How extra data works in auditor-bundle

    master

    The extra_data feature allows you to attach arbitrary supplementary JSON data to audit entries. This is useful for capturing context that isn't part of the entity itself, such as route names, request IDs, or business-specific roles.

    There are two ways to populate this data:

    1. extra_data_provider (Global Scope): Best for request-level context (e.g., current route, tenant ID) that applies to all audit entries. It runs first.
    2. LifecycleEvent Listener (Per-entity Scope): Best for fine-grained, entity-specific data (e.g., a user's department). It runs after the provider and can enrich or override the existing data.

    Both can be used together. The provider returns a plain array, while the listener must provide a JSON-encoded string.

    // The provider returns a plain array
    public function __invoke(): ?array {
        return ['route' => 'app_home'];
    }
    
    // The listener must return a JSON-encoded string
    public function __invoke(LifecycleEvent $event): void {
        $payload = $event->getPayload();
        $payload['extra_data'] = json_encode(['dept' => 'IT'], JSON_THROW_ON_ERROR);
        $event->setPayload($payload);
    }
  11. How User Providers work

    master

    A User Provider is an abstraction used to identify the current user in audit entries. It is responsible for returning information about who performed a database change.

    To implement a provider, you must satisfy the UserProviderInterface, which requires an __invoke method. This method must return an object implementing UserInterface (containing a unique identifier and a username) or null if no user is identified.

    namespace DH👤👤‍♂‍👤👤;
    
    interface UserProviderInterface
    {
        public function __invoke(): ?UserInterface;
    }
    
    interface UserInterface
    {
        public function getIdentifier(): ?string;
        public function getUsername(): ?string;
    }