From 83ef7b32b5c91f5e903e5d00d41bf617d2fcac40 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Mon, 26 Dec 2022 14:18:51 +0200 Subject: [PATCH] hook interfaces --- application/Espo/Core/Hook/GeneralInvoker.php | 188 ++++++++++++++++++ .../Espo/Core/Hook/Hook/AfterMassRelate.php | 59 ++++++ .../Espo/Core/Hook/Hook/AfterRelate.php | 58 ++++++ .../Espo/Core/Hook/Hook/AfterRemove.php | 49 +++++ application/Espo/Core/Hook/Hook/AfterSave.php | 49 +++++ .../Espo/Core/Hook/Hook/AfterUnrelate.php | 56 ++++++ .../Espo/Core/Hook/Hook/BeforeRemove.php | 49 +++++ .../Espo/Core/Hook/Hook/BeforeSave.php | 49 +++++ application/Espo/Core/HookManager.php | 30 ++- application/Espo/Hooks/Common/Stream.php | 76 +++---- .../Repository/Option/MassRelateOptions.php | 42 ++++ .../ORM/Repository/Option/RelateOptions.php | 42 ++++ .../ORM/Repository/Option/RemoveOptions.php | 42 ++++ .../ORM/Repository/Option/SaveOptions.php | 42 ++++ .../ORM/Repository/Option/Traits/Options.php | 100 ++++++++++ .../ORM/Repository/Option/UnrelateOptions.php | 42 ++++ 16 files changed, 932 insertions(+), 41 deletions(-) create mode 100644 application/Espo/Core/Hook/GeneralInvoker.php create mode 100644 application/Espo/Core/Hook/Hook/AfterMassRelate.php create mode 100644 application/Espo/Core/Hook/Hook/AfterRelate.php create mode 100644 application/Espo/Core/Hook/Hook/AfterRemove.php create mode 100644 application/Espo/Core/Hook/Hook/AfterSave.php create mode 100644 application/Espo/Core/Hook/Hook/AfterUnrelate.php create mode 100644 application/Espo/Core/Hook/Hook/BeforeRemove.php create mode 100644 application/Espo/Core/Hook/Hook/BeforeSave.php create mode 100644 application/Espo/ORM/Repository/Option/MassRelateOptions.php create mode 100644 application/Espo/ORM/Repository/Option/RelateOptions.php create mode 100644 application/Espo/ORM/Repository/Option/RemoveOptions.php create mode 100644 application/Espo/ORM/Repository/Option/SaveOptions.php create mode 100644 application/Espo/ORM/Repository/Option/Traits/Options.php create mode 100644 application/Espo/ORM/Repository/Option/UnrelateOptions.php diff --git a/application/Espo/Core/Hook/GeneralInvoker.php b/application/Espo/Core/Hook/GeneralInvoker.php new file mode 100644 index 0000000000..419fead726 --- /dev/null +++ b/application/Espo/Core/Hook/GeneralInvoker.php @@ -0,0 +1,188 @@ + $options Options. + * @param array $hookData Additional data. + */ + public function invoke( + object $hook, + string $name, + mixed $subject, + array $options, + array $hookData + ): void { + + if ($name === self::HOOK_BEFORE_SAVE && $hook instanceof BeforeSave) { + if (!$subject instanceof Entity) { + throw new LogicException(); + } + + $hook->beforeSave($subject, SaveOptions::fromAssoc($options)); + + return; + } + + if ($name === self::HOOK_AFTER_SAVE && $hook instanceof AfterSave) { + if (!$subject instanceof Entity) { + throw new LogicException(); + } + + $hook->afterSave($subject, SaveOptions::fromAssoc($options)); + + return; + } + + if ($name === self::HOOK_BEFORE_REMOVE && $hook instanceof BeforeRemove) { + if (!$subject instanceof Entity) { + throw new LogicException(); + } + + $hook->beforeRemove($subject, RemoveOptions::fromAssoc($options)); + + return; + } + + if ($name === self::HOOK_AFTER_REMOVE && $hook instanceof AfterRemove) { + if (!$subject instanceof Entity) { + throw new LogicException(); + } + + $hook->afterRemove($subject, RemoveOptions::fromAssoc($options)); + + return; + } + + if ($name === self::HOOK_AFTER_RELATE && $hook instanceof AfterRelate) { + $relationName = $hookData['relationName'] ?? null; + $relatedEntity = $hookData['foreignEntity'] ?? null; + $columnData = $hookData['relationData'] ?? []; + + if ( + !$subject instanceof Entity || + !is_string($relationName) || + !$relatedEntity instanceof Entity + ) { + throw new LogicException(); + } + + $hook->afterRelate( + $subject, + $relationName, + $relatedEntity, + $columnData, + RelateOptions::fromAssoc($options) + ); + + return; + } + + if ($name === self::HOOK_AFTER_UNRELATE && $hook instanceof AfterUnrelate) { + $relationName = $hookData['relationName'] ?? null; + $relatedEntity = $hookData['foreignEntity'] ?? null; + + if ( + !$subject instanceof Entity || + !is_string($relationName) || + !$relatedEntity instanceof Entity + ) { + throw new LogicException(); + } + + $hook->afterUnrelate( + $subject, + $relationName, + $relatedEntity, + UnrelateOptions::fromAssoc($options) + ); + + return; + } + + if ($name === self::HOOK_AFTER_MASS_RELATE && $hook instanceof AfterMassRelate) { + $relationName = $hookData['relationName'] ?? null; + $query = $hookData['query'] ?? null; + $columnData = $hookData['relationData'] ?? []; // Not implemented currently. + + if ( + !$subject instanceof Entity || + !is_string($relationName) || + !$query instanceof Select + ) { + throw new LogicException(); + } + + $hook->afterMassRelate( + $subject, + $relationName, + $query, + $columnData, + MassRelateOptions::fromAssoc($options) + ); + + return; + } + + $hook->$name($subject, $options, $hookData); + } +} diff --git a/application/Espo/Core/Hook/Hook/AfterMassRelate.php b/application/Espo/Core/Hook/Hook/AfterMassRelate.php new file mode 100644 index 0000000000..6433bf781b --- /dev/null +++ b/application/Espo/Core/Hook/Hook/AfterMassRelate.php @@ -0,0 +1,59 @@ + $columnData Middle table role values. + * @param MassRelateOptions $options Options. + */ + public function afterMassRelate( + Entity $entity, + string $relationName, + Select $query, + array $columnData, + MassRelateOptions $options + ): void; +} diff --git a/application/Espo/Core/Hook/Hook/AfterRelate.php b/application/Espo/Core/Hook/Hook/AfterRelate.php new file mode 100644 index 0000000000..d41f74bce0 --- /dev/null +++ b/application/Espo/Core/Hook/Hook/AfterRelate.php @@ -0,0 +1,58 @@ + $columnData Middle table role values. + * @param RelateOptions $options Options. + */ + public function afterRelate( + Entity $entity, + string $relationName, + Entity $relatedEntity, + array $columnData, + RelateOptions $options + ): void; +} diff --git a/application/Espo/Core/Hook/Hook/AfterRemove.php b/application/Espo/Core/Hook/Hook/AfterRemove.php new file mode 100644 index 0000000000..d5d659fbbf --- /dev/null +++ b/application/Espo/Core/Hook/Hook/AfterRemove.php @@ -0,0 +1,49 @@ + $options - * @param array $hookData + * @param string $scope A scope (entity type). + * @param string $hookName A hook name. + * @param mixed $injection A subject (usually an entity). + * @param array $options Options. + * @param array $hookData Additional hook data. */ public function process( string $scope, string $hookName, - $injection = null, + mixed $injection = null, array $options = [], array $hookData = [] ): void { @@ -105,7 +115,13 @@ class HookManager $hook = $this->hooks[$className]; - $hook->$hookName($injection, $options, $hookData); + $this->generalInvoker->invoke( + $hook, + $hookName, + $injection, + $options, + $hookData + ); } } diff --git a/application/Espo/Hooks/Common/Stream.php b/application/Espo/Hooks/Common/Stream.php index d9a9248362..8c464aa327 100644 --- a/application/Espo/Hooks/Common/Stream.php +++ b/application/Espo/Hooks/Common/Stream.php @@ -29,67 +29,75 @@ namespace Espo\Hooks\Common; +use Espo\Core\Hook\Hook\AfterRelate; +use Espo\Core\Hook\Hook\AfterRemove; +use Espo\Core\Hook\Hook\AfterSave; +use Espo\Core\Hook\Hook\AfterUnrelate; use Espo\Core\ORM\Repository\Option\SaveOption; use Espo\ORM\Entity; +use Espo\ORM\Repository\Option\RelateOptions; +use Espo\ORM\Repository\Option\RemoveOptions; +use Espo\ORM\Repository\Option\SaveOptions; +use Espo\ORM\Repository\Option\UnrelateOptions; use Espo\Tools\Stream\HookProcessor; -class Stream +/** + * @implements AfterSave + * @implements AfterRemove + * @implements AfterRelate + * @implements AfterUnrelate + */ +class Stream implements AfterSave, AfterRemove, AfterRelate, AfterUnrelate { public static int $order = 9; - private HookProcessor $processor; + public function __construct(private HookProcessor $processor) + {} - public function __construct(HookProcessor $processor) + public function afterSave(Entity $entity, SaveOptions $options): void { - $this->processor = $processor; - } - - /** - * @param array $options - */ - public function afterSave(Entity $entity, array $options): void - { - if (!empty($options[SaveOption::SILENT])) { + if ($options->get(SaveOption::SILENT)) { return; } - $this->processor->afterSave($entity, $options); + $this->processor->afterSave($entity, $options->toAssoc()); } - public function afterRemove(Entity $entity): void + public function afterRemove(Entity $entity, RemoveOptions $options): void { + if ($options->get(SaveOption::SILENT)) { + return; + } + $this->processor->afterRemove($entity); } - /** - * @param array $options - * @param array $data - */ - public function afterRelate(Entity $entity, array $options, array $data): void - { - $link = $data['relationName'] ?? null; - $foreignEntity = $data['foreignEntity'] ?? null; + public function afterRelate( + Entity $entity, + string $relationName, + Entity $relatedEntity, + array $columnData, + RelateOptions $options + ): void { - if (!$link || !$foreignEntity instanceof Entity) { + if ($options->get(SaveOption::SILENT)) { return; } - $this->processor->afterRelate($entity, $foreignEntity, $link, $options); + $this->processor->afterRelate($entity, $relatedEntity, $relationName, $options->toAssoc()); } - /** - * @param array $options - * @param array $data - */ - public function afterUnrelate(Entity $entity, array $options, array $data): void - { - $link = $data['relationName'] ?? null; - $foreignEntity = $data['foreignEntity'] ?? null; + public function afterUnrelate( + Entity $entity, + string $relationName, + Entity $relatedEntity, + UnrelateOptions $options + ): void { - if (!$link || !$foreignEntity instanceof Entity) { + if ($options->get(SaveOption::SILENT)) { return; } - $this->processor->afterUnrelate($entity, $foreignEntity, $link, $options); + $this->processor->afterUnrelate($entity, $relatedEntity, $relationName, $options->toAssoc()); } } diff --git a/application/Espo/ORM/Repository/Option/MassRelateOptions.php b/application/Espo/ORM/Repository/Option/MassRelateOptions.php new file mode 100644 index 0000000000..50aef94c53 --- /dev/null +++ b/application/Espo/ORM/Repository/Option/MassRelateOptions.php @@ -0,0 +1,42 @@ + */ + private array $options; + + /** + * @param array $options + */ + private function __construct(array $options) + { + $this->options = $options; + } + + /** + * Create from an associative array. + * + * @param array $options + */ + public static function fromAssoc(array $options): self + { + return new self($options); + } + + /** + * Get an option value. Returns `null` if not set. + */ + public function get(string $option): mixed + { + return $this->options[$option] ?? null; + } + + /** + * Whether an option is set. + */ + public function has(string $option): bool + { + return array_key_exists($option, $this->options); + } + + /** + * Clone with an option value. + */ + public function with(string $option, mixed $value): self + { + $obj = clone $this; + $obj->options[$option] = $value; + + return $obj; + } + + /** + * Clone with an option removed. + */ + public function without(string $option): self + { + $obj = clone $this; + unset($obj->options[$option]); + + return $obj; + } + + /** + * @return array + */ + public function toAssoc(): array + { + return $this->options; + } +} diff --git a/application/Espo/ORM/Repository/Option/UnrelateOptions.php b/application/Espo/ORM/Repository/Option/UnrelateOptions.php new file mode 100644 index 0000000000..1c5a940b2b --- /dev/null +++ b/application/Espo/ORM/Repository/Option/UnrelateOptions.php @@ -0,0 +1,42 @@ +