From da5a282620f34331f37c7c5ae7c040612fd23ab3 Mon Sep 17 00:00:00 2001 From: Joseph Bielawski Date: Mon, 4 Jul 2022 18:17:18 +0200 Subject: [PATCH] [Notifier] Add Zendesk Notifier Bridge --- .../FrameworkExtension.php | 2 + .../Resources/config/notifier_transports.php | 5 + .../Notifier/Bridge/Zendesk/.gitattributes | 4 + .../Notifier/Bridge/Zendesk/.gitignore | 3 + .../Notifier/Bridge/Zendesk/CHANGELOG.md | 7 ++ .../Component/Notifier/Bridge/Zendesk/LICENSE | 19 ++++ .../Notifier/Bridge/Zendesk/README.md | 24 +++++ .../Tests/ZendeskTransportFactoryTest.php | 48 +++++++++ .../Zendesk/Tests/ZendeskTransportTest.php | 45 ++++++++ .../Bridge/Zendesk/ZendeskOptions.php | 49 +++++++++ .../Bridge/Zendesk/ZendeskTransport.php | 101 ++++++++++++++++++ .../Zendesk/ZendeskTransportFactory.php | 57 ++++++++++ .../Notifier/Bridge/Zendesk/composer.json | 30 ++++++ .../Notifier/Bridge/Zendesk/phpunit.xml.dist | 31 ++++++ .../Exception/UnsupportedSchemeException.php | 4 + .../UnsupportedSchemeExceptionTest.php | 3 + src/Symfony/Component/Notifier/Transport.php | 2 + 17 files changed, 434 insertions(+) create mode 100644 src/Symfony/Component/Notifier/Bridge/Zendesk/.gitattributes create mode 100644 src/Symfony/Component/Notifier/Bridge/Zendesk/.gitignore create mode 100644 src/Symfony/Component/Notifier/Bridge/Zendesk/CHANGELOG.md create mode 100644 src/Symfony/Component/Notifier/Bridge/Zendesk/LICENSE create mode 100644 src/Symfony/Component/Notifier/Bridge/Zendesk/README.md create mode 100644 src/Symfony/Component/Notifier/Bridge/Zendesk/Tests/ZendeskTransportFactoryTest.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Zendesk/Tests/ZendeskTransportTest.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Zendesk/ZendeskOptions.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Zendesk/ZendeskTransport.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Zendesk/ZendeskTransportFactory.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Zendesk/composer.json create mode 100644 src/Symfony/Component/Notifier/Bridge/Zendesk/phpunit.xml.dist diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 8834c48d422bd..998f5606da771 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -168,6 +168,7 @@ use Symfony\Component\Notifier\Bridge\Twilio\TwilioTransportFactory; use Symfony\Component\Notifier\Bridge\Vonage\VonageTransportFactory; use Symfony\Component\Notifier\Bridge\Yunpian\YunpianTransportFactory; +use Symfony\Component\Notifier\Bridge\Zendesk\ZendeskTransportFactory; use Symfony\Component\Notifier\Bridge\Zulip\ZulipTransportFactory; use Symfony\Component\Notifier\Notifier; use Symfony\Component\Notifier\Recipient\Recipient; @@ -2567,6 +2568,7 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ TwilioTransportFactory::class => 'notifier.transport_factory.twilio', VonageTransportFactory::class => 'notifier.transport_factory.vonage', YunpianTransportFactory::class => 'notifier.transport_factory.yunpian', + ZendeskTransportFactory::class => 'notifier.transport_factory.zendesk', ZulipTransportFactory::class => 'notifier.transport_factory.zulip', ]; diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php index 918564f3765be..691588d730d92 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php @@ -60,6 +60,7 @@ use Symfony\Component\Notifier\Bridge\Twilio\TwilioTransportFactory; use Symfony\Component\Notifier\Bridge\Vonage\VonageTransportFactory; use Symfony\Component\Notifier\Bridge\Yunpian\YunpianTransportFactory; +use Symfony\Component\Notifier\Bridge\Zendesk\ZendeskTransportFactory; use Symfony\Component\Notifier\Bridge\Zulip\ZulipTransportFactory; use Symfony\Component\Notifier\Transport\AbstractTransportFactory; use Symfony\Component\Notifier\Transport\NullTransportFactory; @@ -276,5 +277,9 @@ ->set('notifier.transport_factory.engagespot', EngagespotTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.zendesk', ZendeskTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory') ; }; diff --git a/src/Symfony/Component/Notifier/Bridge/Zendesk/.gitattributes b/src/Symfony/Component/Notifier/Bridge/Zendesk/.gitattributes new file mode 100644 index 0000000000000..84c7add058fb5 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Zendesk/.gitattributes @@ -0,0 +1,4 @@ +/Tests export-ignore +/phpunit.xml.dist export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore diff --git a/src/Symfony/Component/Notifier/Bridge/Zendesk/.gitignore b/src/Symfony/Component/Notifier/Bridge/Zendesk/.gitignore new file mode 100644 index 0000000000000..c49a5d8df5c65 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Zendesk/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/src/Symfony/Component/Notifier/Bridge/Zendesk/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/Zendesk/CHANGELOG.md new file mode 100644 index 0000000000000..7174cd7fdeedd --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Zendesk/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +6.2 +--- + + * Add the bridge diff --git a/src/Symfony/Component/Notifier/Bridge/Zendesk/LICENSE b/src/Symfony/Component/Notifier/Bridge/Zendesk/LICENSE new file mode 100644 index 0000000000000..0ece8964f767d --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Zendesk/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2022 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/Symfony/Component/Notifier/Bridge/Zendesk/README.md b/src/Symfony/Component/Notifier/Bridge/Zendesk/README.md new file mode 100644 index 0000000000000..b3839f065ca78 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Zendesk/README.md @@ -0,0 +1,24 @@ +Zendesk Notifier +================ + +Provides [Zendesk](https://www.zendesk.com) integration for Symfony Notifier. + +DSN example +----------- + +``` +ZENDESK_DSN=zendesk://EMAIL:TOKEN@SUBDOMAIN +``` + +where: + - `EMAIL` is your Zendesk Email Address + - `TOKEN` is your Zendesk API token + - `SUBDOMAIN` is your subdomain name at zendesk.com + +Resources +--------- + + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/src/Symfony/Component/Notifier/Bridge/Zendesk/Tests/ZendeskTransportFactoryTest.php b/src/Symfony/Component/Notifier/Bridge/Zendesk/Tests/ZendeskTransportFactoryTest.php new file mode 100644 index 0000000000000..ba7f2fc1a4797 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Zendesk/Tests/ZendeskTransportFactoryTest.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Zendesk\Tests; + +use Symfony\Component\Notifier\Bridge\Zendesk\ZendeskTransportFactory; +use Symfony\Component\Notifier\Test\TransportFactoryTestCase; + +final class ZendeskTransportFactoryTest extends TransportFactoryTestCase +{ + public function createFactory(): ZendeskTransportFactory + { + return new ZendeskTransportFactory(); + } + + public function createProvider(): iterable + { + yield [ + 'zendesk://subdomain.zendesk.com', + 'zendesk://email:token@subdomain.zendesk.com', + ]; + } + + public function supportsProvider(): iterable + { + yield [true, 'zendesk://host']; + yield [false, 'somethingElse://host']; + } + + public function incompleteDsnProvider(): iterable + { + yield 'missing email or token' => ['zendesk://testOneOfEmailOrToken@host']; + yield 'wrong host' => ['zendesk://testEmail:Token@host.com']; + } + + public function unsupportedSchemeProvider(): iterable + { + yield ['somethingElse://email:token@host']; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Zendesk/Tests/ZendeskTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Zendesk/Tests/ZendeskTransportTest.php new file mode 100644 index 0000000000000..39cb527485e84 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Zendesk/Tests/ZendeskTransportTest.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Zendesk\Tests; + +use Symfony\Component\Notifier\Bridge\Zendesk\ZendeskOptions; +use Symfony\Component\Notifier\Bridge\Zendesk\ZendeskTransport; +use Symfony\Component\Notifier\Message\ChatMessage; +use Symfony\Component\Notifier\Message\MessageInterface; +use Symfony\Component\Notifier\Message\SmsMessage; +use Symfony\Component\Notifier\Test\TransportTestCase; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +final class ZendeskTransportTest extends TransportTestCase +{ + public function createTransport(HttpClientInterface $client = null): ZendeskTransport + { + return (new ZendeskTransport('testEmail', 'testToken', $client ?? $this->createMock(HttpClientInterface::class)))->setHost('test.zendesk.com'); + } + + public function toStringProvider(): iterable + { + yield ['zendesk://test.zendesk.com', $this->createTransport()]; + } + + public function supportedMessagesProvider(): iterable + { + yield [new ChatMessage('Hello!')]; + yield [new ChatMessage('Hello!', new ZendeskOptions('urgent'))]; + } + + public function unsupportedMessagesProvider(): iterable + { + yield [new SmsMessage('0611223344', 'Hello!')]; + yield [$this->createMock(MessageInterface::class)]; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Zendesk/ZendeskOptions.php b/src/Symfony/Component/Notifier/Bridge/Zendesk/ZendeskOptions.php new file mode 100644 index 0000000000000..2b84d437f5c00 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Zendesk/ZendeskOptions.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Zendesk; + +use Symfony\Component\Notifier\Message\MessageOptionsInterface; + +/** + * @author Joseph Bielawski + */ +final class ZendeskOptions implements MessageOptionsInterface +{ + private ?string $priority; + + public function __construct(string $priority = null) + { + $this->priority = $priority; + } + + public function toArray(): array + { + return [ + 'priority' => $this->priority, + ]; + } + + public function getRecipientId(): ?string + { + return null; + } + + /** + * @return $this + */ + public function priority(string $priority): static + { + $this->priority = $priority; + + return $this; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Zendesk/ZendeskTransport.php b/src/Symfony/Component/Notifier/Bridge/Zendesk/ZendeskTransport.php new file mode 100644 index 0000000000000..c04746787c988 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Zendesk/ZendeskTransport.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Zendesk; + +use Symfony\Component\Notifier\Exception\LogicException; +use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; +use Symfony\Component\Notifier\Message\ChatMessage; +use Symfony\Component\Notifier\Message\MessageInterface; +use Symfony\Component\Notifier\Message\SentMessage; +use Symfony\Component\Notifier\Transport\AbstractTransport; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * @author Joseph Bielawski + */ +final class ZendeskTransport extends AbstractTransport +{ + private string $email; + private string $token; + + public function __construct(string $email, string $token, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null) + { + parent::__construct($client, $dispatcher); + + $this->email = $email; + $this->token = $token; + } + + public function __toString(): string + { + return sprintf('zendesk://%s', $this->getEndpoint()); + } + + public function supports(MessageInterface $message): bool + { + return $message instanceof ChatMessage; + } + + protected function doSend(MessageInterface $message = null): SentMessage + { + if (!$message instanceof ChatMessage) { + throw new UnsupportedMessageTypeException(__CLASS__, ChatMessage::class, $message); + } + + if (null !== $message->getOptions() && !($message->getOptions() instanceof ZendeskOptions)) { + throw new LogicException(sprintf('The "%s" transport only supports instances of "%s" for options.', __CLASS__, ZendeskOptions::class)); + } + + $endpoint = sprintf('https://%s/api/v2/tickets.json', $this->getEndpoint()); + + $body = [ + 'ticket' => [ + 'subject' => $message->getSubject(), + 'comment' => [ + 'body' => $message->getNotification() ? $message->getNotification()->getContent() : '', + ], + ], + ]; + + $options = ($opts = $message->getOptions()) ? $opts->toArray() : []; + if ($options['priority'] ?? null) { + $body['ticket']['priority'] = $options['priority']; + } + + $response = $this->client->request('POST', $endpoint, [ + 'auth_basic' => [$this->email.'/token', $this->token], + 'json' => $body, + ]); + + try { + $statusCode = $response->getStatusCode(); + } catch (TransportExceptionInterface $e) { + throw new TransportException('Could not reach the remote Zendesk server.', $response, 0, $e); + } + + if (201 !== $statusCode) { + $result = $response->toArray(false); + + $errorMessage = $result['error']; + if (\is_array($errorMessage)) { + $errorMessage = implode(' | ', array_values($errorMessage)); + } + + throw new TransportException(sprintf('Unable to post the Zendesk message: "%s".', $errorMessage), $response); + } + + return new SentMessage($message, (string) $this); + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Zendesk/ZendeskTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Zendesk/ZendeskTransportFactory.php new file mode 100644 index 0000000000000..2015f5a765514 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Zendesk/ZendeskTransportFactory.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Zendesk; + +use Symfony\Component\Notifier\Exception\IncompleteDsnException; +use Symfony\Component\Notifier\Exception\UnsupportedSchemeException; +use Symfony\Component\Notifier\Transport\AbstractTransportFactory; +use Symfony\Component\Notifier\Transport\Dsn; + +/** + * @author Joseph Bielawski + */ +final class ZendeskTransportFactory extends AbstractTransportFactory +{ + public function create(Dsn $dsn): ZendeskTransport + { + $scheme = $dsn->getScheme(); + + if ('zendesk' !== $scheme) { + throw new UnsupportedSchemeException($dsn, 'zendesk', $this->getSupportedSchemes()); + } + + $emailAddress = $this->getUser($dsn); + $apiToken = $this->getPassword($dsn); + $host = $this->getHost($dsn); + + return (new ZendeskTransport($emailAddress, $apiToken, $this->client, $this->dispatcher))->setHost($host); + } + + protected function getSupportedSchemes(): array + { + return ['zendesk']; + } + + private function getHost(Dsn $dsn): string + { + $host = $dsn->getHost(); + if ('default' === $host) { + throw new IncompleteDsnException('Host is not set.', $dsn->getOriginalDsn()); + } + + if (!str_ends_with($host, '.zendesk.com')) { + throw new IncompleteDsnException('Host must be in format: "subdomain.zendesk.com".', $dsn->getOriginalDsn()); + } + + return $host; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Zendesk/composer.json b/src/Symfony/Component/Notifier/Bridge/Zendesk/composer.json new file mode 100644 index 0000000000000..8979831d10197 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Zendesk/composer.json @@ -0,0 +1,30 @@ +{ + "name": "symfony/zendesk-notifier", + "type": "symfony-notifier-bridge", + "description": "Symfony Zendesk Notifier Bridge", + "keywords": ["chat", "zendesk", "notifier"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Joseph Bielawski", + "email": "stloyd@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1", + "symfony/http-client": "^5.4|^6.0", + "symfony/notifier": "^6.2" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Zendesk\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/src/Symfony/Component/Notifier/Bridge/Zendesk/phpunit.xml.dist b/src/Symfony/Component/Notifier/Bridge/Zendesk/phpunit.xml.dist new file mode 100644 index 0000000000000..5bd9816e2cf8b --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Zendesk/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + + ./Resources + ./Tests + ./vendor + + + diff --git a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php index 065bbae86473d..f5b51bf9bfaaf 100644 --- a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php +++ b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php @@ -212,6 +212,10 @@ class UnsupportedSchemeException extends LogicException 'class' => Bridge\Yunpian\YunpianTransportFactory::class, 'package' => 'symfony/yunpian-notifier', ], + 'zendesk' => [ + 'class' => Bridge\Zendesk\ZendeskTransportFactory::class, + 'package' => 'symfony/zendesk-notifier', + ], 'zulip' => [ 'class' => Bridge\Zulip\ZulipTransportFactory::class, 'package' => 'symfony/zulip-notifier', diff --git a/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php b/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php index 8b27a0e92e0a6..8ffb0c1f79c4c 100644 --- a/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php +++ b/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php @@ -58,6 +58,7 @@ use Symfony\Component\Notifier\Bridge\Twilio\TwilioTransportFactory; use Symfony\Component\Notifier\Bridge\Vonage\VonageTransportFactory; use Symfony\Component\Notifier\Bridge\Yunpian\YunpianTransportFactory; +use Symfony\Component\Notifier\Bridge\Zendesk\ZendeskTransportFactory; use Symfony\Component\Notifier\Bridge\Zulip\ZulipTransportFactory; use Symfony\Component\Notifier\Exception\UnsupportedSchemeException; use Symfony\Component\Notifier\Transport\Dsn; @@ -116,6 +117,7 @@ public static function setUpBeforeClass(): void TwilioTransportFactory::class => false, VonageTransportFactory::class => false, YunpianTransportFactory::class => false, + ZendeskTransportFactory::class => false, ZulipTransportFactory::class => false, ]); } @@ -177,6 +179,7 @@ public function messageWhereSchemeIsPartOfSchemeToPackageMapProvider(): \Generat yield ['telnyx', 'symfony/telnyx-notifier']; yield ['turbosms', 'symfony/turbo-sms-notifier']; yield ['twilio', 'symfony/twilio-notifier']; + yield ['zendesk', 'symfony/zendesk-notifier']; yield ['zulip', 'symfony/zulip-notifier']; } diff --git a/src/Symfony/Component/Notifier/Transport.php b/src/Symfony/Component/Notifier/Transport.php index 0c0d7ace931f7..8389474e1468c 100644 --- a/src/Symfony/Component/Notifier/Transport.php +++ b/src/Symfony/Component/Notifier/Transport.php @@ -52,6 +52,7 @@ use Symfony\Component\Notifier\Bridge\Twilio\TwilioTransportFactory; use Symfony\Component\Notifier\Bridge\Vonage\VonageTransportFactory; use Symfony\Component\Notifier\Bridge\Yunpian\YunpianTransportFactory; +use Symfony\Component\Notifier\Bridge\Zendesk\ZendeskTransportFactory; use Symfony\Component\Notifier\Bridge\Zulip\ZulipTransportFactory; use Symfony\Component\Notifier\Exception\UnsupportedSchemeException; use Symfony\Component\Notifier\Transport\Dsn; @@ -111,6 +112,7 @@ final class Transport TwilioTransportFactory::class, VonageTransportFactory::class, YunpianTransportFactory::class, + ZendeskTransportFactory::class, ZulipTransportFactory::class, ];