vendor/knplabs/doctrine-behaviors/src/EventSubscriber/BlameableEventSubscriber.php line 139

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace Knp\DoctrineBehaviors\EventSubscriber;
  4. use Doctrine\Bundle\DoctrineBundle\EventSubscriber\EventSubscriberInterface;
  5. use Doctrine\ORM\EntityManagerInterface;
  6. use Doctrine\ORM\Event\LifecycleEventArgs;
  7. use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
  8. use Doctrine\ORM\Events;
  9. use Doctrine\ORM\Mapping\ClassMetadataInfo;
  10. use Doctrine\ORM\UnitOfWork;
  11. use Knp\DoctrineBehaviors\Contract\Entity\BlameableInterface;
  12. use Knp\DoctrineBehaviors\Contract\Provider\UserProviderInterface;
  13. final class BlameableEventSubscriber implements EventSubscriberInterface
  14. {
  15.     /**
  16.      * @var string
  17.      */
  18.     private const DELETED_BY 'deletedBy';
  19.     /**
  20.      * @var string
  21.      */
  22.     private const UPDATED_BY 'updatedBy';
  23.     /**
  24.      * @var string
  25.      */
  26.     private const CREATED_BY 'createdBy';
  27.     public function __construct(
  28.         private UserProviderInterface $userProvider,
  29.         private EntityManagerInterface $entityManager,
  30.         private ?string $blameableUserEntity null
  31.     ) {
  32.     }
  33.     /**
  34.      * Adds metadata about how to store user, either a string or an ManyToOne association on user entity
  35.      */
  36.     public function loadClassMetadata(LoadClassMetadataEventArgs $loadClassMetadataEventArgs): void
  37.     {
  38.         $classMetadata $loadClassMetadataEventArgs->getClassMetadata();
  39.         if ($classMetadata->reflClass === null) {
  40.             // Class has not yet been fully built, ignore this event
  41.             return;
  42.         }
  43.         if (! is_a($classMetadata->reflClass->getName(), BlameableInterface::class, true)) {
  44.             return;
  45.         }
  46.         $this->mapEntity($classMetadata);
  47.     }
  48.     /**
  49.      * Stores the current user into createdBy and updatedBy properties
  50.      */
  51.     public function prePersist(LifecycleEventArgs $lifecycleEventArgs): void
  52.     {
  53.         $entity $lifecycleEventArgs->getEntity();
  54.         if (! $entity instanceof BlameableInterface) {
  55.             return;
  56.         }
  57.         $user $this->userProvider->provideUser();
  58.         // no user set → skip
  59.         if ($user === null) {
  60.             return;
  61.         }
  62.         if (! $entity->getCreatedBy()) {
  63.             $entity->setCreatedBy($user);
  64.             $this->getUnitOfWork()
  65.                 ->propertyChanged($entityself::CREATED_BYnull$user);
  66.         }
  67.         if (! $entity->getUpdatedBy()) {
  68.             $entity->setUpdatedBy($user);
  69.             $this->getUnitOfWork()
  70.                 ->propertyChanged($entityself::UPDATED_BYnull$user);
  71.         }
  72.     }
  73.     /**
  74.      * Stores the current user into updatedBy property
  75.      */
  76.     public function preUpdate(LifecycleEventArgs $lifecycleEventArgs): void
  77.     {
  78.         $entity $lifecycleEventArgs->getEntity();
  79.         if (! $entity instanceof BlameableInterface) {
  80.             return;
  81.         }
  82.         $user $this->userProvider->provideUser();
  83.         if ($user === null) {
  84.             return;
  85.         }
  86.         $oldValue $entity->getUpdatedBy();
  87.         $entity->setUpdatedBy($user);
  88.         $this->getUnitOfWork()
  89.             ->propertyChanged($entityself::UPDATED_BY$oldValue$user);
  90.     }
  91.     /**
  92.      * Stores the current user into deletedBy property
  93.      */
  94.     public function preRemove(LifecycleEventArgs $lifecycleEventArgs): void
  95.     {
  96.         $entity $lifecycleEventArgs->getEntity();
  97.         if (! $entity instanceof BlameableInterface) {
  98.             return;
  99.         }
  100.         $user $this->userProvider->provideUser();
  101.         if ($user === null) {
  102.             return;
  103.         }
  104.         $oldValue $entity->getDeletedBy();
  105.         $entity->setDeletedBy($user);
  106.         $this->getUnitOfWork()
  107.             ->propertyChanged($entityself::DELETED_BY$oldValue$user);
  108.     }
  109.     public function getSubscribedEvents()
  110.     {
  111.         return [Events::prePersistEvents::preUpdateEvents::preRemoveEvents::loadClassMetadata];
  112.     }
  113.     private function mapEntity(ClassMetadataInfo $classMetadataInfo): void
  114.     {
  115.         if ($this->blameableUserEntity !== null && class_exists($this->blameableUserEntity)) {
  116.             $this->mapManyToOneUser($classMetadataInfo);
  117.         } else {
  118.             $this->mapStringUser($classMetadataInfo);
  119.         }
  120.     }
  121.     private function getUnitOfWork(): UnitOfWork
  122.     {
  123.         return $this->entityManager->getUnitOfWork();
  124.     }
  125.     private function mapManyToOneUser(ClassMetadataInfo $classMetadataInfo): void
  126.     {
  127.         $this->mapManyToOneWithTargetEntity($classMetadataInfoself::CREATED_BY);
  128.         $this->mapManyToOneWithTargetEntity($classMetadataInfoself::UPDATED_BY);
  129.         $this->mapManyToOneWithTargetEntity($classMetadataInfoself::DELETED_BY);
  130.     }
  131.     private function mapStringUser(ClassMetadataInfo $classMetadataInfo): void
  132.     {
  133.         $this->mapStringNullableField($classMetadataInfoself::CREATED_BY);
  134.         $this->mapStringNullableField($classMetadataInfoself::UPDATED_BY);
  135.         $this->mapStringNullableField($classMetadataInfoself::DELETED_BY);
  136.     }
  137.     private function mapManyToOneWithTargetEntity(ClassMetadataInfo $classMetadataInfostring $fieldName): void
  138.     {
  139.         if ($classMetadataInfo->hasAssociation($fieldName)) {
  140.             return;
  141.         }
  142.         $classMetadataInfo->mapManyToOne([
  143.             'fieldName' => $fieldName,
  144.             'targetEntity' => $this->blameableUserEntity,
  145.             'joinColumns' => [
  146.                 [
  147.                     'onDelete' => 'SET NULL',
  148.                 ],
  149.             ],
  150.         ]);
  151.     }
  152.     private function mapStringNullableField(ClassMetadataInfo $classMetadataInfostring $fieldName): void
  153.     {
  154.         if ($classMetadataInfo->hasField($fieldName)) {
  155.             return;
  156.         }
  157.         $classMetadataInfo->mapField([
  158.             'fieldName' => $fieldName,
  159.             'type' => 'string',
  160.             'nullable' => true,
  161.         ]);
  162.     }
  163. }