feature #39457 [Notifier] [DX] Dsn::getRequiredOption() (OskarStark)

This PR was squashed before being merged into the 5.3-dev branch.

Discussion
----------

[Notifier] [DX] Dsn::getRequiredOption()

| Q             | A
| ------------- | ---
| Branch?       | 5.x
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Tickets       | ---
| License       | MIT
| Doc PR        | ---

### Todo
* [x] use the new method in all available bridges and bump composer.json files to `notifer:5.3`
* [x] add tests

This will lead to a much cleaner "communication" between the factory and the transport which option is required and which is optional. This method will also reduce the code duplication in the bridges.

IMHO we should add more of those convenient "helpers" to lower the burden for new bridges.

Commits
-------

3f095bf419 [Notifier] [DX] Dsn::getRequiredOption()
This commit is contained in:
Fabien Potencier 2021-01-06 13:35:38 +01:00
commit fe91b862c5
33 changed files with 164 additions and 130 deletions

View File

@ -11,7 +11,6 @@
namespace Symfony\Component\Notifier\Bridge\Discord;
use Symfony\Component\Notifier\Exception\IncompleteDsnException;
use Symfony\Component\Notifier\Exception\UnsupportedSchemeException;
use Symfony\Component\Notifier\Transport\AbstractTransportFactory;
use Symfony\Component\Notifier\Transport\Dsn;
@ -34,12 +33,7 @@ final class DiscordTransportFactory extends AbstractTransportFactory
}
$token = $this->getUser($dsn);
$webhookId = $dsn->getOption('webhook_id');
if (!$webhookId) {
throw new IncompleteDsnException('Missing webhook_id.', $dsn->getOriginalDsn());
}
$webhookId = $dsn->getRequiredOption('webhook_id');
$host = 'default' === $dsn->getHost() ? null : $dsn->getHost();
$port = $dsn->getPort();

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\Notifier\Bridge\Discord\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Notifier\Bridge\Discord\DiscordTransportFactory;
use Symfony\Component\Notifier\Exception\IncompleteDsnException;
use Symfony\Component\Notifier\Exception\MissingRequiredOptionException;
use Symfony\Component\Notifier\Exception\UnsupportedSchemeException;
use Symfony\Component\Notifier\Transport\Dsn;
@ -28,11 +29,11 @@ final class DiscordTransportFactoryTest extends TestCase
$this->assertSame('discord://host.test?webhook_id=testWebhookId', (string) $transport);
}
public function testCreateWithMissingOptionWebhookIdThrowsIncompleteDsnException()
public function testCreateWithMissingOptionWebhookIdThrowsMissingRequiredOptionException()
{
$factory = $this->createFactory();
$this->expectException(IncompleteDsnException::class);
$this->expectException(MissingRequiredOptionException::class);
$factory->create(Dsn::fromString('discord://token@host'));
}

View File

@ -11,7 +11,6 @@
namespace Symfony\Component\Notifier\Bridge\Esendex;
use Symfony\Component\Notifier\Exception\IncompleteDsnException;
use Symfony\Component\Notifier\Exception\UnsupportedSchemeException;
use Symfony\Component\Notifier\Transport\AbstractTransportFactory;
use Symfony\Component\Notifier\Transport\Dsn;
@ -32,18 +31,8 @@ final class EsendexTransportFactory extends AbstractTransportFactory
$email = $this->getUser($dsn);
$password = $this->getPassword($dsn);
$accountReference = $dsn->getOption('accountreference');
if (!$accountReference) {
throw new IncompleteDsnException('Missing accountreference.', $dsn->getOriginalDsn());
}
$from = $dsn->getOption('from');
if (!$from) {
throw new IncompleteDsnException('Missing from.', $dsn->getOriginalDsn());
}
$accountReference = $dsn->getRequiredOption('accountreference');
$from = $dsn->getRequiredOption('from');
$host = 'default' === $dsn->getHost() ? null : $dsn->getHost();
$port = $dsn->getPort();

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\Notifier\Bridge\Esendex\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Notifier\Bridge\Esendex\EsendexTransportFactory;
use Symfony\Component\Notifier\Exception\IncompleteDsnException;
use Symfony\Component\Notifier\Exception\MissingRequiredOptionException;
use Symfony\Component\Notifier\Exception\UnsupportedSchemeException;
use Symfony\Component\Notifier\Transport\Dsn;
@ -46,20 +47,20 @@ final class EsendexTransportFactoryTest extends TestCase
$factory->create(Dsn::fromString('esendex://email:@host?accountreference=testAccountreference&from=FROM'));
}
public function testCreateWithMissingOptionAccountreferenceThrowsIncompleteDsnException()
public function testCreateWithMissingOptionAccountreferenceThrowsMissingRequiredOptionException()
{
$factory = $this->createFactory();
$this->expectException(IncompleteDsnException::class);
$this->expectException(MissingRequiredOptionException::class);
$factory->create(Dsn::fromString('esendex://email:password@host?from=FROM'));
}
public function testCreateWithMissingOptionFromThrowsIncompleteDsnException()
public function testCreateWithMissingOptionFromThrowsMissingRequiredOptionException()
{
$factory = $this->createFactory();
$this->expectException(IncompleteDsnException::class);
$this->expectException(MissingRequiredOptionException::class);
$factory->create(Dsn::fromString('esendex://email:password@host?accountreference=ACCOUNTREFERENCE'));
}

View File

@ -11,7 +11,6 @@
namespace Symfony\Component\Notifier\Bridge\FreeMobile;
use Symfony\Component\Notifier\Exception\IncompleteDsnException;
use Symfony\Component\Notifier\Exception\UnsupportedSchemeException;
use Symfony\Component\Notifier\Transport\AbstractTransportFactory;
use Symfony\Component\Notifier\Transport\Dsn;
@ -35,11 +34,7 @@ final class FreeMobileTransportFactory extends AbstractTransportFactory
$login = $this->getUser($dsn);
$password = $this->getPassword($dsn);
$phone = $dsn->getOption('phone');
if (!$phone) {
throw new IncompleteDsnException('Missing phone.', $dsn->getOriginalDsn());
}
$phone = $dsn->getRequiredOption('phone');
$host = 'default' === $dsn->getHost() ? null : $dsn->getHost();
$port = $dsn->getPort();

View File

@ -39,7 +39,7 @@ final class FreeMobileTransportFactoryTest extends TransportFactoryTestCase
yield [false, 'somethingElse://login:pass@default?phone=0611223344'];
}
public function incompleteDsnProvider(): iterable
public function missingRequiredOptionProvider(): iterable
{
yield 'missing option: phone' => ['freemobile://login:pass@default'];
}

View File

@ -11,7 +11,6 @@
namespace Symfony\Component\Notifier\Bridge\Infobip;
use Symfony\Component\Notifier\Exception\IncompleteDsnException;
use Symfony\Component\Notifier\Exception\UnsupportedSchemeException;
use Symfony\Component\Notifier\Transport\AbstractTransportFactory;
use Symfony\Component\Notifier\Transport\Dsn;
@ -35,14 +34,10 @@ final class InfobipTransportFactory extends AbstractTransportFactory
}
$authToken = $this->getUser($dsn);
$from = $dsn->getOption('from');
$from = $dsn->getRequiredOption('from');
$host = $dsn->getHost();
$port = $dsn->getPort();
if (!$from) {
throw new IncompleteDsnException('Missing from.', $dsn->getOriginalDsn());
}
return (new InfobipTransport($authToken, $from, $this->client, $this->dispatcher))->setHost($host)->setPort($port);
}

View File

@ -13,7 +13,7 @@ namespace Symfony\Component\Notifier\Bridge\Infobip\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Notifier\Bridge\Infobip\InfobipTransportFactory;
use Symfony\Component\Notifier\Exception\IncompleteDsnException;
use Symfony\Component\Notifier\Exception\MissingRequiredOptionException;
use Symfony\Component\Notifier\Exception\UnsupportedSchemeException;
use Symfony\Component\Notifier\Transport\Dsn;
@ -28,11 +28,11 @@ final class InfobipTransportFactoryTest extends TestCase
$this->assertSame('infobip://host.test?from=0611223344', (string) $transport);
}
public function testCreateWithNoFromThrowsIncompleteDsnException()
public function testCreateWithNoFromThrowsMissingRequiredOptionException()
{
$factory = $this->createFactory();
$this->expectException(IncompleteDsnException::class);
$this->expectException(MissingRequiredOptionException::class);
$factory->create(Dsn::fromString('infobip://authtoken@default'));
}

View File

@ -11,7 +11,6 @@
namespace Symfony\Component\Notifier\Bridge\Iqsms;
use Symfony\Component\Notifier\Exception\IncompleteDsnException;
use Symfony\Component\Notifier\Exception\UnsupportedSchemeException;
use Symfony\Component\Notifier\Transport\AbstractTransportFactory;
use Symfony\Component\Notifier\Transport\Dsn;
@ -35,12 +34,7 @@ final class IqsmsTransportFactory extends AbstractTransportFactory
$login = $this->getUser($dsn);
$password = $this->getPassword($dsn);
$from = $dsn->getOption('from');
if (!$from) {
throw new IncompleteDsnException('Missing from.', $dsn->getOriginalDsn());
}
$from = $dsn->getRequiredOption('from');
$host = 'default' === $dsn->getHost() ? null : $dsn->getHost();
$port = $dsn->getPort();

View File

@ -11,7 +11,6 @@
namespace Symfony\Component\Notifier\Bridge\Mattermost;
use Symfony\Component\Notifier\Exception\IncompleteDsnException;
use Symfony\Component\Notifier\Exception\UnsupportedSchemeException;
use Symfony\Component\Notifier\Transport\AbstractTransportFactory;
use Symfony\Component\Notifier\Transport\Dsn;
@ -32,12 +31,7 @@ final class MattermostTransportFactory extends AbstractTransportFactory
$path = $dsn->getPath();
$token = $this->getUser($dsn);
$channel = $dsn->getOption('channel');
if (!$channel) {
throw new IncompleteDsnException('Missing channel.', $dsn->getOriginalDsn());
}
$channel = $dsn->getRequiredOption('channel');
$host = $dsn->getHost();
$port = $dsn->getPort();

View File

@ -59,7 +59,11 @@ final class MattermostTransportFactoryTest extends TransportFactoryTestCase
public function incompleteDsnProvider(): iterable
{
yield 'missing option: token' => ['mattermost://host.test?channel=testChannel'];
yield 'missing token' => ['mattermost://host.test?channel=testChannel'];
}
public function missingRequiredOptionProvider(): iterable
{
yield 'missing option: channel' => ['mattermost://token@host'];
}

View File

@ -11,7 +11,6 @@
namespace Symfony\Component\Notifier\Bridge\Mobyt;
use Symfony\Component\Notifier\Exception\IncompleteDsnException;
use Symfony\Component\Notifier\Exception\UnsupportedSchemeException;
use Symfony\Component\Notifier\Transport\AbstractTransportFactory;
use Symfony\Component\Notifier\Transport\Dsn;
@ -35,12 +34,7 @@ final class MobytTransportFactory extends AbstractTransportFactory
$accountSid = $this->getUser($dsn);
$authToken = $this->getPassword($dsn);
$from = $dsn->getOption('from');
if (!$from) {
throw new IncompleteDsnException('Missing from.', $dsn->getOriginalDsn());
}
$from = $dsn->getRequiredOption('from');
$typeQuality = $dsn->getOption('type_quality', MobytOptions::MESSAGE_TYPE_QUALITY_LOW);
$host = 'default' === $dsn->getHost() ? null : $dsn->getHost();
$port = $dsn->getPort();

View File

@ -11,7 +11,6 @@
namespace Symfony\Component\Notifier\Bridge\Nexmo;
use Symfony\Component\Notifier\Exception\IncompleteDsnException;
use Symfony\Component\Notifier\Exception\UnsupportedSchemeException;
use Symfony\Component\Notifier\Transport\AbstractTransportFactory;
use Symfony\Component\Notifier\Transport\Dsn;
@ -35,12 +34,7 @@ final class NexmoTransportFactory extends AbstractTransportFactory
$apiKey = $this->getUser($dsn);
$apiSecret = $this->getPassword($dsn);
$from = $dsn->getOption('from');
if (!$from) {
throw new IncompleteDsnException('Missing from.', $dsn->getOriginalDsn());
}
$from = $dsn->getRequiredOption('from');
$host = 'default' === $dsn->getHost() ? null : $dsn->getHost();
$port = $dsn->getPort();

View File

@ -39,7 +39,7 @@ final class NexmoTransportFactoryTest extends TransportFactoryTestCase
yield [false, 'somethingElse://apiKey:apiSecret@default?from=0611223344'];
}
public function incompleteDsnProvider(): iterable
public function missingRequiredOptionProvider(): iterable
{
yield 'missing option: from' => ['nexmo://apiKey:apiSecret@default'];
}

View File

@ -11,7 +11,6 @@
namespace Symfony\Component\Notifier\Bridge\OvhCloud;
use Symfony\Component\Notifier\Exception\IncompleteDsnException;
use Symfony\Component\Notifier\Exception\UnsupportedSchemeException;
use Symfony\Component\Notifier\Transport\AbstractTransportFactory;
use Symfony\Component\Notifier\Transport\Dsn;
@ -32,18 +31,8 @@ final class OvhCloudTransportFactory extends AbstractTransportFactory
$applicationKey = $this->getUser($dsn);
$applicationSecret = $this->getPassword($dsn);
$consumerKey = $dsn->getOption('consumer_key');
if (!$consumerKey) {
throw new IncompleteDsnException('Missing consumer_key.', $dsn->getOriginalDsn());
}
$serviceName = $dsn->getOption('service_name');
if (!$serviceName) {
throw new IncompleteDsnException('Missing service_name.', $dsn->getOriginalDsn());
}
$consumerKey = $dsn->getRequiredOption('consumer_key');
$serviceName = $dsn->getRequiredOption('service_name');
$host = 'default' === $dsn->getHost() ? null : $dsn->getHost();
$port = $dsn->getPort();

View File

@ -39,7 +39,7 @@ final class OvhCloudTransportFactoryTest extends TransportFactoryTestCase
yield [false, 'somethingElse://key:secret@default?consumer_key=consumerKey&service_name=serviceName'];
}
public function incompleteDsnProvider(): iterable
public function missingRequiredOptionProvider(): iterable
{
yield 'missing option: consumer_key' => ['ovhcloud://key:secret@default?service_name=serviceName'];
yield 'missing option: service_name' => ['ovhcloud://key:secret@default?consumer_key=consumerKey'];

View File

@ -44,7 +44,7 @@ final class RocketChatTransportFactoryTest extends TransportFactoryTestCase
public function incompleteDsnProvider(): iterable
{
yield 'missing option: token' => ['rocketchat://host.test?channel=testChannel'];
yield 'missing token' => ['rocketchat://host.test?channel=testChannel'];
}
public function unsupportedSchemeProvider(): iterable

View File

@ -11,7 +11,6 @@
namespace Symfony\Component\Notifier\Bridge\Sendinblue;
use Symfony\Component\Notifier\Exception\IncompleteDsnException;
use Symfony\Component\Notifier\Exception\UnsupportedSchemeException;
use Symfony\Component\Notifier\Transport\AbstractTransportFactory;
use Symfony\Component\Notifier\Transport\Dsn;
@ -34,12 +33,7 @@ final class SendinblueTransportFactory extends AbstractTransportFactory
}
$apiKey = $this->getUser($dsn);
$sender = $dsn->getOption('sender');
if (!$sender) {
throw new IncompleteDsnException('Missing sender.', $dsn->getOriginalDsn());
}
$sender = $dsn->getRequiredOption('sender');
$host = 'default' === $dsn->getHost() ? null : $dsn->getHost();
$port = $dsn->getPort();

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\Notifier\Bridge\Sendinblue\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Notifier\Bridge\Sendinblue\SendinblueTransportFactory;
use Symfony\Component\Notifier\Exception\IncompleteDsnException;
use Symfony\Component\Notifier\Exception\MissingRequiredOptionException;
use Symfony\Component\Notifier\Exception\UnsupportedSchemeException;
use Symfony\Component\Notifier\Transport\Dsn;
@ -28,11 +29,11 @@ final class SendinblueTransportFactoryTest extends TestCase
$this->assertSame('sendinblue://host.test?sender=0611223344', (string) $transport);
}
public function testCreateWithMissingOptionSenderThrowsIncompleteDsnException()
public function testCreateWithMissingOptionSenderThrowsMissingRequiredOptionException()
{
$factory = $this->createFactory();
$this->expectException(IncompleteDsnException::class);
$this->expectException(MissingRequiredOptionException::class);
$factory->create(Dsn::fromString('sendinblue://apiKey@host.test'));
}

View File

@ -11,7 +11,6 @@
namespace Symfony\Component\Notifier\Bridge\Sinch;
use Symfony\Component\Notifier\Exception\IncompleteDsnException;
use Symfony\Component\Notifier\Exception\UnsupportedSchemeException;
use Symfony\Component\Notifier\Transport\AbstractTransportFactory;
use Symfony\Component\Notifier\Transport\Dsn;
@ -32,12 +31,7 @@ final class SinchTransportFactory extends AbstractTransportFactory
$accountSid = $this->getUser($dsn);
$authToken = $this->getPassword($dsn);
$from = $dsn->getOption('from');
if (!$from) {
throw new IncompleteDsnException('Missing from.', $dsn->getOriginalDsn());
}
$from = $dsn->getRequiredOption('from');
$host = 'default' === $dsn->getHost() ? null : $dsn->getHost();
$port = $dsn->getPort();

View File

@ -39,7 +39,7 @@ final class SinchTransportFactoryTest extends TransportFactoryTestCase
yield [false, 'somethingElse://accountSid:authToken@default?from=0611223344'];
}
public function incompleteDsnProvider(): iterable
public function missingRequiredOptionProvider(): iterable
{
yield 'missing option: from' => ['sinch://accountSid:authToken@default'];
}

View File

@ -11,7 +11,6 @@
namespace Symfony\Component\Notifier\Bridge\Smsapi;
use Symfony\Component\Notifier\Exception\IncompleteDsnException;
use Symfony\Component\Notifier\Exception\UnsupportedSchemeException;
use Symfony\Component\Notifier\Transport\AbstractTransportFactory;
use Symfony\Component\Notifier\Transport\Dsn;
@ -36,12 +35,7 @@ final class SmsapiTransportFactory extends AbstractTransportFactory
}
$authToken = $this->getUser($dsn);
$from = $dsn->getOption('from');
if (!$from) {
throw new IncompleteDsnException('Missing from.', $dsn->getOriginalDsn());
}
$from = $dsn->getRequiredOption('from');
$host = 'default' === $dsn->getHost() ? null : $dsn->getHost();
$port = $dsn->getPort();

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\Notifier\Bridge\Smsapi\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Notifier\Bridge\Smsapi\SmsapiTransportFactory;
use Symfony\Component\Notifier\Exception\IncompleteDsnException;
use Symfony\Component\Notifier\Exception\MissingRequiredOptionException;
use Symfony\Component\Notifier\Exception\UnsupportedSchemeException;
use Symfony\Component\Notifier\Transport\Dsn;
@ -28,11 +29,11 @@ final class SmsapiTransportFactoryTest extends TestCase
$this->assertSame('smsapi://host.test?from=testFrom', (string) $transport);
}
public function testCreateWithMissingOptionFromThrowsIncompleteDsnException()
public function testCreateWithMissingOptionFromThrowsMissingRequiredOptionException()
{
$factory = $this->createFactory();
$this->expectException(IncompleteDsnException::class);
$this->expectException(MissingRequiredOptionException::class);
$factory->create(Dsn::fromString('smsapi://token@host'));
}

View File

@ -39,7 +39,7 @@ final class TwilioTransportFactoryTest extends TransportFactoryTestCase
yield [false, 'somethingElse://accountSid:authToken@default?from=0611223344'];
}
public function incompleteDsnProvider(): iterable
public function missingRequiredOptionProvider(): iterable
{
yield 'missing option: from' => ['twilio://accountSid:authToken@default'];
}

View File

@ -11,7 +11,6 @@
namespace Symfony\Component\Notifier\Bridge\Twilio;
use Symfony\Component\Notifier\Exception\IncompleteDsnException;
use Symfony\Component\Notifier\Exception\UnsupportedSchemeException;
use Symfony\Component\Notifier\Transport\AbstractTransportFactory;
use Symfony\Component\Notifier\Transport\Dsn;
@ -35,12 +34,7 @@ final class TwilioTransportFactory extends AbstractTransportFactory
$accountSid = $this->getUser($dsn);
$authToken = $this->getPassword($dsn);
$from = $dsn->getOption('from');
if (!$from) {
throw new IncompleteDsnException('Missing from.', $dsn->getOriginalDsn());
}
$from = $dsn->getRequiredOption('from');
$host = 'default' === $dsn->getHost() ? null : $dsn->getHost();
$port = $dsn->getPort();

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\Notifier\Bridge\Zulip\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Notifier\Bridge\Zulip\ZulipTransportFactory;
use Symfony\Component\Notifier\Exception\IncompleteDsnException;
use Symfony\Component\Notifier\Exception\MissingRequiredOptionException;
use Symfony\Component\Notifier\Exception\UnsupportedSchemeException;
use Symfony\Component\Notifier\Transport\Dsn;
@ -28,11 +29,11 @@ final class ZulipTransportFactoryTest extends TestCase
$this->assertSame('zulip://host.test?channel=testChannel', (string) $transport);
}
public function testCreateWithMissingOptionChannelThrowsIncompleteDsnException()
public function testCreateWithMissingOptionChannelThrowsMissingRequiredOptionException()
{
$factory = $this->createFactory();
$this->expectException(IncompleteDsnException::class);
$this->expectException(MissingRequiredOptionException::class);
$factory->create(Dsn::fromString('zulip://email:token@host'));
}

View File

@ -11,7 +11,6 @@
namespace Symfony\Component\Notifier\Bridge\Zulip;
use Symfony\Component\Notifier\Exception\IncompleteDsnException;
use Symfony\Component\Notifier\Exception\UnsupportedSchemeException;
use Symfony\Component\Notifier\Transport\AbstractTransportFactory;
use Symfony\Component\Notifier\Transport\Dsn;
@ -35,12 +34,7 @@ final class ZulipTransportFactory extends AbstractTransportFactory
$email = $this->getUser($dsn);
$token = $this->getPassword($dsn);
$channel = $dsn->getOption('channel');
if (!$channel) {
throw new IncompleteDsnException('Missing channel.', $dsn->getOriginalDsn());
}
$channel = $dsn->getRequiredOption('channel');
$host = $dsn->getHost();
$port = $dsn->getPort();

View File

@ -6,6 +6,7 @@ CHANGELOG
* The component is not marked as `@experimental` anymore
* [BC BREAK] Changed the return type of `AbstractTransportFactory::getEndpoint()` from `?string` to `string`
* Added `DSN::getRequiredOption` method which throws a new `MissingRequiredOptionException`.
5.2.0
-----

View File

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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 Oskar Stark <oskarstark@googlemail.com>
*
* @experimental in 5.3
*/
class MissingRequiredOptionException extends IncompleteDsnException
{
public function __construct(string $option, string $dsn = null, ?\Throwable $previous = null)
{
$message = sprintf('The option "%s" is required but missing.', $option);
parent::__construct($message, $dsn, $previous);
}
}

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\Notifier\Tests\Transport;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Notifier\Exception\InvalidArgumentException;
use Symfony\Component\Notifier\Exception\MissingRequiredOptionException;
use Symfony\Component\Notifier\Transport\Dsn;
final class DsnTest extends TestCase
@ -120,4 +121,54 @@ final class DsnTest extends TestCase
$this->assertSame('default', $dsn->getOption('nullable', 'default'));
$this->assertSame('default', $dsn->getOption('not_existent_property', 'default'));
}
public function testGetRequiredOptionGetsOptionIfSet()
{
$options = ['with_value' => 'some value'];
$dsn = new Dsn('scheme', 'localhost', 'u$er', 'pa$s', '8000', $options, '/channel');
$this->assertSame('some value', $dsn->getRequiredOption('with_value'));
}
public function testGetRequiredOptionGetsOptionIfValueIsZero()
{
$options = ['timeout' => 0];
$dsn = new Dsn('scheme', 'localhost', 'u$er', 'pa$s', '8000', $options, '/channel');
$this->assertSame(0, $dsn->getRequiredOption('timeout'));
}
/**
* @dataProvider getRequiredOptionThrowsMissingRequiredOptionExceptionProvider
*/
public function testGetRequiredOptionThrowsMissingRequiredOptionException(string $expectedExceptionMessage, array $options, string $option)
{
$dsn = new Dsn('scheme', 'localhost', 'u$er', 'pa$s', '8000', $options, '/channel');
$this->expectException(MissingRequiredOptionException::class);
$this->expectExceptionMessage($expectedExceptionMessage);
$dsn->getRequiredOption($option);
}
public function getRequiredOptionThrowsMissingRequiredOptionExceptionProvider(): iterable
{
yield [
'The option "foo_bar" is required but missing.',
['with_value' => 'some value'],
'foo_bar',
];
yield [
'The option "with_empty_string" is required but missing.',
['with_empty_string' => ''],
'with_empty_string',
];
yield [
'The option "with_null" is required but missing.',
['with_null' => null],
'with_null',
];
}
}

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\Notifier\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Notifier\Exception\IncompleteDsnException;
use Symfony\Component\Notifier\Exception\UnsupportedSchemeException;
use Symfony\Component\Notifier\Exception\MissingRequiredOptionException;
use Symfony\Component\Notifier\Transport\Dsn;
use Symfony\Component\Notifier\Transport\TransportFactoryInterface;
@ -52,6 +53,14 @@ abstract class TransportFactoryTestCase extends TestCase
return [];
}
/**
* @return iterable<array{0: string, 1: string|null}>
*/
public function missingRequiredOptionProvider(): iterable
{
return [];
}
/**
* @dataProvider supportsProvider
*/
@ -106,4 +115,21 @@ abstract class TransportFactoryTestCase extends TestCase
$factory->create($dsn);
}
/**
* @dataProvider missingRequiredOptionProvider
*/
public function testMissingRequiredOptionException(string $dsn, string $message = null): void
{
$factory = $this->createFactory();
$dsn = Dsn::fromString($dsn);
$this->expectException(MissingRequiredOptionException::class);
if (null !== $message) {
$this->expectExceptionMessage($message);
}
$factory->create($dsn);
}
}

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Notifier\Transport;
use Symfony\Component\Notifier\Exception\InvalidArgumentException;
use Symfony\Component\Notifier\Exception\MissingRequiredOptionException;
/**
* @author Fabien Potencier <fabien@symfony.com>
@ -94,6 +95,15 @@ final class Dsn
return $this->options[$key] ?? $default;
}
public function getRequiredOption(string $key)
{
if (!\array_key_exists($key, $this->options) || '' === trim($this->options[$key])) {
throw new MissingRequiredOptionException($key);
}
return $this->options[$key];
}
public function getPath(): ?string
{
return $this->path;

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Notifier\Transport;
use Symfony\Component\Notifier\Exception\IncompleteDsnException;
use Symfony\Component\Notifier\Exception\MissingRequiredOptionException;
use Symfony\Component\Notifier\Exception\UnsupportedSchemeException;
/**
@ -22,6 +23,7 @@ interface TransportFactoryInterface
/**
* @throws UnsupportedSchemeException
* @throws IncompleteDsnException
* @throws MissingRequiredOptionException
*/
public function create(Dsn $dsn): TransportInterface;