From f399bc912d4a7ee2af2ee59f2be968f87e0e9057 Mon Sep 17 00:00:00 2001 From: Younes ENNAJI Date: Mon, 2 Mar 2026 03:23:45 +0000 Subject: [PATCH] Update NotificationFactoryLocator and CHANGELOG --- CHANGELOG.md | 1 + .../Factory/NotificationFactoryLocator.php | 16 ++++- .../NotificationFactoryLocatorTest.php | 61 +++++++++++++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17ba8eec..c0e4cce2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ - [Laravel] Add LivewireListener classes for all adapters and themes to enable Livewire event handling * fix [Flasher] Fix FilterCriteria uninitialized property error when constructed with empty array * fix [Flasher] Fix null comparison issues in PriorityCriteria, HopsCriteria, and DelayCriteria that relied on PHP's implicit null-to-0 coercion +* fix [Flasher] Add type validation for callable factory return values in NotificationFactoryLocator with descriptive error messages ## [v2.1.3](https://github.com/php-flasher/php-flasher/compare/v2.1.2...v2.1.3) - 2025-01-25 diff --git a/src/Prime/Factory/NotificationFactoryLocator.php b/src/Prime/Factory/NotificationFactoryLocator.php index 0f5685e3..9b09c78f 100644 --- a/src/Prime/Factory/NotificationFactoryLocator.php +++ b/src/Prime/Factory/NotificationFactoryLocator.php @@ -15,6 +15,7 @@ final class NotificationFactoryLocator implements NotificationFactoryLocatorInte /** * @throws FactoryNotFoundException + * @throws \InvalidArgumentException */ public function get(string $id): NotificationFactoryInterface { @@ -24,7 +25,20 @@ final class NotificationFactoryLocator implements NotificationFactoryLocatorInte $factory = $this->factories[$id]; - return \is_callable($factory) ? $factory() : $factory; + if (\is_callable($factory)) { + $factory = $factory(); + + if (!$factory instanceof NotificationFactoryInterface) { + throw new \InvalidArgumentException(\sprintf( + 'Factory callable for "%s" must return an instance of %s, %s returned.', + $id, + NotificationFactoryInterface::class, + \get_debug_type($factory) + )); + } + } + + return $factory; } public function has(string $id): bool diff --git a/tests/Prime/Factory/NotificationFactoryLocatorTest.php b/tests/Prime/Factory/NotificationFactoryLocatorTest.php index f5bc6bfa..55f116f6 100644 --- a/tests/Prime/Factory/NotificationFactoryLocatorTest.php +++ b/tests/Prime/Factory/NotificationFactoryLocatorTest.php @@ -53,4 +53,65 @@ final class NotificationFactoryLocatorTest extends TestCase $this->assertTrue($notificationFactoryLocator->has('alias')); } + + public function testGetWithCallableFactory(): void + { + $factoryMock = \Mockery::mock(NotificationFactoryInterface::class); + $notificationFactoryLocator = new NotificationFactoryLocator(); + $notificationFactoryLocator->addFactory('alias', fn () => $factoryMock); + + $retrievedFactory = $notificationFactoryLocator->get('alias'); + + $this->assertSame($factoryMock, $retrievedFactory); + } + + public function testGetWithCallableFactoryCalledEachTime(): void + { + $callCount = 0; + $notificationFactoryLocator = new NotificationFactoryLocator(); + $notificationFactoryLocator->addFactory('alias', function () use (&$callCount) { + ++$callCount; + + return \Mockery::mock(NotificationFactoryInterface::class); + }); + + $notificationFactoryLocator->get('alias'); + $notificationFactoryLocator->get('alias'); + + $this->assertSame(2, $callCount); + } + + public function testGetWithCallableReturningInvalidTypeThrowsException(): void + { + $notificationFactoryLocator = new NotificationFactoryLocator(); + $notificationFactoryLocator->addFactory('invalid', fn () => 'not a factory'); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Factory callable for "invalid" must return an instance of'); + + $notificationFactoryLocator->get('invalid'); + } + + public function testGetWithCallableReturningNullThrowsException(): void + { + $notificationFactoryLocator = new NotificationFactoryLocator(); + $notificationFactoryLocator->addFactory('null_factory', fn () => null); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Factory callable for "null_factory" must return an instance of'); + + $notificationFactoryLocator->get('null_factory'); + } + + public function testAddFactoryOverwritesExistingFactory(): void + { + $factory1 = \Mockery::mock(NotificationFactoryInterface::class); + $factory2 = \Mockery::mock(NotificationFactoryInterface::class); + + $notificationFactoryLocator = new NotificationFactoryLocator(); + $notificationFactoryLocator->addFactory('alias', $factory1); + $notificationFactoryLocator->addFactory('alias', $factory2); + + $this->assertSame($factory2, $notificationFactoryLocator->get('alias')); + } }