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

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