diff --git a/Helper/AttributeHelper.php b/Helper/AttributeHelper.php index 26a6bfb..9d5066d 100644 --- a/Helper/AttributeHelper.php +++ b/Helper/AttributeHelper.php @@ -5,6 +5,7 @@ namespace FRZB\Component\TransactionalMessenger\Helper; use Fp\Collections\ArrayList; +use Fp\Functional\Option\Option; use JetBrains\PhpStorm\Immutable; /** @internal */ @@ -59,10 +60,13 @@ public static function getAttributes(string|object $target, string $attributeCla */ public static function getReflectionAttributes(string|object $target, string $attributeClass): array { - try { - return (new \ReflectionClass($target))->getAttributes($attributeClass); - } catch (\ReflectionException) { - return []; - } + return Option::fromNullable(ClassHelper::getReflectionClass($target)) + ->map( + static fn (\ReflectionClass $rClass) => Option::fromNullable(ClassHelper::getParentReflectionClass($rClass)) + ->map(static fn (\ReflectionClass $rClass) => [...ClassHelper::getReflectionAttributes($rClass, $attributeClass), ...self::getReflectionAttributes($rClass, $attributeClass)]) + ->getOrElse(ClassHelper::getReflectionAttributes($rClass, $attributeClass)) + ) + ->getOrElse([]) + ; } } diff --git a/Helper/ClassHelper.php b/Helper/ClassHelper.php index 1f27585..fb7e620 100644 --- a/Helper/ClassHelper.php +++ b/Helper/ClassHelper.php @@ -17,11 +17,47 @@ private function __construct() } public static function getShortName(string|object $target): string + { + return self::getReflectionClass($target)?->getShortName() ?? self::DEFAULT_SHORT_NAME; + } + + /** + * @template T + * + * @param class-string|T $target + * + * @return null|\ReflectionClass + */ + public static function getReflectionClass(string|object $target): ?\ReflectionClass { try { - return (new \ReflectionClass($target))->getShortName(); + return $target instanceof \ReflectionClass ? $target : new \ReflectionClass($target); } catch (\ReflectionException) { - return self::DEFAULT_SHORT_NAME; + return null; } } + + /** + * @template T + * + * @param class-string|T $target + * + * @return null|\ReflectionClass + */ + public static function getParentReflectionClass(string|object $target): ?\ReflectionClass + { + return self::getReflectionClass($target)?->getParentClass() ?: null; + } + + /** + * @template T + * + * @param class-string $attributeClass + * + * @return \Iterator<\ReflectionAttribute> + */ + public static function getReflectionAttributes(string|object $target, string $attributeClass): iterable + { + return self::getReflectionClass($target)?->getAttributes($attributeClass, \ReflectionAttribute::IS_INSTANCEOF) ?? []; + } } diff --git a/Helper/TransactionHelper.php b/Helper/TransactionHelper.php index 5f58455..0784579 100644 --- a/Helper/TransactionHelper.php +++ b/Helper/TransactionHelper.php @@ -7,7 +7,6 @@ use Fp\Collections\ArrayList; use FRZB\Component\TransactionalMessenger\Attribute\Transactional; use FRZB\Component\TransactionalMessenger\Enum\CommitType; -use FRZB\Component\TransactionalMessenger\ValueObject\PendingEnvelope; use JetBrains\PhpStorm\Immutable; /** @internal */ @@ -28,7 +27,7 @@ public static function getTransactional(string|object $target): array return AttributeHelper::getAttributes($target, Transactional::class); } - public static function isDispatchAllowed(string|object $target, CommitType ...$commitTypes): bool + public static function isDispatchable(string|object $target, CommitType ...$commitTypes): bool { return ArrayList::collect(self::getTransactional($target)) ->map(static fn (Transactional $t) => $t->commitTypes) diff --git a/MessageBus/TransactionalMessageBus.php b/MessageBus/TransactionalMessageBus.php index 9baabba..da50f8b 100644 --- a/MessageBus/TransactionalMessageBus.php +++ b/MessageBus/TransactionalMessageBus.php @@ -97,7 +97,7 @@ private function dispatchPendingEnvelopes(CommitType ...$commitTypes): void $notAllowedForDispatchEnvelopes = new StorageImpl(); while ($pendingEnvelope = $this->pendingStorage->next()) { - TransactionHelper::isDispatchAllowed($pendingEnvelope->getMessageClass(), ...$commitTypes) + TransactionHelper::isDispatchable($pendingEnvelope->getMessageClass(), ...$commitTypes) ? $this->dispatchEnvelope($pendingEnvelope->envelope) : $notAllowedForDispatchEnvelopes->prepend($pendingEnvelope) ; diff --git a/Tests/Stub/Message/AbstractTransactionalMessage.php b/Tests/Stub/Message/AbstractTransactionalMessage.php new file mode 100644 index 0000000..40f1c60 --- /dev/null +++ b/Tests/Stub/Message/AbstractTransactionalMessage.php @@ -0,0 +1,14 @@ + [ 'class_name' => TransactionalOnTerminateMessage::class, - 'short_class_name' => 'TransactionalOnTerminateMessage', + 'has_attributes' => true, ]; yield sprintf('%s', ClassHelper::getShortName(TransactionalOnResponseMessage::class)) => [ 'class_name' => TransactionalOnResponseMessage::class, - 'short_class_name' => 'TransactionalOnResponseMessage', + 'has_attributes' => true, ]; yield sprintf('%s', ClassHelper::getShortName(TransactionalOnHandledMessage::class)) => [ 'class_name' => TransactionalOnHandledMessage::class, - 'short_class_name' => 'TransactionalOnHandledMessage', + 'has_attributes' => true, + ]; + + yield sprintf('%s', ClassHelper::getShortName(ExtendedTransactionalMessage::class)) => [ + 'class_name' => ExtendedTransactionalMessage::class, + 'has_attributes' => true, ]; yield sprintf('%s', ClassHelper::getShortName(NonTransactionalMessage::class)) => [ 'class_name' => NonTransactionalMessage::class, - 'short_class_name' => 'NonTransactionalMessage', + 'has_attributes' => false, ]; yield 'InvalidClassName' => [ 'class_name' => 'InvalidClassName', - 'short_class_name' => 'InvalidClassName', + 'has_attributes' => false, ]; } } diff --git a/Tests/Unit/Helper/ClassHelperTest.php b/Tests/Unit/Helper/ClassHelperTest.php index 614e545..f952910 100644 --- a/Tests/Unit/Helper/ClassHelperTest.php +++ b/Tests/Unit/Helper/ClassHelperTest.php @@ -2,14 +2,11 @@ declare(strict_types=1); - namespace FRZB\Component\TransactionalMessenger\Tests\Unit\Helper; use FRZB\Component\TransactionalMessenger\Attribute\Transactional; -use FRZB\Component\TransactionalMessenger\Enum\CommitType; -use FRZB\Component\TransactionalMessenger\Helper\AttributeHelper; use FRZB\Component\TransactionalMessenger\Helper\ClassHelper; -use FRZB\Component\TransactionalMessenger\Helper\TransactionHelper; +use FRZB\Component\TransactionalMessenger\Tests\Stub\Message\ExtendedTransactionalMessage; use FRZB\Component\TransactionalMessenger\Tests\Stub\Message\NonTransactionalMessage; use FRZB\Component\TransactionalMessenger\Tests\Stub\Message\TransactionalOnHandledMessage; use FRZB\Component\TransactionalMessenger\Tests\Stub\Message\TransactionalOnResponseMessage; @@ -22,65 +19,163 @@ #[Group('transactional-messenger')] final class ClassHelperTest extends TestCase { - #[DataProvider('dataProvider')] - public function testGetAttributesMethod(string $className, bool $hasAttributes): void + #[DataProvider('shortNameProvider')] + public function testGetShortNameMethod(string $className, string $shortClassName): void { - $hasAttributes - ? self::assertNotEmpty(AttributeHelper::getAttributes($className, Transactional::class)) - : self::assertEmpty(AttributeHelper::getAttributes($className, Transactional::class)) - ; + self::assertSame($shortClassName, ClassHelper::getShortName($className)); } - #[DataProvider('dataProvider')] - public function testGetAttributeMethod(string $className, bool $hasAttributes): void + #[DataProvider('reflectionProvider')] + public function testGetReflectionClassMethod(string $className, bool $isNull): void { - $hasAttributes - ? self::assertNotNull(AttributeHelper::getAttribute($className, Transactional::class)) - : self::assertNull(AttributeHelper::getAttribute($className, Transactional::class)) + $isNull + ? self::assertNull(ClassHelper::getReflectionClass($className)) + : self::assertNotNull(ClassHelper::getReflectionClass($className)) ; } - #[DataProvider('dataProvider')] - public function testGetReflectionAttributesMethod(string $className, bool $hasAttributes): void + #[DataProvider('parentReflectionProvider')] + public function testGetParentReflectionClassMethod(string $className, bool $isNull): void { - $hasAttributes - ? self::assertNotEmpty(AttributeHelper::getReflectionAttributes($className, Transactional::class)) - : self::assertEmpty(AttributeHelper::getReflectionAttributes($className, Transactional::class)) + $isNull + ? self::assertNull(ClassHelper::getParentReflectionClass($className)) + : self::assertNotNull(ClassHelper::getParentReflectionClass($className)) ; } - #[DataProvider('dataProvider')] - public function testHasAttributeMethod(string $className, bool $hasAttributes): void + #[DataProvider('reflectionAttributesProvider')] + public function testGetReflectionAttributesClassMethod(string $className, bool $isEmpty): void { - self::assertSame($hasAttributes, AttributeHelper::hasAttribute($className, Transactional::class)) + $isEmpty + ? self::assertEmpty(ClassHelper::getReflectionAttributes($className, Transactional::class)) + : self::assertNotEmpty(ClassHelper::getReflectionAttributes($className, Transactional::class)) ; } - public function dataProvider(): iterable + public function shortNameProvider(): iterable + { + yield sprintf('%s', ClassHelper::getShortName(TransactionalOnTerminateMessage::class)) => [ + 'class_name' => TransactionalOnTerminateMessage::class, + 'short_class_name' => 'TransactionalOnTerminateMessage', + ]; + + yield sprintf('%s', ClassHelper::getShortName(TransactionalOnResponseMessage::class)) => [ + 'class_name' => TransactionalOnResponseMessage::class, + 'short_class_name' => 'TransactionalOnResponseMessage', + ]; + + yield sprintf('%s', ClassHelper::getShortName(TransactionalOnHandledMessage::class)) => [ + 'class_name' => TransactionalOnHandledMessage::class, + 'short_class_name' => 'TransactionalOnHandledMessage', + ]; + + yield sprintf('%s', ClassHelper::getShortName(NonTransactionalMessage::class)) => [ + 'class_name' => NonTransactionalMessage::class, + 'short_class_name' => 'NonTransactionalMessage', + ]; + + yield 'InvalidClassName' => [ + 'class_name' => 'InvalidClassName', + 'short_class_name' => 'InvalidClassName', + ]; + } + + public function reflectionProvider(): iterable + { + yield sprintf('%s', ClassHelper::getShortName(TransactionalOnTerminateMessage::class)) => [ + 'class_name' => TransactionalOnTerminateMessage::class, + 'is_null' => false, + ]; + + yield sprintf('%s', ClassHelper::getShortName(TransactionalOnResponseMessage::class)) => [ + 'class_name' => TransactionalOnResponseMessage::class, + 'is_null' => false, + ]; + + yield sprintf('%s', ClassHelper::getShortName(TransactionalOnHandledMessage::class)) => [ + 'class_name' => TransactionalOnHandledMessage::class, + 'is_null' => false, + ]; + + yield sprintf('%s', ClassHelper::getShortName(ExtendedTransactionalMessage::class)) => [ + 'class_name' => ExtendedTransactionalMessage::class, + 'is_null' => false, + ]; + + yield sprintf('%s', ClassHelper::getShortName(NonTransactionalMessage::class)) => [ + 'class_name' => NonTransactionalMessage::class, + 'is_null' => false, + ]; + + yield 'InvalidClassName' => [ + 'class_name' => 'InvalidClassName', + 'is_null' => true, + ]; + } + + public function parentReflectionProvider(): iterable { yield sprintf('%s', ClassHelper::getShortName(TransactionalOnTerminateMessage::class)) => [ 'class_name' => TransactionalOnTerminateMessage::class, - 'has_attributes' => true, + 'is_null' => true, ]; yield sprintf('%s', ClassHelper::getShortName(TransactionalOnResponseMessage::class)) => [ 'class_name' => TransactionalOnResponseMessage::class, - 'has_attributes' => true, + 'is_null' => true, ]; yield sprintf('%s', ClassHelper::getShortName(TransactionalOnHandledMessage::class)) => [ 'class_name' => TransactionalOnHandledMessage::class, - 'has_attributes' => true, + 'is_null' => true, + ]; + + yield sprintf('%s', ClassHelper::getShortName(ExtendedTransactionalMessage::class)) => [ + 'class_name' => ExtendedTransactionalMessage::class, + 'is_null' => false, + ]; + + yield sprintf('%s', ClassHelper::getShortName(NonTransactionalMessage::class)) => [ + 'class_name' => NonTransactionalMessage::class, + 'is_null' => true, + ]; + + yield 'InvalidClassName' => [ + 'class_name' => 'InvalidClassName', + 'is_null' => true, + ]; + } + + public function reflectionAttributesProvider(): iterable + { + yield sprintf('%s', ClassHelper::getShortName(TransactionalOnTerminateMessage::class)) => [ + 'class_name' => TransactionalOnTerminateMessage::class, + 'is_empty' => false, + ]; + + yield sprintf('%s', ClassHelper::getShortName(TransactionalOnResponseMessage::class)) => [ + 'class_name' => TransactionalOnResponseMessage::class, + 'is_empty' => false, + ]; + + yield sprintf('%s', ClassHelper::getShortName(TransactionalOnHandledMessage::class)) => [ + 'class_name' => TransactionalOnHandledMessage::class, + 'is_empty' => false, + ]; + + yield sprintf('%s', ClassHelper::getShortName(ExtendedTransactionalMessage::class)) => [ + 'class_name' => ExtendedTransactionalMessage::class, + 'is_empty' => true, ]; yield sprintf('%s', ClassHelper::getShortName(NonTransactionalMessage::class)) => [ 'class_name' => NonTransactionalMessage::class, - 'has_attributes' => false, + 'is_empty' => true, ]; yield 'InvalidClassName' => [ 'class_name' => 'InvalidClassName', - 'has_attributes' => false, + 'is_empty' => true, ]; } } diff --git a/Tests/Unit/Helper/TransactionHelperTest.php b/Tests/Unit/Helper/TransactionHelperTest.php index 3d2f026..0028ff0 100644 --- a/Tests/Unit/Helper/TransactionHelperTest.php +++ b/Tests/Unit/Helper/TransactionHelperTest.php @@ -2,7 +2,6 @@ declare(strict_types=1); - namespace FRZB\Component\TransactionalMessenger\Tests\Unit\Helper; use FRZB\Component\TransactionalMessenger\Enum\CommitType; @@ -20,7 +19,7 @@ #[Group('transactional-messenger')] final class TransactionHelperTest extends TestCase { - #[DataProvider('transactionalDataProvider')] + #[DataProvider('transactionalProvider')] public function testIsTransactionalMethod(string $className, bool $isTransactional): void { self::assertSame($isTransactional, TransactionHelper::isTransactional($className)); @@ -35,13 +34,13 @@ public function getTransactionalMethod(string $className, bool $isTransactional) ; } - #[DataProvider('allowedDataProvider')] - public function getIsDispatchAllowedMethod(string $className, bool $isAllowed, array $commitTypes): void + #[DataProvider('dispatchableProvider')] + public function getIsDispatchableMethod(string $className, bool $isAllowed, array $commitTypes): void { - self::assertSame($isAllowed, TransactionHelper::isDispatchAllowed($className, ...$commitTypes)); + self::assertSame($isAllowed, TransactionHelper::isDispatchable($className, ...$commitTypes)); } - public function transactionalDataProvider(): iterable + public function transactionalProvider(): iterable { yield sprintf('%s', ClassHelper::getShortName(TransactionalOnTerminateMessage::class)) => [ 'class_name' => TransactionalOnTerminateMessage::class, @@ -69,7 +68,7 @@ public function transactionalDataProvider(): iterable ]; } - public function allowedDataProvider(): iterable + public function dispatchableProvider(): iterable { yield sprintf('%s is allowed', ClassHelper::getShortName(TransactionalOnTerminateMessage::class)) => [ 'class_name' => TransactionalOnTerminateMessage::class,