diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier.php index 73beb2c346698..fde0533140809 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier.php @@ -22,6 +22,7 @@ use Symfony\Component\Notifier\ChatterInterface; use Symfony\Component\Notifier\EventListener\NotificationLoggerListener; use Symfony\Component\Notifier\EventListener\SendFailedMessageToNotifierListener; +use Symfony\Component\Notifier\FlashMessage\DefaultFlashMessageImportanceMapper; use Symfony\Component\Notifier\Message\ChatMessage; use Symfony\Component\Notifier\Message\PushMessage; use Symfony\Component\Notifier\Message\SmsMessage; @@ -43,8 +44,11 @@ ->set('notifier.channel_policy', ChannelPolicy::class) ->args([[]]) + ->set('notifier.flash_message_importance_mapper', DefaultFlashMessageImportanceMapper::class) + ->args([[]]) + ->set('notifier.channel.browser', BrowserChannel::class) - ->args([service('request_stack')]) + ->args([service('request_stack'), service('notifier.flash_message_importance_mapper')]) ->tag('notifier.channel', ['channel' => 'browser']) ->set('notifier.channel.chat', ChatChannel::class) diff --git a/src/Symfony/Component/Notifier/CHANGELOG.md b/src/Symfony/Component/Notifier/CHANGELOG.md index 5e353ec5cc437..0eeef9f6216b3 100644 --- a/src/Symfony/Component/Notifier/CHANGELOG.md +++ b/src/Symfony/Component/Notifier/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.1 +--- + + * Use importance level to set flash message type + 5.4 --- diff --git a/src/Symfony/Component/Notifier/Channel/BrowserChannel.php b/src/Symfony/Component/Notifier/Channel/BrowserChannel.php index b4a7c52442634..28e74e93a3651 100644 --- a/src/Symfony/Component/Notifier/Channel/BrowserChannel.php +++ b/src/Symfony/Component/Notifier/Channel/BrowserChannel.php @@ -12,6 +12,8 @@ namespace Symfony\Component\Notifier\Channel; use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Notifier\FlashMessage\DefaultFlashMessageImportanceMapper; +use Symfony\Component\Notifier\FlashMessage\FlashMessageImportanceMapperInterface; use Symfony\Component\Notifier\Notification\Notification; use Symfony\Component\Notifier\Recipient\RecipientInterface; @@ -22,9 +24,12 @@ final class BrowserChannel implements ChannelInterface { private RequestStack $stack; - public function __construct(RequestStack $stack) + private FlashMessageImportanceMapperInterface $mapper; + + public function __construct(RequestStack $stack, FlashMessageImportanceMapperInterface $mapper = new DefaultFlashMessageImportanceMapper()) { $this->stack = $stack; + $this->mapper = $mapper; } public function notify(Notification $notification, RecipientInterface $recipient, string $transportName = null): void @@ -37,7 +42,7 @@ public function notify(Notification $notification, RecipientInterface $recipient if ($notification->getEmoji()) { $message = $notification->getEmoji().' '.$message; } - $request->getSession()->getFlashBag()->add('notification', $message); + $request->getSession()->getFlashBag()->add($this->mapper->flashMessageTypeFromImportance($notification->getImportance()), $message); } public function supports(Notification $notification, RecipientInterface $recipient): bool diff --git a/src/Symfony/Component/Notifier/Exception/FlashMessageMappingException.php b/src/Symfony/Component/Notifier/Exception/FlashMessageMappingException.php new file mode 100644 index 0000000000000..07d5ae60483a0 --- /dev/null +++ b/src/Symfony/Component/Notifier/Exception/FlashMessageMappingException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Exception; + +/** + * @author Ben Roberts + */ +class FlashMessageImportanceMapperException extends LogicException +{ + public function __construct(string $importance, string $mappingClass) + { + $message = sprintf('The "%s" Notifier flash message mapper does not support an importance value of "%s".', $mappingClass, $importance); + + parent::__construct($message); + } +} diff --git a/src/Symfony/Component/Notifier/FlashMessage/AbstractFlashMessageImportanceMapper.php b/src/Symfony/Component/Notifier/FlashMessage/AbstractFlashMessageImportanceMapper.php new file mode 100644 index 0000000000000..5ea2fca427cb7 --- /dev/null +++ b/src/Symfony/Component/Notifier/FlashMessage/AbstractFlashMessageImportanceMapper.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\FlashMessage; + +use Symfony\Component\Notifier\Exception\FlashMessageImportanceMapperException; + +/** + * @author Ben Roberts + */ +abstract class AbstractFlashMessageImportanceMapper +{ + public function flashMessageTypeFromImportance(string $importance): string + { + if (!\array_key_exists($importance, static::IMPORTANCE_MAP)) { + throw new FlashMessageImportanceMapperException($importance, static::class); + } + + return static::IMPORTANCE_MAP[$importance]; + } +} diff --git a/src/Symfony/Component/Notifier/FlashMessage/BootstrapFlashMessageImportanceMapper.php b/src/Symfony/Component/Notifier/FlashMessage/BootstrapFlashMessageImportanceMapper.php new file mode 100644 index 0000000000000..2e7fe0546cc3a --- /dev/null +++ b/src/Symfony/Component/Notifier/FlashMessage/BootstrapFlashMessageImportanceMapper.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\FlashMessage; + +use Symfony\Component\Notifier\Notification\Notification; + +/** + * @author Ben Roberts + */ +class BootstrapFlashMessageImportanceMapper extends AbstractFlashMessageImportanceMapper implements FlashMessageImportanceMapperInterface +{ + protected const IMPORTANCE_MAP = [ + Notification::IMPORTANCE_URGENT => 'danger', + Notification::IMPORTANCE_HIGH => 'warning', + Notification::IMPORTANCE_MEDIUM => 'info', + Notification::IMPORTANCE_LOW => 'success', + ]; +} diff --git a/src/Symfony/Component/Notifier/FlashMessage/DefaultFlashMessageImportanceMapper.php b/src/Symfony/Component/Notifier/FlashMessage/DefaultFlashMessageImportanceMapper.php new file mode 100644 index 0000000000000..ce86a3a64ed4b --- /dev/null +++ b/src/Symfony/Component/Notifier/FlashMessage/DefaultFlashMessageImportanceMapper.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\FlashMessage; + +use Symfony\Component\Notifier\Notification\Notification; + +/** + * @author Ben Roberts + */ +class DefaultFlashMessageImportanceMapper extends AbstractFlashMessageImportanceMapper implements FlashMessageImportanceMapperInterface +{ + protected const IMPORTANCE_MAP = [ + Notification::IMPORTANCE_URGENT => 'notification', + Notification::IMPORTANCE_HIGH => 'notification', + Notification::IMPORTANCE_MEDIUM => 'notification', + Notification::IMPORTANCE_LOW => 'notification', + ]; +} diff --git a/src/Symfony/Component/Notifier/FlashMessage/FlashMessageImportanceMapperInterface.php b/src/Symfony/Component/Notifier/FlashMessage/FlashMessageImportanceMapperInterface.php new file mode 100644 index 0000000000000..e903ed8c45302 --- /dev/null +++ b/src/Symfony/Component/Notifier/FlashMessage/FlashMessageImportanceMapperInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\FlashMessage; + +use Symfony\Component\Notifier\Exception\FlashMessageImportanceMapperException; + +/** + * @author Ben Roberts + */ +interface FlashMessageImportanceMapperInterface +{ + /** + * @throws FlashMessageImportanceMapperException + */ + public function flashMessageTypeFromImportance(string $importance): string; +} diff --git a/src/Symfony/Component/Notifier/Tests/Channel/BrowserChannelTest.php b/src/Symfony/Component/Notifier/Tests/Channel/BrowserChannelTest.php new file mode 100644 index 0000000000000..610ade7c158e8 --- /dev/null +++ b/src/Symfony/Component/Notifier/Tests/Channel/BrowserChannelTest.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Tests\Channel; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\Notifier\Channel\BrowserChannel; +use Symfony\Component\Notifier\Exception\FlashMessageImportanceMapperException; +use Symfony\Component\Notifier\FlashMessage\BootstrapFlashMessageImportanceMapper; +use Symfony\Component\Notifier\FlashMessage\DefaultFlashMessageImportanceMapper; +use Symfony\Component\Notifier\FlashMessage\FlashMessageImportanceMapperInterface; +use Symfony\Component\Notifier\Notification\Notification; +use Symfony\Component\Notifier\Recipient\Recipient; + +/** + * @author Ben Roberts + */ +class BrowserChannelTest extends TestCase +{ + /** + * @dataProvider defaultFlashMessageImportanceDataProvider + */ + public function testImportanceLevelIsReflectedInFlashMessageType( + FlashMessageImportanceMapperInterface $mapper, + string $importance, + string $expectedFlashMessageType + ) { + $session = $this->createMock(Session::class); + $session->method('getFlashBag')->willReturn(new FlashBag()); + $browserChannel = $this->buildBrowserChannel($session, $mapper); + $notification = new Notification(); + $notification->importance($importance); + $recipient = new Recipient('hello@example.com'); + + $browserChannel->notify($notification, $recipient); + + $this->assertEquals($expectedFlashMessageType, array_key_first($session->getFlashBag()->all())); + } + + public function testUnknownImportanceMappingIsReported() + { + $session = $this->createMock(Session::class); + $session->method('getFlashBag')->willReturn(new FlashBag()); + $browserChannel = $this->buildBrowserChannel($session, new DefaultFlashMessageImportanceMapper()); + $notification = new Notification(); + $notification->importance('unknown-importance-string'); + $recipient = new Recipient('hello@example.com'); + + $this->expectException(FlashMessageImportanceMapperException::class); + + $browserChannel->notify($notification, $recipient); + } + + public function defaultFlashMessageImportanceDataProvider(): array + { + return [ + [new DefaultFlashMessageImportanceMapper(), Notification::IMPORTANCE_URGENT, 'notification'], + [new DefaultFlashMessageImportanceMapper(), Notification::IMPORTANCE_HIGH, 'notification'], + [new DefaultFlashMessageImportanceMapper(), Notification::IMPORTANCE_MEDIUM, 'notification'], + [new DefaultFlashMessageImportanceMapper(), Notification::IMPORTANCE_LOW, 'notification'], + [new BootstrapFlashMessageImportanceMapper(), Notification::IMPORTANCE_URGENT, 'danger'], + [new BootstrapFlashMessageImportanceMapper(), Notification::IMPORTANCE_HIGH, 'warning'], + [new BootstrapFlashMessageImportanceMapper(), Notification::IMPORTANCE_MEDIUM, 'info'], + [new BootstrapFlashMessageImportanceMapper(), Notification::IMPORTANCE_LOW, 'success'], + ]; + } + + private function buildBrowserChannel(Session $session, FlashMessageImportanceMapperInterface $mapper): BrowserChannel + { + $request = $this->createMock(Request::class); + $request->method('getSession')->willReturn($session); + $requestStack = $this->createStub(RequestStack::class); + $requestStack->method('getCurrentRequest')->willReturn($request); + + return new BrowserChannel($requestStack, $mapper); + } +} diff --git a/src/Symfony/Component/Notifier/composer.json b/src/Symfony/Component/Notifier/composer.json index 1178d5d1fa41d..cceb46806f1ca 100644 --- a/src/Symfony/Component/Notifier/composer.json +++ b/src/Symfony/Component/Notifier/composer.json @@ -22,6 +22,7 @@ "require-dev": { "symfony/event-dispatcher-contracts": "^2|^3", "symfony/http-client-contracts": "^2|^3", + "symfony/http-foundation": "^5.4|^6.0", "symfony/messenger": "^5.4|^6.0" }, "conflict": {