diff --git a/application/Espo/Core/Hook/Hook/LateAfterSave.php b/application/Espo/Core/Hook/Hook/LateAfterSave.php index 1cefc987d2..fc8f169d34 100644 --- a/application/Espo/Core/Hook/Hook/LateAfterSave.php +++ b/application/Espo/Core/Hook/Hook/LateAfterSave.php @@ -43,6 +43,8 @@ interface LateAfterSave { /** * Processed after an entity is saved, after the transaction (if one is used). + * To check whether the entity was new before save, obtain the save context + * from the options and call `isNew`. * * @param TEntity $entity An entity. * @param SaveOptions $options Options. diff --git a/application/Espo/Core/ORM/Repository/Option/SaveContext.php b/application/Espo/Core/ORM/Repository/Option/SaveContext.php index 1647495944..d1c7fb4c16 100644 --- a/application/Espo/Core/ORM/Repository/Option/SaveContext.php +++ b/application/Espo/Core/ORM/Repository/Option/SaveContext.php @@ -32,6 +32,7 @@ namespace Espo\Core\ORM\Repository\Option; use Closure; use Espo\Core\Utils\Util; use Espo\ORM\Repository\Option\SaveOptions; +use LogicException; /** * A save context. @@ -47,6 +48,7 @@ class SaveContext private string $actionId; private bool $linkUpdated = false; + private ?bool $isNew = null; /** @var Closure[] */ private array $deferredActions = []; @@ -159,4 +161,28 @@ class SaveContext { return new self($this->actionId); } + + /** + * @internal + * @since 9.4.0 + */ + public function setIsNew(bool $isNew): void + { + if ($this->isNew !== null) { + throw new LogicException("Cannot set already set isNew."); + } + + $this->isNew = $isNew; + } + + /** + * Was the entity new before save. Can be accessed only after save is started. + * To be used for late hooks, when then entity is already not new, to check. + * + * @since 9.4.0 + */ + public function isNew(): bool + { + return $this->isNew ?? throw new LogicException("Cannot access isNew before it's set."); + } } diff --git a/application/Espo/Core/Repositories/Database.php b/application/Espo/Core/Repositories/Database.php index c94f947eb4..4b1f773549 100644 --- a/application/Espo/Core/Repositories/Database.php +++ b/application/Espo/Core/Repositories/Database.php @@ -69,13 +69,6 @@ class Database extends RDBRepository */ protected $hooksDisabled = false; - /** - * To save and remove in a DB transaction. - * - * @since 9.4.0 - */ - protected bool $transactionalSave = false; - /** @var ?array */ private $restoreData = null; /** @var Metadata */ @@ -136,22 +129,16 @@ class Database extends RDBRepository */ public function save(Entity $entity, array $options = []): void { - if ($this->transactionalSave) { - $this->entityManager->getTransactionManager()->run(function () use ($entity, $options) { - $this->saveInternal($entity, $options); - }); - } else { - $this->saveInternal($entity, $options); - } + $this->prepareSaveInternal($entity, $options); - $this->lateAfterSave($entity, $options); + parent::save($entity, $options); } /** * @param TEntity $entity * @param array $options */ - private function saveInternal(Entity $entity, array $options = []): void + private function prepareSaveInternal(Entity $entity, array $options = []): void { if ( $entity->isNew() && @@ -166,57 +153,30 @@ class Database extends RDBRepository } $this->restoreData = []; - - parent::save($entity, $options); } /** * @param TEntity $entity * @param array $options */ - private function lateAfterSave(Entity $entity, array $options): void + final protected function lateAfterSave(Entity $entity, array $options): void { if (!$this->hooksDisabled && empty($options[SaveOption::SKIP_HOOKS])) { $this->hookManager->process($this->entityType, 'lateAfterSave', $entity, $options); } } - /** - * Remove a record (mark as deleted). - */ - public function remove(Entity $entity, array $options = []): void - { - if ($this->transactionalSave) { - $this->entityManager->getTransactionManager()->run(function () use ($entity, $options) { - $this->removeInternal($entity, $options); - }); - } else { - $this->removeInternal($entity, $options); - } - - $this->lateAfterRemove($entity, $options); - } - /** * @param TEntity $entity * @param array $options */ - private function lateAfterRemove(Entity $entity, array $options): void + final protected function lateAfterRemove(Entity $entity, array $options): void { if (!$this->hooksDisabled && empty($options[SaveOption::SKIP_HOOKS])) { $this->hookManager->process($this->entityType, 'lateAfterRemove', $entity, $options); } } - /** - * @param TEntity $entity - * @param array $options - */ - private function removeInternal(Entity $entity, array $options = []): void - { - parent::remove($entity, $options); - } - /** * @deprecated Do not extend. Use hooks. * diff --git a/application/Espo/ORM/Repository/RDBRepository.php b/application/Espo/ORM/Repository/RDBRepository.php index dcbc260773..bbc41d9a41 100644 --- a/application/Espo/ORM/Repository/RDBRepository.php +++ b/application/Espo/ORM/Repository/RDBRepository.php @@ -67,6 +67,13 @@ class RDBRepository implements Repository protected HookMediator $hookMediator; protected RDBTransactionManager $transactionManager; + /** + * To save and remove in a DB transaction. + * + * @since 9.4.0 + */ + protected bool $transactionalSave = false; + public function __construct( protected string $entityType, protected EntityManager $entityManager, @@ -137,6 +144,29 @@ class RDBRepository implements Repository $options[SaveContext::NAME] = new SaveContext(); } + $context = $options[SaveContext::NAME]; + + if ($context instanceof SaveContext) { + $context->setIsNew($entity->isNew()); + } + + if ($this->transactionalSave) { + $this->entityManager->getTransactionManager()->run(function () use ($entity, $options) { + $this->saveInternal($entity, $options); + }); + } else { + $this->saveInternal($entity, $options); + } + + $this->lateAfterSave($entity, $options); + } + + /** + * @param TEntity $entity + * @param array $options + */ + private function saveInternal(Entity $entity, array $options = []): void + { $this->processCheckEntity($entity); if ($entity instanceof BaseEntity) { @@ -191,6 +221,13 @@ class RDBRepository implements Repository } } + /** + * @param TEntity $entity + * @param array $options + */ + protected function lateAfterSave(Entity $entity, array $options): void + {} + /** * Restore a record flagged as deleted. */ @@ -220,6 +257,30 @@ class RDBRepository implements Repository * Remove a record (mark as deleted). */ public function remove(Entity $entity, array $options = []): void + { + if ($this->transactionalSave) { + $this->entityManager->getTransactionManager()->run(function () use ($entity, $options) { + $this->removeInternal($entity, $options); + }); + } else { + $this->removeInternal($entity, $options); + } + + $this->lateAfterRemove($entity, $options); + } + + /** + * @param TEntity $entity + * @param array $options + */ + protected function lateAfterRemove(Entity $entity, array $options): void + {} + + /** + * @param TEntity $entity + * @param array $options + */ + private function removeInternal(Entity $entity, array $options = []): void { $this->processCheckEntity($entity); $this->beforeRemove($entity, $options);