Compare commits
2 Commits
v1.4.0
...
508066d39f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
508066d39f | ||
|
|
70956c204e |
Submodule Inventory_frontend updated: 9f7dd12b34...7c44778f25
@@ -6,8 +6,11 @@ namespace App\EventSubscriber;
|
|||||||
|
|
||||||
use App\Entity\AuditLog;
|
use App\Entity\AuditLog;
|
||||||
use App\Entity\Composant;
|
use App\Entity\Composant;
|
||||||
|
use App\Entity\CustomFieldValue;
|
||||||
use App\Entity\ModelType;
|
use App\Entity\ModelType;
|
||||||
use App\Entity\Product;
|
use App\Entity\Product;
|
||||||
|
use App\Entity\Profile;
|
||||||
|
use DateTimeInterface;
|
||||||
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
|
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\Common\EventSubscriber;
|
use Doctrine\Common\EventSubscriber;
|
||||||
@@ -15,15 +18,24 @@ use Doctrine\ORM\EntityManagerInterface;
|
|||||||
use Doctrine\ORM\Event\OnFlushEventArgs;
|
use Doctrine\ORM\Event\OnFlushEventArgs;
|
||||||
use Doctrine\ORM\Events;
|
use Doctrine\ORM\Events;
|
||||||
use Doctrine\ORM\PersistentCollection;
|
use Doctrine\ORM\PersistentCollection;
|
||||||
|
use Doctrine\ORM\UnitOfWork;
|
||||||
|
use Symfony\Bundle\SecurityBundle\Security;
|
||||||
use Symfony\Component\HttpFoundation\RequestStack;
|
use Symfony\Component\HttpFoundation\RequestStack;
|
||||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
use function is_array;
|
||||||
|
use function is_object;
|
||||||
|
use function is_scalar;
|
||||||
|
use function method_exists;
|
||||||
|
|
||||||
#[AsDoctrineListener(event: Events::onFlush)]
|
#[AsDoctrineListener(event: Events::onFlush)]
|
||||||
final class ComposantAuditSubscriber implements EventSubscriber
|
final class ComposantAuditSubscriber implements EventSubscriber
|
||||||
{
|
{
|
||||||
public function __construct(private readonly RequestStack $requestStack)
|
public function __construct(
|
||||||
{
|
private readonly RequestStack $requestStack,
|
||||||
}
|
private readonly Security $security,
|
||||||
|
) {}
|
||||||
|
|
||||||
public function getSubscribedEvents(): array
|
public function getSubscribedEvents(): array
|
||||||
{
|
{
|
||||||
@@ -39,10 +51,10 @@ final class ComposantAuditSubscriber implements EventSubscriber
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$uow = $em->getUnitOfWork();
|
$uow = $em->getUnitOfWork();
|
||||||
$actorProfileId = $this->resolveActorProfileId();
|
$actorProfileId = $this->resolveActorProfileId();
|
||||||
$pendingUpdates = [];
|
$pendingUpdates = [];
|
||||||
$pendingSnapshots = [];
|
$pendingSnapshots = [];
|
||||||
$pendingComponents = [];
|
$pendingComponents = [];
|
||||||
|
|
||||||
foreach ($uow->getScheduledEntityInsertions() as $entity) {
|
foreach ($uow->getScheduledEntityInsertions() as $entity) {
|
||||||
@@ -50,7 +62,7 @@ final class ComposantAuditSubscriber implements EventSubscriber
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$diff = $this->buildDiffFromChangeSet($uow->getEntityChangeSet($entity));
|
$diff = $this->buildDiffFromChangeSet($uow->getEntityChangeSet($entity));
|
||||||
$snapshot = $this->snapshotComposant($entity);
|
$snapshot = $this->snapshotComposant($entity);
|
||||||
$this->persistAuditLog($em, new AuditLog('composant', (string) $entity->getId(), 'create', $diff, $snapshot, $actorProfileId));
|
$this->persistAuditLog($em, new AuditLog('composant', (string) $entity->getId(), 'create', $diff, $snapshot, $actorProfileId));
|
||||||
}
|
}
|
||||||
@@ -61,14 +73,14 @@ final class ComposantAuditSubscriber implements EventSubscriber
|
|||||||
}
|
}
|
||||||
|
|
||||||
$componentId = (string) $entity->getId();
|
$componentId = (string) $entity->getId();
|
||||||
if ($componentId === '') {
|
if ('' === $componentId) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$diff = $this->buildDiffFromChangeSet($uow->getEntityChangeSet($entity));
|
$diff = $this->buildDiffFromChangeSet($uow->getEntityChangeSet($entity));
|
||||||
if ($diff !== []) {
|
if ([] !== $diff) {
|
||||||
$pendingUpdates[$componentId] = $this->mergeDiffs($pendingUpdates[$componentId] ?? [], $diff);
|
$pendingUpdates[$componentId] = $this->mergeDiffs($pendingUpdates[$componentId] ?? [], $diff);
|
||||||
$pendingSnapshots[$componentId] = $this->snapshotComposant($entity);
|
$pendingSnapshots[$componentId] = $this->snapshotComposant($entity);
|
||||||
$pendingComponents[$componentId] = $entity;
|
$pendingComponents[$componentId] = $entity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,8 +101,10 @@ final class ComposantAuditSubscriber implements EventSubscriber
|
|||||||
$this->collectCollectionUpdate($collection, $pendingUpdates, $pendingSnapshots, $pendingComponents);
|
$this->collectCollectionUpdate($collection, $pendingUpdates, $pendingSnapshots, $pendingComponents);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->collectCustomFieldValueChanges($uow, $pendingUpdates, $pendingSnapshots, $pendingComponents);
|
||||||
|
|
||||||
foreach ($pendingUpdates as $componentId => $diff) {
|
foreach ($pendingUpdates as $componentId => $diff) {
|
||||||
if ($diff === []) {
|
if ([] === $diff) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,8 +120,8 @@ final class ComposantAuditSubscriber implements EventSubscriber
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<string, array<string, array{from:mixed, to:mixed}>> $pendingUpdates
|
* @param array<string, array<string, array{from:mixed, to:mixed}>> $pendingUpdates
|
||||||
* @param array<string, array<string, mixed>> $pendingSnapshots
|
* @param array<string, array<string, mixed>> $pendingSnapshots
|
||||||
* @param array<string, Composant> $pendingComponents
|
* @param array<string, Composant> $pendingComponents
|
||||||
*/
|
*/
|
||||||
private function collectCollectionUpdate(
|
private function collectCollectionUpdate(
|
||||||
object $collection,
|
object $collection,
|
||||||
@@ -125,18 +139,18 @@ final class ComposantAuditSubscriber implements EventSubscriber
|
|||||||
}
|
}
|
||||||
|
|
||||||
$componentId = (string) $owner->getId();
|
$componentId = (string) $owner->getId();
|
||||||
if ($componentId === '') {
|
if ('' === $componentId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$mapping = $collection->getMapping();
|
$mapping = $collection->getMapping();
|
||||||
$fieldName = $mapping['fieldName'] ?? null;
|
$fieldName = $mapping['fieldName'] ?? null;
|
||||||
if ($fieldName !== 'constructeurs') {
|
if ('constructeurs' !== $fieldName) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$before = $this->normalizeCollection($collection->getSnapshot());
|
$before = $this->normalizeCollection($collection->getSnapshot());
|
||||||
$after = $this->normalizeCollection($collection->toArray());
|
$after = $this->normalizeCollection($collection->toArray());
|
||||||
|
|
||||||
if ($before === $after) {
|
if ($before === $after) {
|
||||||
return;
|
return;
|
||||||
@@ -145,15 +159,84 @@ final class ComposantAuditSubscriber implements EventSubscriber
|
|||||||
$diff = [
|
$diff = [
|
||||||
'constructeurIds' => [
|
'constructeurIds' => [
|
||||||
'from' => $before,
|
'from' => $before,
|
||||||
'to' => $after,
|
'to' => $after,
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
$pendingUpdates[$componentId] = $this->mergeDiffs($pendingUpdates[$componentId] ?? [], $diff);
|
$pendingUpdates[$componentId] = $this->mergeDiffs($pendingUpdates[$componentId] ?? [], $diff);
|
||||||
$pendingSnapshots[$componentId] = $this->snapshotComposant($owner);
|
$pendingSnapshots[$componentId] = $this->snapshotComposant($owner);
|
||||||
$pendingComponents[$componentId] = $owner;
|
$pendingComponents[$componentId] = $owner;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, array<string, array{from:mixed, to:mixed}>> $pendingUpdates
|
||||||
|
* @param array<string, array<string, mixed>> $pendingSnapshots
|
||||||
|
* @param array<string, Composant> $pendingComponents
|
||||||
|
*/
|
||||||
|
private function collectCustomFieldValueChanges(
|
||||||
|
UnitOfWork $uow,
|
||||||
|
array &$pendingUpdates,
|
||||||
|
array &$pendingSnapshots,
|
||||||
|
array &$pendingComponents,
|
||||||
|
): void {
|
||||||
|
foreach ($uow->getScheduledEntityInsertions() as $entity) {
|
||||||
|
if ($entity instanceof CustomFieldValue) {
|
||||||
|
$this->trackCustomFieldValueChange($entity, null, $entity->getValue(), $pendingUpdates, $pendingSnapshots, $pendingComponents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($uow->getScheduledEntityUpdates() as $entity) {
|
||||||
|
if (!$entity instanceof CustomFieldValue) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$changeSet = $uow->getEntityChangeSet($entity);
|
||||||
|
if (!isset($changeSet['value'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
[$oldVal, $newVal] = $changeSet['value'];
|
||||||
|
if ($oldVal !== $newVal) {
|
||||||
|
$this->trackCustomFieldValueChange($entity, $oldVal, $newVal, $pendingUpdates, $pendingSnapshots, $pendingComponents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($uow->getScheduledEntityDeletions() as $entity) {
|
||||||
|
if ($entity instanceof CustomFieldValue) {
|
||||||
|
$this->trackCustomFieldValueChange($entity, $entity->getValue(), null, $pendingUpdates, $pendingSnapshots, $pendingComponents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, array<string, array{from:mixed, to:mixed}>> $pendingUpdates
|
||||||
|
* @param array<string, array<string, mixed>> $pendingSnapshots
|
||||||
|
* @param array<string, Composant> $pendingComponents
|
||||||
|
*/
|
||||||
|
private function trackCustomFieldValueChange(
|
||||||
|
CustomFieldValue $cfv,
|
||||||
|
mixed $from,
|
||||||
|
mixed $to,
|
||||||
|
array &$pendingUpdates,
|
||||||
|
array &$pendingSnapshots,
|
||||||
|
array &$pendingComponents,
|
||||||
|
): void {
|
||||||
|
$owner = $cfv->getComposant();
|
||||||
|
if (!$owner instanceof Composant) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ownerId = (string) $owner->getId();
|
||||||
|
if ('' === $ownerId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fieldName = 'customField:'.$cfv->getCustomField()->getName();
|
||||||
|
$diff = [$fieldName => ['from' => $from, 'to' => $to]];
|
||||||
|
|
||||||
|
$pendingUpdates[$ownerId] = $this->mergeDiffs($pendingUpdates[$ownerId] ?? [], $diff);
|
||||||
|
$pendingSnapshots[$ownerId] = $this->snapshotComposant($owner);
|
||||||
|
$pendingComponents[$ownerId] = $owner;
|
||||||
|
}
|
||||||
|
|
||||||
private function persistAuditLog(EntityManagerInterface $em, AuditLog $log): void
|
private function persistAuditLog(EntityManagerInterface $em, AuditLog $log): void
|
||||||
{
|
{
|
||||||
$uow = $em->getUnitOfWork();
|
$uow = $em->getUnitOfWork();
|
||||||
@@ -166,13 +249,14 @@ final class ComposantAuditSubscriber implements EventSubscriber
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<string, array{0:mixed, 1:mixed}> $changeSet
|
* @param array<string, array{0:mixed, 1:mixed}> $changeSet
|
||||||
|
*
|
||||||
* @return array<string, array{from:mixed, to:mixed}>
|
* @return array<string, array{from:mixed, to:mixed}>
|
||||||
*/
|
*/
|
||||||
private function buildDiffFromChangeSet(array $changeSet): array
|
private function buildDiffFromChangeSet(array $changeSet): array
|
||||||
{
|
{
|
||||||
$diff = [];
|
$diff = [];
|
||||||
foreach ($changeSet as $field => [$oldValue, $newValue]) {
|
foreach ($changeSet as $field => [$oldValue, $newValue]) {
|
||||||
if ($field === 'updatedAt' || $field === 'createdAt') {
|
if ('updatedAt' === $field || 'createdAt' === $field) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +269,7 @@ final class ComposantAuditSubscriber implements EventSubscriber
|
|||||||
|
|
||||||
$diff[$field] = [
|
$diff[$field] = [
|
||||||
'from' => $normalizedOld,
|
'from' => $normalizedOld,
|
||||||
'to' => $normalizedNew,
|
'to' => $normalizedNew,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,28 +279,29 @@ final class ComposantAuditSubscriber implements EventSubscriber
|
|||||||
private function snapshotComposant(Composant $component): array
|
private function snapshotComposant(Composant $component): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'id' => $component->getId(),
|
'id' => $component->getId(),
|
||||||
'name' => $component->getName(),
|
'name' => $component->getName(),
|
||||||
'reference' => $component->getReference(),
|
'reference' => $component->getReference(),
|
||||||
'prix' => $component->getPrix(),
|
'prix' => $component->getPrix(),
|
||||||
'structure' => $component->getStructure(),
|
'structure' => $component->getStructure(),
|
||||||
'typeComposant' => $this->normalizeValue($component->getTypeComposant()),
|
'typeComposant' => $this->normalizeValue($component->getTypeComposant()),
|
||||||
'product' => $this->normalizeValue($component->getProduct()),
|
'product' => $this->normalizeValue($component->getProduct()),
|
||||||
'constructeurIds' => $this->normalizeCollection($component->getConstructeurs()),
|
'constructeurIds' => $this->normalizeCollection($component->getConstructeurs()),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param iterable<mixed> $items
|
* @param iterable<mixed> $items
|
||||||
|
*
|
||||||
* @return list<string>
|
* @return list<string>
|
||||||
*/
|
*/
|
||||||
private function normalizeCollection(iterable $items): array
|
private function normalizeCollection(iterable $items): array
|
||||||
{
|
{
|
||||||
$ids = [];
|
$ids = [];
|
||||||
foreach ($items as $item) {
|
foreach ($items as $item) {
|
||||||
if (\is_object($item) && \method_exists($item, 'getId')) {
|
if (is_object($item) && method_exists($item, 'getId')) {
|
||||||
$id = $item->getId();
|
$id = $item->getId();
|
||||||
if ($id !== null && $id !== '') {
|
if (null !== $id && '' !== $id) {
|
||||||
$ids[] = (string) $id;
|
$ids[] = (string) $id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -229,17 +314,17 @@ final class ComposantAuditSubscriber implements EventSubscriber
|
|||||||
|
|
||||||
private function normalizeValue(mixed $value): mixed
|
private function normalizeValue(mixed $value): mixed
|
||||||
{
|
{
|
||||||
if ($value === null || \is_scalar($value)) {
|
if (null === $value || is_scalar($value)) {
|
||||||
return $value;
|
return $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($value instanceof \DateTimeInterface) {
|
if ($value instanceof DateTimeInterface) {
|
||||||
return $value->format(\DateTimeInterface::ATOM);
|
return $value->format(DateTimeInterface::ATOM);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($value instanceof ModelType) {
|
if ($value instanceof ModelType) {
|
||||||
return [
|
return [
|
||||||
'id' => $value->getId(),
|
'id' => $value->getId(),
|
||||||
'name' => $value->getName(),
|
'name' => $value->getName(),
|
||||||
'code' => $value->getCode(),
|
'code' => $value->getCode(),
|
||||||
];
|
];
|
||||||
@@ -247,8 +332,8 @@ final class ComposantAuditSubscriber implements EventSubscriber
|
|||||||
|
|
||||||
if ($value instanceof Product) {
|
if ($value instanceof Product) {
|
||||||
return [
|
return [
|
||||||
'id' => $value->getId(),
|
'id' => $value->getId(),
|
||||||
'name' => $value->getName(),
|
'name' => $value->getName(),
|
||||||
'reference' => $value->getReference(),
|
'reference' => $value->getReference(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -257,11 +342,11 @@ final class ComposantAuditSubscriber implements EventSubscriber
|
|||||||
return $this->normalizeCollection($value);
|
return $this->normalizeCollection($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (\is_object($value) && \method_exists($value, 'getId')) {
|
if (is_object($value) && method_exists($value, 'getId')) {
|
||||||
return (string) $value->getId();
|
return (string) $value->getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (\is_array($value)) {
|
if (is_array($value)) {
|
||||||
return $value;
|
return $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,6 +356,7 @@ final class ComposantAuditSubscriber implements EventSubscriber
|
|||||||
/**
|
/**
|
||||||
* @param array<string, array{from:mixed, to:mixed}> $base
|
* @param array<string, array{from:mixed, to:mixed}> $base
|
||||||
* @param array<string, array{from:mixed, to:mixed}> $extra
|
* @param array<string, array{from:mixed, to:mixed}> $extra
|
||||||
|
*
|
||||||
* @return array<string, array{from:mixed, to:mixed}>
|
* @return array<string, array{from:mixed, to:mixed}>
|
||||||
*/
|
*/
|
||||||
private function mergeDiffs(array $base, array $extra): array
|
private function mergeDiffs(array $base, array $extra): array
|
||||||
@@ -284,17 +370,23 @@ final class ComposantAuditSubscriber implements EventSubscriber
|
|||||||
|
|
||||||
private function resolveActorProfileId(): ?string
|
private function resolveActorProfileId(): ?string
|
||||||
{
|
{
|
||||||
$session = $this->requestStack->getSession();
|
try {
|
||||||
if (!$session instanceof SessionInterface) {
|
$session = $this->requestStack->getSession();
|
||||||
return null;
|
if ($session instanceof SessionInterface) {
|
||||||
|
$profileId = $session->get('profileId');
|
||||||
|
if ($profileId) {
|
||||||
|
return (string) $profileId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable) {
|
||||||
|
// No session available (CLI context, etc.)
|
||||||
}
|
}
|
||||||
|
|
||||||
$profileId = $session->get('profileId');
|
$user = $this->security->getUser();
|
||||||
if (!$profileId) {
|
if ($user instanceof Profile) {
|
||||||
return null;
|
return $user->getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (string) $profileId;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,12 @@ declare(strict_types=1);
|
|||||||
namespace App\EventSubscriber;
|
namespace App\EventSubscriber;
|
||||||
|
|
||||||
use App\Entity\AuditLog;
|
use App\Entity\AuditLog;
|
||||||
|
use App\Entity\CustomFieldValue;
|
||||||
use App\Entity\ModelType;
|
use App\Entity\ModelType;
|
||||||
use App\Entity\Piece;
|
use App\Entity\Piece;
|
||||||
use App\Entity\Product;
|
use App\Entity\Product;
|
||||||
|
use App\Entity\Profile;
|
||||||
|
use DateTimeInterface;
|
||||||
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
|
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\Common\EventSubscriber;
|
use Doctrine\Common\EventSubscriber;
|
||||||
@@ -15,15 +18,24 @@ use Doctrine\ORM\EntityManagerInterface;
|
|||||||
use Doctrine\ORM\Event\OnFlushEventArgs;
|
use Doctrine\ORM\Event\OnFlushEventArgs;
|
||||||
use Doctrine\ORM\Events;
|
use Doctrine\ORM\Events;
|
||||||
use Doctrine\ORM\PersistentCollection;
|
use Doctrine\ORM\PersistentCollection;
|
||||||
|
use Doctrine\ORM\UnitOfWork;
|
||||||
|
use Symfony\Bundle\SecurityBundle\Security;
|
||||||
use Symfony\Component\HttpFoundation\RequestStack;
|
use Symfony\Component\HttpFoundation\RequestStack;
|
||||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
use function is_array;
|
||||||
|
use function is_object;
|
||||||
|
use function is_scalar;
|
||||||
|
use function method_exists;
|
||||||
|
|
||||||
#[AsDoctrineListener(event: Events::onFlush)]
|
#[AsDoctrineListener(event: Events::onFlush)]
|
||||||
final class PieceAuditSubscriber implements EventSubscriber
|
final class PieceAuditSubscriber implements EventSubscriber
|
||||||
{
|
{
|
||||||
public function __construct(private readonly RequestStack $requestStack)
|
public function __construct(
|
||||||
{
|
private readonly RequestStack $requestStack,
|
||||||
}
|
private readonly Security $security,
|
||||||
|
) {}
|
||||||
|
|
||||||
public function getSubscribedEvents(): array
|
public function getSubscribedEvents(): array
|
||||||
{
|
{
|
||||||
@@ -39,18 +51,18 @@ final class PieceAuditSubscriber implements EventSubscriber
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$uow = $em->getUnitOfWork();
|
$uow = $em->getUnitOfWork();
|
||||||
$actorProfileId = $this->resolveActorProfileId();
|
$actorProfileId = $this->resolveActorProfileId();
|
||||||
$pendingUpdates = [];
|
$pendingUpdates = [];
|
||||||
$pendingSnapshots = [];
|
$pendingSnapshots = [];
|
||||||
$pendingPieces = [];
|
$pendingPieces = [];
|
||||||
|
|
||||||
foreach ($uow->getScheduledEntityInsertions() as $entity) {
|
foreach ($uow->getScheduledEntityInsertions() as $entity) {
|
||||||
if (!$entity instanceof Piece) {
|
if (!$entity instanceof Piece) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$diff = $this->buildDiffFromChangeSet($uow->getEntityChangeSet($entity));
|
$diff = $this->buildDiffFromChangeSet($uow->getEntityChangeSet($entity));
|
||||||
$snapshot = $this->snapshotPiece($entity);
|
$snapshot = $this->snapshotPiece($entity);
|
||||||
$this->persistAuditLog($em, new AuditLog('piece', (string) $entity->getId(), 'create', $diff, $snapshot, $actorProfileId));
|
$this->persistAuditLog($em, new AuditLog('piece', (string) $entity->getId(), 'create', $diff, $snapshot, $actorProfileId));
|
||||||
}
|
}
|
||||||
@@ -61,15 +73,15 @@ final class PieceAuditSubscriber implements EventSubscriber
|
|||||||
}
|
}
|
||||||
|
|
||||||
$pieceId = (string) $entity->getId();
|
$pieceId = (string) $entity->getId();
|
||||||
if ($pieceId === '') {
|
if ('' === $pieceId) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$diff = $this->buildDiffFromChangeSet($uow->getEntityChangeSet($entity));
|
$diff = $this->buildDiffFromChangeSet($uow->getEntityChangeSet($entity));
|
||||||
if ($diff !== []) {
|
if ([] !== $diff) {
|
||||||
$pendingUpdates[$pieceId] = $this->mergeDiffs($pendingUpdates[$pieceId] ?? [], $diff);
|
$pendingUpdates[$pieceId] = $this->mergeDiffs($pendingUpdates[$pieceId] ?? [], $diff);
|
||||||
$pendingSnapshots[$pieceId] = $this->snapshotPiece($entity);
|
$pendingSnapshots[$pieceId] = $this->snapshotPiece($entity);
|
||||||
$pendingPieces[$pieceId] = $entity;
|
$pendingPieces[$pieceId] = $entity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,8 +101,10 @@ final class PieceAuditSubscriber implements EventSubscriber
|
|||||||
$this->collectCollectionUpdate($collection, $pendingUpdates, $pendingSnapshots, $pendingPieces);
|
$this->collectCollectionUpdate($collection, $pendingUpdates, $pendingSnapshots, $pendingPieces);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->collectCustomFieldValueChanges($uow, $pendingUpdates, $pendingSnapshots, $pendingPieces);
|
||||||
|
|
||||||
foreach ($pendingUpdates as $pieceId => $diff) {
|
foreach ($pendingUpdates as $pieceId => $diff) {
|
||||||
if ($diff === []) {
|
if ([] === $diff) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,8 +120,8 @@ final class PieceAuditSubscriber implements EventSubscriber
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<string, array<string, array{from:mixed, to:mixed}>> $pendingUpdates
|
* @param array<string, array<string, array{from:mixed, to:mixed}>> $pendingUpdates
|
||||||
* @param array<string, array<string, mixed>> $pendingSnapshots
|
* @param array<string, array<string, mixed>> $pendingSnapshots
|
||||||
* @param array<string, Piece> $pendingPieces
|
* @param array<string, Piece> $pendingPieces
|
||||||
*/
|
*/
|
||||||
private function collectCollectionUpdate(
|
private function collectCollectionUpdate(
|
||||||
object $collection,
|
object $collection,
|
||||||
@@ -125,18 +139,18 @@ final class PieceAuditSubscriber implements EventSubscriber
|
|||||||
}
|
}
|
||||||
|
|
||||||
$pieceId = (string) $owner->getId();
|
$pieceId = (string) $owner->getId();
|
||||||
if ($pieceId === '') {
|
if ('' === $pieceId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$mapping = $collection->getMapping();
|
$mapping = $collection->getMapping();
|
||||||
$fieldName = $mapping['fieldName'] ?? null;
|
$fieldName = $mapping['fieldName'] ?? null;
|
||||||
if ($fieldName !== 'constructeurs') {
|
if ('constructeurs' !== $fieldName) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$before = $this->normalizeCollection($collection->getSnapshot());
|
$before = $this->normalizeCollection($collection->getSnapshot());
|
||||||
$after = $this->normalizeCollection($collection->toArray());
|
$after = $this->normalizeCollection($collection->toArray());
|
||||||
|
|
||||||
if ($before === $after) {
|
if ($before === $after) {
|
||||||
return;
|
return;
|
||||||
@@ -145,13 +159,82 @@ final class PieceAuditSubscriber implements EventSubscriber
|
|||||||
$diff = [
|
$diff = [
|
||||||
'constructeurIds' => [
|
'constructeurIds' => [
|
||||||
'from' => $before,
|
'from' => $before,
|
||||||
'to' => $after,
|
'to' => $after,
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
$pendingUpdates[$pieceId] = $this->mergeDiffs($pendingUpdates[$pieceId] ?? [], $diff);
|
$pendingUpdates[$pieceId] = $this->mergeDiffs($pendingUpdates[$pieceId] ?? [], $diff);
|
||||||
$pendingSnapshots[$pieceId] = $this->snapshotPiece($owner);
|
$pendingSnapshots[$pieceId] = $this->snapshotPiece($owner);
|
||||||
$pendingPieces[$pieceId] = $owner;
|
$pendingPieces[$pieceId] = $owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, array<string, array{from:mixed, to:mixed}>> $pendingUpdates
|
||||||
|
* @param array<string, array<string, mixed>> $pendingSnapshots
|
||||||
|
* @param array<string, Piece> $pendingPieces
|
||||||
|
*/
|
||||||
|
private function collectCustomFieldValueChanges(
|
||||||
|
UnitOfWork $uow,
|
||||||
|
array &$pendingUpdates,
|
||||||
|
array &$pendingSnapshots,
|
||||||
|
array &$pendingPieces,
|
||||||
|
): void {
|
||||||
|
foreach ($uow->getScheduledEntityInsertions() as $entity) {
|
||||||
|
if ($entity instanceof CustomFieldValue) {
|
||||||
|
$this->trackCustomFieldValueChange($entity, null, $entity->getValue(), $pendingUpdates, $pendingSnapshots, $pendingPieces);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($uow->getScheduledEntityUpdates() as $entity) {
|
||||||
|
if (!$entity instanceof CustomFieldValue) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$changeSet = $uow->getEntityChangeSet($entity);
|
||||||
|
if (!isset($changeSet['value'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
[$oldVal, $newVal] = $changeSet['value'];
|
||||||
|
if ($oldVal !== $newVal) {
|
||||||
|
$this->trackCustomFieldValueChange($entity, $oldVal, $newVal, $pendingUpdates, $pendingSnapshots, $pendingPieces);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($uow->getScheduledEntityDeletions() as $entity) {
|
||||||
|
if ($entity instanceof CustomFieldValue) {
|
||||||
|
$this->trackCustomFieldValueChange($entity, $entity->getValue(), null, $pendingUpdates, $pendingSnapshots, $pendingPieces);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, array<string, array{from:mixed, to:mixed}>> $pendingUpdates
|
||||||
|
* @param array<string, array<string, mixed>> $pendingSnapshots
|
||||||
|
* @param array<string, Piece> $pendingPieces
|
||||||
|
*/
|
||||||
|
private function trackCustomFieldValueChange(
|
||||||
|
CustomFieldValue $cfv,
|
||||||
|
mixed $from,
|
||||||
|
mixed $to,
|
||||||
|
array &$pendingUpdates,
|
||||||
|
array &$pendingSnapshots,
|
||||||
|
array &$pendingPieces,
|
||||||
|
): void {
|
||||||
|
$owner = $cfv->getPiece();
|
||||||
|
if (!$owner instanceof Piece) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ownerId = (string) $owner->getId();
|
||||||
|
if ('' === $ownerId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fieldName = 'customField:'.$cfv->getCustomField()->getName();
|
||||||
|
$diff = [$fieldName => ['from' => $from, 'to' => $to]];
|
||||||
|
|
||||||
|
$pendingUpdates[$ownerId] = $this->mergeDiffs($pendingUpdates[$ownerId] ?? [], $diff);
|
||||||
|
$pendingSnapshots[$ownerId] = $this->snapshotPiece($owner);
|
||||||
|
$pendingPieces[$ownerId] = $owner;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function persistAuditLog(EntityManagerInterface $em, AuditLog $log): void
|
private function persistAuditLog(EntityManagerInterface $em, AuditLog $log): void
|
||||||
@@ -166,13 +249,14 @@ final class PieceAuditSubscriber implements EventSubscriber
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<string, array{0:mixed, 1:mixed}> $changeSet
|
* @param array<string, array{0:mixed, 1:mixed}> $changeSet
|
||||||
|
*
|
||||||
* @return array<string, array{from:mixed, to:mixed}>
|
* @return array<string, array{from:mixed, to:mixed}>
|
||||||
*/
|
*/
|
||||||
private function buildDiffFromChangeSet(array $changeSet): array
|
private function buildDiffFromChangeSet(array $changeSet): array
|
||||||
{
|
{
|
||||||
$diff = [];
|
$diff = [];
|
||||||
foreach ($changeSet as $field => [$oldValue, $newValue]) {
|
foreach ($changeSet as $field => [$oldValue, $newValue]) {
|
||||||
if ($field === 'updatedAt' || $field === 'createdAt') {
|
if ('updatedAt' === $field || 'createdAt' === $field) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +269,7 @@ final class PieceAuditSubscriber implements EventSubscriber
|
|||||||
|
|
||||||
$diff[$field] = [
|
$diff[$field] = [
|
||||||
'from' => $normalizedOld,
|
'from' => $normalizedOld,
|
||||||
'to' => $normalizedNew,
|
'to' => $normalizedNew,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,28 +279,29 @@ final class PieceAuditSubscriber implements EventSubscriber
|
|||||||
private function snapshotPiece(Piece $piece): array
|
private function snapshotPiece(Piece $piece): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'id' => $piece->getId(),
|
'id' => $piece->getId(),
|
||||||
'name' => $piece->getName(),
|
'name' => $piece->getName(),
|
||||||
'reference' => $piece->getReference(),
|
'reference' => $piece->getReference(),
|
||||||
'prix' => $piece->getPrix(),
|
'prix' => $piece->getPrix(),
|
||||||
'typePiece' => $this->normalizeValue($piece->getTypePiece()),
|
'typePiece' => $this->normalizeValue($piece->getTypePiece()),
|
||||||
'product' => $this->normalizeValue($piece->getProduct()),
|
'product' => $this->normalizeValue($piece->getProduct()),
|
||||||
'productIds' => $piece->getProductIds(),
|
'productIds' => $piece->getProductIds(),
|
||||||
'constructeurIds' => $this->normalizeCollection($piece->getConstructeurs()),
|
'constructeurIds' => $this->normalizeCollection($piece->getConstructeurs()),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param iterable<mixed> $items
|
* @param iterable<mixed> $items
|
||||||
|
*
|
||||||
* @return list<string>
|
* @return list<string>
|
||||||
*/
|
*/
|
||||||
private function normalizeCollection(iterable $items): array
|
private function normalizeCollection(iterable $items): array
|
||||||
{
|
{
|
||||||
$ids = [];
|
$ids = [];
|
||||||
foreach ($items as $item) {
|
foreach ($items as $item) {
|
||||||
if (\is_object($item) && \method_exists($item, 'getId')) {
|
if (is_object($item) && method_exists($item, 'getId')) {
|
||||||
$id = $item->getId();
|
$id = $item->getId();
|
||||||
if ($id !== null && $id !== '') {
|
if (null !== $id && '' !== $id) {
|
||||||
$ids[] = (string) $id;
|
$ids[] = (string) $id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -229,17 +314,17 @@ final class PieceAuditSubscriber implements EventSubscriber
|
|||||||
|
|
||||||
private function normalizeValue(mixed $value): mixed
|
private function normalizeValue(mixed $value): mixed
|
||||||
{
|
{
|
||||||
if ($value === null || \is_scalar($value)) {
|
if (null === $value || is_scalar($value)) {
|
||||||
return $value;
|
return $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($value instanceof \DateTimeInterface) {
|
if ($value instanceof DateTimeInterface) {
|
||||||
return $value->format(\DateTimeInterface::ATOM);
|
return $value->format(DateTimeInterface::ATOM);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($value instanceof ModelType) {
|
if ($value instanceof ModelType) {
|
||||||
return [
|
return [
|
||||||
'id' => $value->getId(),
|
'id' => $value->getId(),
|
||||||
'name' => $value->getName(),
|
'name' => $value->getName(),
|
||||||
'code' => $value->getCode(),
|
'code' => $value->getCode(),
|
||||||
];
|
];
|
||||||
@@ -247,8 +332,8 @@ final class PieceAuditSubscriber implements EventSubscriber
|
|||||||
|
|
||||||
if ($value instanceof Product) {
|
if ($value instanceof Product) {
|
||||||
return [
|
return [
|
||||||
'id' => $value->getId(),
|
'id' => $value->getId(),
|
||||||
'name' => $value->getName(),
|
'name' => $value->getName(),
|
||||||
'reference' => $value->getReference(),
|
'reference' => $value->getReference(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -257,11 +342,11 @@ final class PieceAuditSubscriber implements EventSubscriber
|
|||||||
return $this->normalizeCollection($value);
|
return $this->normalizeCollection($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (\is_object($value) && \method_exists($value, 'getId')) {
|
if (is_object($value) && method_exists($value, 'getId')) {
|
||||||
return (string) $value->getId();
|
return (string) $value->getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (\is_array($value)) {
|
if (is_array($value)) {
|
||||||
return $value;
|
return $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,6 +356,7 @@ final class PieceAuditSubscriber implements EventSubscriber
|
|||||||
/**
|
/**
|
||||||
* @param array<string, array{from:mixed, to:mixed}> $base
|
* @param array<string, array{from:mixed, to:mixed}> $base
|
||||||
* @param array<string, array{from:mixed, to:mixed}> $extra
|
* @param array<string, array{from:mixed, to:mixed}> $extra
|
||||||
|
*
|
||||||
* @return array<string, array{from:mixed, to:mixed}>
|
* @return array<string, array{from:mixed, to:mixed}>
|
||||||
*/
|
*/
|
||||||
private function mergeDiffs(array $base, array $extra): array
|
private function mergeDiffs(array $base, array $extra): array
|
||||||
@@ -284,17 +370,23 @@ final class PieceAuditSubscriber implements EventSubscriber
|
|||||||
|
|
||||||
private function resolveActorProfileId(): ?string
|
private function resolveActorProfileId(): ?string
|
||||||
{
|
{
|
||||||
$session = $this->requestStack->getSession();
|
try {
|
||||||
if (!$session instanceof SessionInterface) {
|
$session = $this->requestStack->getSession();
|
||||||
return null;
|
if ($session instanceof SessionInterface) {
|
||||||
|
$profileId = $session->get('profileId');
|
||||||
|
if ($profileId) {
|
||||||
|
return (string) $profileId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable) {
|
||||||
|
// No session available (CLI context, etc.)
|
||||||
}
|
}
|
||||||
|
|
||||||
$profileId = $session->get('profileId');
|
$user = $this->security->getUser();
|
||||||
if (!$profileId) {
|
if ($user instanceof Profile) {
|
||||||
return null;
|
return $user->getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (string) $profileId;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,11 @@ declare(strict_types=1);
|
|||||||
namespace App\EventSubscriber;
|
namespace App\EventSubscriber;
|
||||||
|
|
||||||
use App\Entity\AuditLog;
|
use App\Entity\AuditLog;
|
||||||
|
use App\Entity\CustomFieldValue;
|
||||||
use App\Entity\ModelType;
|
use App\Entity\ModelType;
|
||||||
use App\Entity\Product;
|
use App\Entity\Product;
|
||||||
|
use App\Entity\Profile;
|
||||||
|
use DateTimeInterface;
|
||||||
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
|
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\Common\EventSubscriber;
|
use Doctrine\Common\EventSubscriber;
|
||||||
@@ -14,8 +17,16 @@ use Doctrine\ORM\EntityManagerInterface;
|
|||||||
use Doctrine\ORM\Event\OnFlushEventArgs;
|
use Doctrine\ORM\Event\OnFlushEventArgs;
|
||||||
use Doctrine\ORM\Events;
|
use Doctrine\ORM\Events;
|
||||||
use Doctrine\ORM\PersistentCollection;
|
use Doctrine\ORM\PersistentCollection;
|
||||||
|
use Doctrine\ORM\UnitOfWork;
|
||||||
|
use Symfony\Bundle\SecurityBundle\Security;
|
||||||
use Symfony\Component\HttpFoundation\RequestStack;
|
use Symfony\Component\HttpFoundation\RequestStack;
|
||||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
use function is_array;
|
||||||
|
use function is_object;
|
||||||
|
use function is_scalar;
|
||||||
|
use function method_exists;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Record a lightweight, per-product audit trail.
|
* Record a lightweight, per-product audit trail.
|
||||||
@@ -27,9 +38,10 @@ use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
|||||||
#[AsDoctrineListener(event: Events::onFlush)]
|
#[AsDoctrineListener(event: Events::onFlush)]
|
||||||
final class ProductAuditSubscriber implements EventSubscriber
|
final class ProductAuditSubscriber implements EventSubscriber
|
||||||
{
|
{
|
||||||
public function __construct(private readonly RequestStack $requestStack)
|
public function __construct(
|
||||||
{
|
private readonly RequestStack $requestStack,
|
||||||
}
|
private readonly Security $security,
|
||||||
|
) {}
|
||||||
|
|
||||||
public function getSubscribedEvents(): array
|
public function getSubscribedEvents(): array
|
||||||
{
|
{
|
||||||
@@ -45,18 +57,18 @@ final class ProductAuditSubscriber implements EventSubscriber
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$uow = $em->getUnitOfWork();
|
$uow = $em->getUnitOfWork();
|
||||||
$actorProfileId = $this->resolveActorProfileId();
|
$actorProfileId = $this->resolveActorProfileId();
|
||||||
$pendingUpdates = [];
|
$pendingUpdates = [];
|
||||||
$pendingSnapshots = [];
|
$pendingSnapshots = [];
|
||||||
$pendingProducts = [];
|
$pendingProducts = [];
|
||||||
|
|
||||||
foreach ($uow->getScheduledEntityInsertions() as $entity) {
|
foreach ($uow->getScheduledEntityInsertions() as $entity) {
|
||||||
if (!$entity instanceof Product) {
|
if (!$entity instanceof Product) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$diff = $this->buildDiffFromChangeSet($uow->getEntityChangeSet($entity));
|
$diff = $this->buildDiffFromChangeSet($uow->getEntityChangeSet($entity));
|
||||||
$snapshot = $this->snapshotProduct($entity);
|
$snapshot = $this->snapshotProduct($entity);
|
||||||
$this->persistAuditLog($em, new AuditLog('product', (string) $entity->getId(), 'create', $diff, $snapshot, $actorProfileId));
|
$this->persistAuditLog($em, new AuditLog('product', (string) $entity->getId(), 'create', $diff, $snapshot, $actorProfileId));
|
||||||
}
|
}
|
||||||
@@ -67,15 +79,15 @@ final class ProductAuditSubscriber implements EventSubscriber
|
|||||||
}
|
}
|
||||||
|
|
||||||
$productId = (string) $entity->getId();
|
$productId = (string) $entity->getId();
|
||||||
if ($productId === '') {
|
if ('' === $productId) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$diff = $this->buildDiffFromChangeSet($uow->getEntityChangeSet($entity));
|
$diff = $this->buildDiffFromChangeSet($uow->getEntityChangeSet($entity));
|
||||||
if ($diff !== []) {
|
if ([] !== $diff) {
|
||||||
$pendingUpdates[$productId] = $this->mergeDiffs($pendingUpdates[$productId] ?? [], $diff);
|
$pendingUpdates[$productId] = $this->mergeDiffs($pendingUpdates[$productId] ?? [], $diff);
|
||||||
$pendingSnapshots[$productId] = $this->snapshotProduct($entity);
|
$pendingSnapshots[$productId] = $this->snapshotProduct($entity);
|
||||||
$pendingProducts[$productId] = $entity;
|
$pendingProducts[$productId] = $entity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,8 +108,10 @@ final class ProductAuditSubscriber implements EventSubscriber
|
|||||||
$this->collectCollectionUpdate($collection, $pendingUpdates, $pendingSnapshots, $pendingProducts);
|
$this->collectCollectionUpdate($collection, $pendingUpdates, $pendingSnapshots, $pendingProducts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->collectCustomFieldValueChanges($uow, $pendingUpdates, $pendingSnapshots, $pendingProducts);
|
||||||
|
|
||||||
foreach ($pendingUpdates as $productId => $diff) {
|
foreach ($pendingUpdates as $productId => $diff) {
|
||||||
if ($diff === []) {
|
if ([] === $diff) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,8 +127,8 @@ final class ProductAuditSubscriber implements EventSubscriber
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<string, array<string, array{from:mixed, to:mixed}>> $pendingUpdates
|
* @param array<string, array<string, array{from:mixed, to:mixed}>> $pendingUpdates
|
||||||
* @param array<string, array<string, mixed>> $pendingSnapshots
|
* @param array<string, array<string, mixed>> $pendingSnapshots
|
||||||
* @param array<string, Product> $pendingProducts
|
* @param array<string, Product> $pendingProducts
|
||||||
*/
|
*/
|
||||||
private function collectCollectionUpdate(
|
private function collectCollectionUpdate(
|
||||||
object $collection,
|
object $collection,
|
||||||
@@ -132,18 +146,18 @@ final class ProductAuditSubscriber implements EventSubscriber
|
|||||||
}
|
}
|
||||||
|
|
||||||
$productId = (string) $owner->getId();
|
$productId = (string) $owner->getId();
|
||||||
if ($productId === '') {
|
if ('' === $productId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$mapping = $collection->getMapping();
|
$mapping = $collection->getMapping();
|
||||||
$fieldName = $mapping['fieldName'] ?? null;
|
$fieldName = $mapping['fieldName'] ?? null;
|
||||||
if ($fieldName !== 'constructeurs') {
|
if ('constructeurs' !== $fieldName) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$before = $this->normalizeCollection($collection->getSnapshot());
|
$before = $this->normalizeCollection($collection->getSnapshot());
|
||||||
$after = $this->normalizeCollection($collection->toArray());
|
$after = $this->normalizeCollection($collection->toArray());
|
||||||
|
|
||||||
if ($before === $after) {
|
if ($before === $after) {
|
||||||
return;
|
return;
|
||||||
@@ -152,13 +166,82 @@ final class ProductAuditSubscriber implements EventSubscriber
|
|||||||
$diff = [
|
$diff = [
|
||||||
'constructeurIds' => [
|
'constructeurIds' => [
|
||||||
'from' => $before,
|
'from' => $before,
|
||||||
'to' => $after,
|
'to' => $after,
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
$pendingUpdates[$productId] = $this->mergeDiffs($pendingUpdates[$productId] ?? [], $diff);
|
$pendingUpdates[$productId] = $this->mergeDiffs($pendingUpdates[$productId] ?? [], $diff);
|
||||||
$pendingSnapshots[$productId] = $this->snapshotProduct($owner);
|
$pendingSnapshots[$productId] = $this->snapshotProduct($owner);
|
||||||
$pendingProducts[$productId] = $owner;
|
$pendingProducts[$productId] = $owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, array<string, array{from:mixed, to:mixed}>> $pendingUpdates
|
||||||
|
* @param array<string, array<string, mixed>> $pendingSnapshots
|
||||||
|
* @param array<string, Product> $pendingProducts
|
||||||
|
*/
|
||||||
|
private function collectCustomFieldValueChanges(
|
||||||
|
UnitOfWork $uow,
|
||||||
|
array &$pendingUpdates,
|
||||||
|
array &$pendingSnapshots,
|
||||||
|
array &$pendingProducts,
|
||||||
|
): void {
|
||||||
|
foreach ($uow->getScheduledEntityInsertions() as $entity) {
|
||||||
|
if ($entity instanceof CustomFieldValue) {
|
||||||
|
$this->trackCustomFieldValueChange($entity, null, $entity->getValue(), $pendingUpdates, $pendingSnapshots, $pendingProducts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($uow->getScheduledEntityUpdates() as $entity) {
|
||||||
|
if (!$entity instanceof CustomFieldValue) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$changeSet = $uow->getEntityChangeSet($entity);
|
||||||
|
if (!isset($changeSet['value'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
[$oldVal, $newVal] = $changeSet['value'];
|
||||||
|
if ($oldVal !== $newVal) {
|
||||||
|
$this->trackCustomFieldValueChange($entity, $oldVal, $newVal, $pendingUpdates, $pendingSnapshots, $pendingProducts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($uow->getScheduledEntityDeletions() as $entity) {
|
||||||
|
if ($entity instanceof CustomFieldValue) {
|
||||||
|
$this->trackCustomFieldValueChange($entity, $entity->getValue(), null, $pendingUpdates, $pendingSnapshots, $pendingProducts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, array<string, array{from:mixed, to:mixed}>> $pendingUpdates
|
||||||
|
* @param array<string, array<string, mixed>> $pendingSnapshots
|
||||||
|
* @param array<string, Product> $pendingProducts
|
||||||
|
*/
|
||||||
|
private function trackCustomFieldValueChange(
|
||||||
|
CustomFieldValue $cfv,
|
||||||
|
mixed $from,
|
||||||
|
mixed $to,
|
||||||
|
array &$pendingUpdates,
|
||||||
|
array &$pendingSnapshots,
|
||||||
|
array &$pendingProducts,
|
||||||
|
): void {
|
||||||
|
$owner = $cfv->getProduct();
|
||||||
|
if (!$owner instanceof Product) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ownerId = (string) $owner->getId();
|
||||||
|
if ('' === $ownerId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fieldName = 'customField:'.$cfv->getCustomField()->getName();
|
||||||
|
$diff = [$fieldName => ['from' => $from, 'to' => $to]];
|
||||||
|
|
||||||
|
$pendingUpdates[$ownerId] = $this->mergeDiffs($pendingUpdates[$ownerId] ?? [], $diff);
|
||||||
|
$pendingSnapshots[$ownerId] = $this->snapshotProduct($owner);
|
||||||
|
$pendingProducts[$ownerId] = $owner;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function persistAuditLog(EntityManagerInterface $em, AuditLog $log): void
|
private function persistAuditLog(EntityManagerInterface $em, AuditLog $log): void
|
||||||
@@ -174,6 +257,7 @@ final class ProductAuditSubscriber implements EventSubscriber
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<string, array{0:mixed, 1:mixed}> $changeSet
|
* @param array<string, array{0:mixed, 1:mixed}> $changeSet
|
||||||
|
*
|
||||||
* @return array<string, array{from:mixed, to:mixed}>
|
* @return array<string, array{from:mixed, to:mixed}>
|
||||||
*/
|
*/
|
||||||
private function buildDiffFromChangeSet(array $changeSet): array
|
private function buildDiffFromChangeSet(array $changeSet): array
|
||||||
@@ -181,7 +265,7 @@ final class ProductAuditSubscriber implements EventSubscriber
|
|||||||
$diff = [];
|
$diff = [];
|
||||||
foreach ($changeSet as $field => [$oldValue, $newValue]) {
|
foreach ($changeSet as $field => [$oldValue, $newValue]) {
|
||||||
// Skip noisy timestamps managed automatically.
|
// Skip noisy timestamps managed automatically.
|
||||||
if ($field === 'updatedAt' || $field === 'createdAt') {
|
if ('updatedAt' === $field || 'createdAt' === $field) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,7 +278,7 @@ final class ProductAuditSubscriber implements EventSubscriber
|
|||||||
|
|
||||||
$diff[$field] = [
|
$diff[$field] = [
|
||||||
'from' => $normalizedOld,
|
'from' => $normalizedOld,
|
||||||
'to' => $normalizedNew,
|
'to' => $normalizedNew,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,11 +288,11 @@ final class ProductAuditSubscriber implements EventSubscriber
|
|||||||
private function snapshotProduct(Product $product): array
|
private function snapshotProduct(Product $product): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'id' => $product->getId(),
|
'id' => $product->getId(),
|
||||||
'name' => $product->getName(),
|
'name' => $product->getName(),
|
||||||
'reference' => $product->getReference(),
|
'reference' => $product->getReference(),
|
||||||
'supplierPrice' => $product->getSupplierPrice(),
|
'supplierPrice' => $product->getSupplierPrice(),
|
||||||
'typeProduct' => $this->normalizeValue($product->getTypeProduct()),
|
'typeProduct' => $this->normalizeValue($product->getTypeProduct()),
|
||||||
'constructeurIds' => $this->normalizeCollection($product->getConstructeurs()),
|
'constructeurIds' => $this->normalizeCollection($product->getConstructeurs()),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -216,6 +300,7 @@ final class ProductAuditSubscriber implements EventSubscriber
|
|||||||
/**
|
/**
|
||||||
* @param array<string, array{from:mixed, to:mixed}> $base
|
* @param array<string, array{from:mixed, to:mixed}> $base
|
||||||
* @param array<string, array{from:mixed, to:mixed}> $extra
|
* @param array<string, array{from:mixed, to:mixed}> $extra
|
||||||
|
*
|
||||||
* @return array<string, array{from:mixed, to:mixed}>
|
* @return array<string, array{from:mixed, to:mixed}>
|
||||||
*/
|
*/
|
||||||
private function mergeDiffs(array $base, array $extra): array
|
private function mergeDiffs(array $base, array $extra): array
|
||||||
@@ -229,15 +314,16 @@ final class ProductAuditSubscriber implements EventSubscriber
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param iterable<mixed> $items
|
* @param iterable<mixed> $items
|
||||||
|
*
|
||||||
* @return list<string>
|
* @return list<string>
|
||||||
*/
|
*/
|
||||||
private function normalizeCollection(iterable $items): array
|
private function normalizeCollection(iterable $items): array
|
||||||
{
|
{
|
||||||
$ids = [];
|
$ids = [];
|
||||||
foreach ($items as $item) {
|
foreach ($items as $item) {
|
||||||
if (\is_object($item) && \method_exists($item, 'getId')) {
|
if (is_object($item) && method_exists($item, 'getId')) {
|
||||||
$id = $item->getId();
|
$id = $item->getId();
|
||||||
if ($id !== null && $id !== '') {
|
if (null !== $id && '' !== $id) {
|
||||||
$ids[] = (string) $id;
|
$ids[] = (string) $id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -250,17 +336,17 @@ final class ProductAuditSubscriber implements EventSubscriber
|
|||||||
|
|
||||||
private function normalizeValue(mixed $value): mixed
|
private function normalizeValue(mixed $value): mixed
|
||||||
{
|
{
|
||||||
if ($value === null || \is_scalar($value)) {
|
if (null === $value || is_scalar($value)) {
|
||||||
return $value;
|
return $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($value instanceof \DateTimeInterface) {
|
if ($value instanceof DateTimeInterface) {
|
||||||
return $value->format(\DateTimeInterface::ATOM);
|
return $value->format(DateTimeInterface::ATOM);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($value instanceof ModelType) {
|
if ($value instanceof ModelType) {
|
||||||
return [
|
return [
|
||||||
'id' => $value->getId(),
|
'id' => $value->getId(),
|
||||||
'name' => $value->getName(),
|
'name' => $value->getName(),
|
||||||
'code' => $value->getCode(),
|
'code' => $value->getCode(),
|
||||||
];
|
];
|
||||||
@@ -270,11 +356,11 @@ final class ProductAuditSubscriber implements EventSubscriber
|
|||||||
return $this->normalizeCollection($value);
|
return $this->normalizeCollection($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (\is_object($value) && \method_exists($value, 'getId')) {
|
if (is_object($value) && method_exists($value, 'getId')) {
|
||||||
return (string) $value->getId();
|
return (string) $value->getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (\is_array($value)) {
|
if (is_array($value)) {
|
||||||
return $value;
|
return $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,16 +369,23 @@ final class ProductAuditSubscriber implements EventSubscriber
|
|||||||
|
|
||||||
private function resolveActorProfileId(): ?string
|
private function resolveActorProfileId(): ?string
|
||||||
{
|
{
|
||||||
$session = $this->requestStack->getSession();
|
try {
|
||||||
if (!$session instanceof SessionInterface) {
|
$session = $this->requestStack->getSession();
|
||||||
return null;
|
if ($session instanceof SessionInterface) {
|
||||||
|
$profileId = $session->get('profileId');
|
||||||
|
if ($profileId) {
|
||||||
|
return (string) $profileId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable) {
|
||||||
|
// No session available (CLI context, etc.)
|
||||||
}
|
}
|
||||||
|
|
||||||
$profileId = $session->get('profileId');
|
$user = $this->security->getUser();
|
||||||
if (!$profileId) {
|
if ($user instanceof Profile) {
|
||||||
return null;
|
return $user->getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (string) $profileId;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user