<?php
//----------------------------------------------------------------------
// src/AppRemix/Logging/DatabaseActivitySubscriber.php
//----------------------------------------------------------------------
namespace App\AppRemix\Logging;
use Doctrine\Bundle\DoctrineBundle\EventSubscriber\EventSubscriberInterface;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Events;
use Doctrine\Persistence\Event\LifecycleEventArgs;
use Doctrine\Persistence\Proxy;
use App\AppRemix\Logging\Objects\ClientLog;
use App\AppRemix\Logging\Objects\DevisLog;
use App\AppRemix\Logging\Objects\FileLog;
use App\AppRemix\Logging\Objects\InvoiceLog;
use App\AppRemix\Logging\Objects\MissionLog;
use App\AppRemix\Logging\Objects\PaymentLog;
use App\AppRemix\Logging\Objects\ProductLog;
use App\AppRemix\Logging\Objects\PurchaseLog;
use App\AppRemix\Logging\Objects\ReportLog;
use App\AppRemix\Logging\Objects\SocietyLog;
use App\AppRemix\Logging\Objects\OpenAIConfigLog;
use App\AppRemix\Logging\Objects\OpenAIPriceLog;
use App\AppRemix\Logging\Objects\SocietySettingsLog;
use App\AppRemix\Logging\Objects\TemplateLog;
use App\AppRemix\Logging\DeletionContextLogger;
use App\AppRemix\Logging\Tools;
use App\AppRemix\Entity\LoggableInterface;
use App\Services\LogTools;
class DatabaseActivitySubscriber implements EventSubscriberInterface
{
private array $pendingLogs = [];
private array $avoidDuplicates = [];
public function __construct(EntityManagerInterface $em, LogTools $logTools, Tools $tools,
DeletionContextLogger $contextLogger,
SocietyLog $societyLog, MissionLog $missionLog, PurchaseLog $purchaseLog, ReportLog $reportLog, TemplateLog $templateLog, ProductLog $productLog, DevisLog $devisLog, InvoiceLog $invoiceLog, PaymentLog $paymentLog, FileLog $fileLog, SocietySettingsLog $societySettingsLog, OpenAIConfigLog $openAIConfigLog, ClientLog $clientLog, OpenAIPriceLog $openAIPriceLog)
{
$this->em = $em;
$this->logTools = $logTools;
$this->tools = $tools;
$this->contextLogger = $contextLogger;
$this->societyLog = $societyLog;
$this->missionLog = $missionLog;
$this->purchaseLog = $purchaseLog;
$this->reportLog = $reportLog;
$this->templateLog = $templateLog;
$this->productLog = $productLog;
$this->devisLog = $devisLog;
$this->invoiceLog = $invoiceLog;
$this->paymentLog = $paymentLog;
$this->fileLog = $fileLog;
$this->societySettingsLog = $societySettingsLog;
$this->openAIConfigLog = $openAIConfigLog;
$this->clientLog = $clientLog;
$this->openAIPriceLog = $openAIPriceLog;
$this->logServices = [
"App\AppRemix\Entity\Society" => $this->societyLog,
"App\AppRemix\Entity\Mission" => $this->missionLog,
"App\AppRemix\Entity\Purchase" => $this->purchaseLog,
"App\AppRemix\Entity\Report" => $this->reportLog,
"App\AppRemix\Entity\Template" => $this->templateLog,
"App\AppRemix\Entity\Product" => $this->productLog,
"App\AppRemix\Entity\Devis" => $this->devisLog,
"App\AppRemix\Entity\Invoice" => $this->invoiceLog,
"App\AppRemix\Entity\Payment" => $this->paymentLog,
"App\AppRemix\Entity\File" => $this->fileLog,
"App\AppRemix\Entity\SocietySettings" => $this->societySettingsLog,
"App\AppRemix\Entity\OpenAIConfig" => $this->openAIConfigLog,
"App\AppRemix\Entity\OpenAIPrice" => $this->openAIPriceLog,
"App\AppRemix\Entity\Client" => $this->clientLog,
];
}
// this method can only return the event names; you cannot define a
// custom method name to execute when each event triggers
public function getSubscribedEvents(): array
{
// Comment this to activate subscriber
// return [];
return [
Events::prePersist,
Events::preUpdate,
Events::preRemove,
Events::postFlush,
];
}
// Do not return in this method, it breaks Doctrine cycles
// Called when an object is created
public function prePersist(LifecycleEventArgs $args): void
{
$data = $this->handleData("prePersist", $args);
if ($data !== null)
{
$entity = $data['entity'];
$entityClass = $data['entityClass'];
$logService = $data['logService'];
// Create logging args for object creation
$logArgs = $logService->logCreation($entity);
// Save log args for postFlush
$this->handleLogs("prePersist", $logArgs, $entity);
}
}
// Do not return in this method, it breaks Doctrine cycles
public function preUpdate(LifecycleEventArgs $args): void
{
$data = $this->handleData("preUpdate", $args);
if ($data !== null)
{
$entity = $data['entity'];
$entityClass = $data['entityClass'];
$logService = $data['logService'];
// Get the changes from the Unit of Work
$changes = $args->getEntityChangeSet();
// Create logging args for object modification
$logArgs = $logService->logChanges($entity, $changes);
// Save log args for postFlush
$this->handleLogs("prePersist", $logArgs, $entity);
}
}
// Do not return in this method, it breaks Doctrine cycles
public function preRemove(LifecycleEventArgs $args): void
{
$data = $this->handleData("preRemove", $args);
if ($data !== null)
{
$entity = $data['entity'];
$entityClass = $data['entityClass'];
$logService = $data['logService'];
// Create logging args for object removal
$logArgs = $logService->logRemoval($entity);
// Save log args for postFlush
$this->handleLogs("preRemove", $logArgs, $entity);
}
}
// Specific Installment case (and also Attachment, Address etc ...)
// Some entities are logged not as an object, but attached to another object
// An Installment related to an Invoice will have log.objectId = installment.invoice.id
// However, in those cases, log.objectId is not null even in prePersist
// since we are prePersisting the newly created installment,
// but actually logging for an Invoice that is already created.
// So the main condition if (empty($log->getObjectId()))
// is never true for such cases.
// So no error and no problem ;)
public function postFlush(): void
{
if (!empty($this->pendingLogs))
{
// Handle logs of objects that have been created => Update ID now that we have it
foreach ($this->pendingLogs as $logData)
{
$log = $logData['log'];
$object = $logData['object'];
if (empty($log->getObjectId()))
{
$log->setObjectId($object->getId());
}
// Handle Special Cases
$this->tools->handleNewSociety($log);
$this->tools->handleMission($log);
$this->tools->handleClient($log, $object);
$this->em->persist($log);
}
// Reset arrays
$this->pendingLogs = [];
try
{
// Save data to database
$this->em->flush();
}
catch (\Exception $e)
{
$allGood = false;
$this->logTools->errorlog($e->getMessage());
$this->logTools->errorlog_db($e);
}
$this->contextLogger->clear();
}
// Reset duplicates array
$this->avoidDuplicates = [];
}
protected function handleData($caller, $args)
{
// Get Entity and Entity class
$entity = $args->getObject();
// Avoid getting Proxies\__CG__\App\Entity\NameOfEntity
// instead of App\Entity\NameOfEntity
$entityClass = $entity instanceof Proxy
? get_parent_class($entity)
: get_class($entity);
// Optionally ignore certain entity types (like internal logs themselves)
if (!$entity instanceof LoggableInterface)
{
return null;
}
if (!array_key_exists($entityClass, $this->logServices))
{
return null;
}
// Avoid duplicates
// I really have no idea if this is necessary
// And I really hope it does harm either
$hash = spl_object_hash($entity);
if (in_array($hash, $this->avoidDuplicates))
{
return null;
}
$this->avoidDuplicates[] = $hash;
// Get the corresponding Log Service
$logService = $this->logServices[$entityClass];
// Check to see if the Entity has some info stored
$info = null;
if (method_exists($entity, "getLoggingInfo"))
{
$info = $entity->getLoggingInfo();
}
return array(
'entity' => $entity,
'entityClass' => $entityClass,
'logService' => $logService,
'info' => $info,
);
}
protected function handleLogs($caller, $logArgs, $entity)
{
foreach ($logArgs as $key => $args)
{
$log = $this->tools->smartLog($args);
$this->pendingLogs[] = array(
'log' => $log,
'object' => $entity,
);
}
}
}