refactoring, save context in late hooks

This commit is contained in:
Yurii
2026-03-03 20:47:03 +02:00
parent 04794bb2aa
commit 3375ca3181
4 changed files with 94 additions and 45 deletions

View File

@@ -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.

View File

@@ -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.");
}
}

View File

@@ -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<string, mixed> */
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<string, mixed> $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<string, mixed> $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<string, mixed> $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<string, mixed> $options
*/
private function removeInternal(Entity $entity, array $options = []): void
{
parent::remove($entity, $options);
}
/**
* @deprecated Do not extend. Use hooks.
*

View File

@@ -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<string, mixed> $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<string, mixed> $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<string, mixed> $options
*/
protected function lateAfterRemove(Entity $entity, array $options): void
{}
/**
* @param TEntity $entity
* @param array<string, mixed> $options
*/
private function removeInternal(Entity $entity, array $options = []): void
{
$this->processCheckEntity($entity);
$this->beforeRemove($entity, $options);