<?php
//----------------------------------------------------------------------
// src/Logging/DatabaseActivitySubscriber.php
// Plan.io Task #3922
//----------------------------------------------------------------------
namespace App\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\Entity\Access;
use App\Entity\LoggableInterface;
use App\Entity\Product\Product;
use App\Logging\DeletionContextLogger;
use App\Logging\Activity\AccessAPILog;
use App\Logging\Activity\AccessClientLog;
use App\Logging\Activity\AccessLog;
use App\Logging\Activity\AddressLog;
use App\Logging\Activity\AdvancedNotificationLog;
use App\Logging\Activity\AttachmentLog;
use App\Logging\Activity\ChargeLog;
use App\Logging\Activity\ClientLog;
use App\Logging\Activity\CommandLog;
use App\Logging\Activity\CostLog;
use App\Logging\Activity\CostNoteLog;
use App\Logging\Activity\DemandLog;
use App\Logging\Activity\DevisLog;
use App\Logging\Activity\DocumentLog;
use App\Logging\Activity\ExternalMessageLog;
use App\Logging\Activity\FinancialLog;
use App\Logging\Activity\GhostDevisLog;
use App\Logging\Activity\IkeaServiceOrderLog;
use App\Logging\Activity\IkeaServiceOrderUpdateLog;
use App\Logging\Activity\IndividualLog;
use App\Logging\Activity\InstallmentLog;
use App\Logging\Activity\InvoiceLog;
use App\Logging\Activity\MissionLog;
use App\Logging\Activity\NoteLog;
use App\Logging\Activity\PhoneLog;
use App\Logging\Activity\PlanningResourceLog;
use App\Logging\Activity\ProjectNotebookLog;
use App\Logging\Activity\ProjectNotebookItemLog;
use App\Logging\Activity\ProductLog;
use App\Logging\Activity\SocietyLog;
use App\Logging\Activity\SocietyGroupLog;
use App\Logging\Activity\StoreLog;
use App\Logging\Activity\StoreGroupLog;
use App\Logging\Activity\SupplierLog;
use App\Logging\Activity\TaskLog;
use App\Logging\Activity\TemplateLog;
use App\Logging\Activity\Prospect\ContactLog as ProspectContactLog;
use App\Logging\Activity\Prospect\ReportLog as ProspectReportLog;
use App\Logging\Activity\Prospect\SurveyLog as ProspectSurveyLog;
use App\Logging\Activity\Prospect\SurveyQuestionLog as ProspectSurveyQuestionLog;
use App\Logging\HR\AccessFunctionLog;
use App\Logging\HR\ApplicationLog;
use App\Logging\HR\HRVarLog;
use App\Logging\HR\HumanResourceLog;
use App\Logging\HR\NoteLog as HRNoteLog;
use App\Logging\HR\OPVarLog;
use App\Logging\HR\Equipment\DamageLog;
use App\Logging\HR\Equipment\EquipmentLog;
use App\Logging\HR\Equipment\FineLog;
use App\Logging\HR\Equipment\MileageLog;
use App\Logging\HR\Equipment\NoteLog as EquipmentNoteLog;
use App\Logging\HR\Equipment\VehicleLog;
use App\Logging\HR\Equipment\VehicleMaintenanceLog;
use App\Logging\HR\Equipment\VehicleMaintenanceDeadlineLog;
use App\Logging\HR\RHForm\AccidentLog as RHFormAccidentLogLog;
use App\Logging\HR\RHForm\LeaveLog as RHFormLeaveLog;
use App\Logging\HR\RHForm\RHFormLog;
use App\Logging\Media\ArticleLog;
use App\Logging\Media\CloudAclInodeLog;
use App\Logging\Media\CloudInodeLog;
use App\Logging\Media\CommentLog as ArticleCommentLog;
use App\Logging\Security\AclLog;
use App\Logging\Security\AclPlanningLog;
use App\Services\Logging;
use App\Services\LogTools;
class DatabaseActivitySubscriber implements EventSubscriberInterface
{
private array $pendingLogs = [];
private array $avoidDuplicates = [];
public function __construct(EntityManagerInterface $em, Logging $logger, LogTools $logTools, DeletionContextLogger $contextLogger,
AccessAPILog $accessApiLog, AccessClientLog $accessClientLog, AccessLog $accessLog, AccessFunctionLog $accessFunctionLog,
AclLog $aclLog, AclPlanningLog $aclPlanningLog,
AddressLog $addressLog, AdvancedNotificationLog $advancedNotificationLog, ApplicationLog $applicationLog,
ArticleLog $articleLog, ArticleCommentLog $articleCommentLog,
AttachmentLog $attachmentLog,
ChargeLog $chargeLog, ClientLog $clientLog,
CloudAclInodeLog $cloudAclInodeLog, CloudInodeLog $cloudInodeLog,
CommandLog $commandLog, CostLog $costLog, CostNoteLog $costNoteLog,
DamageLog $damageLog, DevisLog $devisLog, DemandLog $demandLog, DocumentLog $documentLog,
EquipmentLog $equipmentLog, EquipmentNoteLog $equipmentNoteLog, ExternalMessageLog $externalMessageLog,
FinancialLog $financialLog, FineLog $fineLog, GhostDevisLog $ghostDevisLog,
IkeaServiceOrderLog $ikeaServiceOrderLog, IkeaServiceOrderUpdateLog $ikeaServiceOrderUpdateLog,
IndividualLog $individualLog, InstallmentLog $installmentLog, InvoiceLog $invoiceLog,
HumanResourceLog $humanResourceLog, HRNoteLog $hrNoteLog, HRVarLog $hrVarLog,
MileageLog $mileageLog, MissionLog $missionLog, NoteLog $noteLog, OPVarLog $opVarLog,
PhoneLog $phoneLog, PlanningResourceLog $planningResourceLog,
ProjectNotebookLog $projectNotebookLog, ProjectNotebookItemLog $projectNotebookItemLog,
ProductLog $productLog,
ProspectContactLog $prospectContactLog, ProspectReportLog $prospectReportLog, ProspectSurveyLog $prospectSurveyLog, ProspectSurveyQuestionLog $prospectSurveyQuestionLog,
RHFormLog $rhFormLog, RHFormAccidentLogLog $rhFormAccidentLog, RHFormLeaveLog $rhFormLeaveLog,
SocietyLog $societyLog, SocietyGroupLog $societyGroupLog, StoreLog $storeLog, StoreGroupLog $storeGroupLog, SupplierLog $supplierLog,
TaskLog $taskLog, TemplateLog $templateLog,
VehicleLog $vehicleLog, VehicleMaintenanceLog $vehicleMaintenanceLog, VehicleMaintenanceDeadlineLog $vmdLog)
{
$this->em = $em;
$this->contextLogger = $contextLogger;
$this->logger = $logger;
$this->logTools = $logTools;
$this->accessApiLog = $accessApiLog;
$this->accessClientLog = $accessClientLog;
$this->accessLog = $accessLog;
$this->accessFunctionLog = $accessFunctionLog;
$this->aclLog = $aclLog;
$this->aclPlanningLog = $aclPlanningLog;
$this->addressLog = $addressLog;
$this->advancedNotificationLog = $advancedNotificationLog;
$this->applicationLog = $applicationLog;
$this->articleLog = $articleLog;
$this->articleCommentLog = $articleCommentLog;
$this->attachmentLog = $attachmentLog;
$this->chargeLog = $chargeLog;
$this->clientLog = $clientLog;
$this->cloudAclInodeLog = $cloudAclInodeLog;
$this->cloudInodeLog = $cloudInodeLog;
$this->commandLog = $commandLog;
$this->costLog = $costLog;
$this->costNoteLog = $costNoteLog;
$this->demandLog = $demandLog;
$this->damageLog = $damageLog;
$this->devisLog = $devisLog;
$this->documentLog = $documentLog;
$this->equipmentLog = $equipmentLog;
$this->equipmentNoteLog = $equipmentNoteLog;
$this->externalMessageLog = $externalMessageLog;
$this->financialLog = $financialLog;
$this->ghostDevisLog = $ghostDevisLog;
$this->fineLog = $fineLog;
$this->ikeaServiceOrderLog = $ikeaServiceOrderLog;
$this->ikeaServiceOrderUpdateLog = $ikeaServiceOrderUpdateLog;
$this->individualLog = $individualLog;
$this->installmentLog = $installmentLog;
$this->invoiceLog = $invoiceLog;
$this->humanResourceLog = $humanResourceLog;
$this->hrNoteLog = $hrNoteLog;
$this->hrVarLog = $hrVarLog;
$this->mileageLog = $mileageLog;
$this->missionLog = $missionLog;
$this->noteLog = $noteLog;
$this->opVarLog = $opVarLog;
$this->phoneLog = $phoneLog;
$this->planningResourceLog = $planningResourceLog;
$this->projectNotebookLog = $projectNotebookLog;
$this->projectNotebookItemLog = $projectNotebookItemLog;
$this->productLog = $productLog;
$this->prospectContactLog = $prospectContactLog;
$this->prospectReportLog = $prospectReportLog;
$this->prospectSurveyLog = $prospectSurveyLog;
$this->prospectSurveyQuestionLog = $prospectSurveyQuestionLog;
$this->rhFormLog = $rhFormLog;
$this->rhFormAccidentLog = $rhFormAccidentLog;
$this->rhFormLeaveLog = $rhFormLeaveLog;
$this->societyLog = $societyLog;
$this->societyGroupLog = $societyGroupLog;
$this->storeLog = $storeLog;
$this->storeGroupLog = $storeGroupLog;
$this->supplierLog = $supplierLog;
$this->taskLog = $taskLog;
$this->templateLog = $templateLog;
$this->vehicleLog = $vehicleLog;
$this->vehicleMaintenanceLog = $vehicleMaintenanceLog;
$this->vmdLog = $vmdLog;
$today = new \DateTime();
$this->timestamp = "[".$today->format("Y/m/d H:i:s")."] ";
$this->logServices = [
"App\Entity\Access" => $this->accessLog,
"App\Entity\AccessClient\AccessClient" => $this->accessClientLog,
"App\Entity\APIRest\AccessAPI" => $this->accessApiLog,
"App\Entity\Common\Attachment" => $this->attachmentLog,
"App\Entity\Client\Client" => $this->clientLog,
"App\Entity\Client\Contact" => $this->prospectContactLog,
"App\Entity\Client\Financial" => $this->financialLog,
"App\Entity\Client\Individual" => $this->individualLog,
"App\Entity\Client\Store" => $this->storeLog,
"App\Entity\Client\StoreGroup" => $this->storeGroupLog,
"App\Entity\Cloud\AclInode" => $this->cloudAclInodeLog,
"App\Entity\Cloud\Inode" => $this->cloudInodeLog,
"App\Entity\Ding\AdvancedNotification" => $this->advancedNotificationLog,
"App\Entity\Equipment\Damage" => $this->damageLog,
"App\Entity\Equipment\Fine" => $this->fineLog,
"App\Entity\Equipment\Equipment" => $this->equipmentLog,
"App\Entity\Equipment\Mileage" => $this->mileageLog,
"App\Entity\Equipment\Note" => $this->equipmentNoteLog,
"App\Entity\Equipment\Vehicle" => $this->vehicleLog,
"App\Entity\Equipment\VehicleMaintenance" => $this->vehicleMaintenanceLog,
"App\Entity\Equipment\VehicleMaintenanceDeadline" => $this->vmdLog,
"App\Entity\HR\AccessFunction" => $this->accessFunctionLog,
"App\Entity\HR\Application\Application" => $this->applicationLog,
"App\Entity\HR\HumanResource" => $this->humanResourceLog,
"App\Entity\HR\Note" => $this->hrNoteLog,
"App\Entity\HR\RHForm\Accident" => $this->rhFormAccidentLog,
"App\Entity\HR\RHForm\Leave" => $this->rhFormLeaveLog,
"App\Entity\HR\RHForm\RHForm" => $this->rhFormLog,
"App\Entity\HR\Salary\HRVariable" => $this->hrVarLog,
"App\Entity\HR\Salary\OPVariable" => $this->opVarLog,
"App\Entity\Ikea\ServiceOrder" => $this->ikeaServiceOrderLog,
"App\Entity\Ikea\ServiceOrderUpdate" => $this->ikeaServiceOrderUpdateLog,
"App\Entity\Location\Address" => $this->addressLog,
"App\Entity\Mission\Mission" => $this->missionLog,
"App\Entity\Media\Article" => $this->articleLog,
"App\Entity\Media\Comment" => $this->articleCommentLog,
"App\Entity\Planning\PlanningResource" => $this->planningResourceLog,
"App\Entity\Planning\Task" => $this->taskLog,
"App\Entity\Platform\Command\Command" => $this->commandLog,
"App\Entity\Platform\Cost\Cost" => $this->costLog,
"App\Entity\Platform\Cost\CostNote" => $this->costNoteLog,
"App\Entity\Platform\Demand\Demand" => $this->demandLog,
"App\Entity\Platform\Devis\Devis" => $this->devisLog,
"App\Entity\Platform\Devis\GhostDevis" => $this->ghostDevisLog,
"App\Entity\Platform\ExternalMessage\ExternalMessage" => $this->externalMessageLog,
"App\Entity\Platform\Installment" => $this->installmentLog,
"App\Entity\Platform\Invoice\Invoice" => $this->invoiceLog,
"App\Entity\Platform\Note" => $this->noteLog,
"App\Entity\Platform\Phone" => $this->phoneLog,
"App\Entity\Platform\Report" => $this->prospectReportLog,
"App\Entity\Platform\Society" => $this->societyLog,
"App\Entity\Platform\Supplier" => $this->supplierLog,
"App\Entity\Platform\Survey\Survey" => $this->prospectSurveyLog,
"App\Entity\Platform\Survey\SurveyQuestion" => $this->prospectSurveyQuestionLog,
"App\Entity\ProjectManager\ProjectNotebook" => $this->projectNotebookLog,
"App\Entity\ProjectManager\ProjectNotebookItem" => $this->projectNotebookItemLog,
"App\Entity\Product\Charge" => $this->chargeLog,
"App\Entity\Product\Product" => $this->productLog,
"App\Entity\Product\Template" => $this->templateLog,
"App\Entity\Security\Acl" => $this->aclLog,
"App\Entity\Security\AclPlanning" => $this->aclPlanningLog,
"App\Entity\SocietyGroup" => $this->societyGroupLog,
"App\Entity\Webapp\Document" => $this->documentLog,
];
}
// 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->societyGroupLog->handleSocietyGroupCreation($log, $object);
// > $objectClientId case (Client creation)
$this->individualLog->handleIndividual($log, $object);
// > $objectMissionId case (Mission creation)
$this->missionLog->handleMission($log, $object);
// > $objectHumanResourceId case (HumanResource creation)
$this->humanResourceLog->handleHumanResource($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->logger->smartLog($args);
$this->pendingLogs[] = array(
'log' => $log,
'object' => $entity,
);
}
}
}