DamienHarper / auditor

auditor, the missing audit log library
MIT License
164 stars 53 forks source link

How to define a storage mapper? #146

Closed bertoost closed 1 year ago

bertoost commented 1 year ago

Hi there,

I have configured multiple entity-managers and therefore I understand I have to configure a storage mapper.

I have created one like the example in documentation, as follows;

<?php

namespace App\Storage;

class AuditStorageMapper
{
    public function __invoke(....)
    {
        // ...
    }
}

And configured it like

storage_mapper: '@App\Storage\AuditStorageMapper'

And tried multiple configurations... but I always get this error;

Symfony\Component\ErrorHandler\Error\UndefinedFunctionError^ {#6807
  #message: "Attempted to call function "AuditStorageMapper" from namespace "@App\Storage"."
  #code: 0
  #file: "./vendor/damienharper/auditor/src/Provider/Doctrine/DoctrineProvider.php"
  #line: 94
  trace: {
    ./vendor/damienharper/auditor/src/Provider/Doctrine/DoctrineProvider.php:94 { …}
    ./vendor/damienharper/auditor/src/Provider/Doctrine/Persistence/Event/TableSchemaSubscriber.php:27 { …}
    ./vendor/symfony/doctrine-bridge/ContainerAwareEventManager.php:66 { …}

How should I manage this?

In your DoctrineProvider it just does return $storageMapper($entity, $this->getStorageServices());, without new or something. Therefore... why not used a normal service class? which is auto-wired or default instantiated in Symfony?

Kind regards, Bert

DamienHarper commented 1 year ago

Hi @bertoost, auditor can be used outside of a Symfony project, that's why it doesn't require an autowired Symfony service for storage mapper.

A storage mapper has to be a closure/callable/invokable. It takes 2 parameters: an entity name and an array of storage services. It must return the storage service (from the provided array) handling the provided entity name.

There was a bug until version 2.4.2 which prevented using invokable classes, it's fixed now.

Let me give you an example:

<?php
namespace App;

use App\Entity\MyEntity1; 
use App\Entity\MyEntity2; 
use App\Entity\MyEntity3; 
use DH\Auditor\Provider\Service\StorageServiceInterface;

/**
 * MyStorageMapper is an invokable class/service
 *
 */
class MyStorageMapper
{
    // the service expects 2 parameters and should return an object 
    // implementing StorageServiceInterface
    public function __invoke(string $entity, array $storageServices): StorageServiceInterface {
        return \in_array($entity, [MyEntity1::class, MyEntity2::class], true) ? $storageServices['db1'] : $storageServices['db2'];
    }
}

Then, if using auditor-bundle, register it as a storage mapper

# config/packages/dh_auditor.yaml
dh_auditor:
    providers:
        doctrine:
            # Invokable service that maps audit events to storage services
            storage_mapper: App\MyStorageMapper
bertoost commented 1 year ago

Thanks @DamienHarper ! The storage mapper now works. I had to report this on the bundle maybe ;-)

Therefore I am now running into this error when running schema-update command of doctrine.

In MappingException.php line 26:

  [Doctrine\Persistence\Mapping\MappingException]
  The class 'App\Entity\Tenant\Student' was not found in the chain configured namespaces App\Entity\Main

The namespace is configured on the second entity-manager which I do correctly map in the storage mapper.

dmitryuk commented 1 year ago

@bertoost can you provide more debug trace?

DamienHarper commented 1 year ago

@bertoost I close this issue since no feedback has been provided, feel free to reopen it if needed.