zenstruck / dsn
支持复杂表达式的DSN解析库。
Requires
- php: >=8.0
- zenstruck/uri: ^2.0
Requires (Dev)
- phpstan/phpstan: ^1.4
- phpunit/phpunit: ^9.5.0
- symfony/cache: ^5.4|^6.0|^7.0
- symfony/framework-bundle: ^5.4|^6.0|^7.0
- symfony/phpunit-bridge: ^6.0|^7.0
- symfony/var-dumper: ^5.4|^6.0|^7.0
README
支持复杂表达式的DSN解析库
安装
composer require zenstruck/dsn
使用
解析DSN
对于基本使用,你可以使用 Zenstruck\Dsn::parse($mydsn)
。它接受一个 string
并返回以下对象之一
这些返回对象共同的特点是它们都是 \Stringable
。
如果解析失败,将抛出 Zenstruck\Dsn\Exception\UnableToParse
异常。
注意 请参阅
zenstruck/uri
以查看Uri|Mailto
的API。
URI
此DSN对象是 Zenstruck\Uri
的实例。查看它的完整API文档。
$dsn = Zenstruck\Dsn::parse('https://example.com/some/dir/file.html?q=abc&flag=1#test') /* @var Zenstruck\Uri $dsn */ $dsn->scheme()->toString(); // 'https' $dsn->host()->toString(); // 'example.com' $dsn->path()->toString(); // /some/dir/file.html $dsn->query()->all(); // ['q' => 'abc', 'flag' => '1'] $dsn->fragment(); // 'test'
Mailto
此DSN对象是 Zenstruck\Uri\Mailto
的实例。查看它的完整API文档。
$dsn = Zenstruck\Dsn::parse('mailto:kevin@example.com?cc=jane@example.com&subject=some+subject&body=some+body') /** @var Zenstruck\Uri\Mailto $dsn */ $dsn->to(); // ['kevin@example.com'] $dsn->cc(); // ['jane@example.com'] $dsn->bcc(); // [] $dsn->subject(); // 'some subject' $dsn->body(); // 'some body'
装饰
这是一个包装单个 内部 DSN 的 DSN函数。
retry(inner://dsn)?times=5
上面的示例将解析为具有以下属性的 Zenstruck\Dsn\Decorated
对象
- 方案/函数名:
retry
- 查询:
['times' => '5']
- 内部DSN: 这将是此情况下的
Zenstruck\Uri
实例,但可能是任何 DSN对象。
$dsn = Zenstruck\Dsn::parse('retry(inner://dsn)?times=5'); /** @var Zenstruck\Dsn\Decorated $dsn */ $dsn->scheme()->toString(); // 'retry' $dsn->query()->all(); // ['times' => '5'] $inner = $dsn->inner(); /** @var Zenstruck\Uri $inner */ $inner->scheme()->toString(); // 'inner' $inner->host()->toString(); // 'dsn'
分组
这是一个包装多个 内部 DSN(用空格分隔)的 DSN函数。
round+robin(inner://dsn1 inner://dsn2)?strategy=random
上面的示例将解析为具有以下属性的 Zenstruck\Dsn\Group
对象
- 方案/函数名:
round+robin
- 查询:
['strategy' => 'random']
- 子DSN: 这将是此情况下的2个
Zenstruck\Uri
对象的数组,但可以是任何 DSN对象 的数组。
$dsn = Zenstruck\Dsn::parse('round+robin(inner://dsn1 inner://dsn2)?strategy=random'); /** @var Zenstruck\Dsn\Group $dsn */ $dsn->scheme()->toString(); // 'round+robin' $dsn->query()->all(); // ['strategy' => 'random'] $children = $dsn->children(); /** @var Zenstruck\Uri[] $children */ $children[0]->scheme()->toString(); // 'inner' $children[0]->host()->toString(); // 'dsn1' $children[1]->scheme()->toString(); // 'inner' $children[1]->host()->toString(); // 'dsn2'
复杂DSN
$dsn = Zenstruck\Dsn::parse('retry(round+robin(inner://dsn1 inner://dsn2)?strategy=random)?times=5'); /** @var Zenstruck\Dsn\Decorated $dsn */ $dsn->scheme()->toString(); // 'retry' $dsn->query()->all(); // ['times' => '5'] $inner = $dsn->inner(); /** @var Zenstruck\Dsn\Group $inner */ $inner->scheme()->toString(); // 'round+robin' $inner->query()->all(); // ['strategy' => 'random'] $children = $inner->children(); /** @var Zenstruck\Uri[] $children */ $children[0]->scheme()->toString(); // 'inner' $children[0]->host()->toString(); // 'dsn1' $children[1]->scheme()->toString(); // 'inner' $children[1]->host()->toString(); // 'dsn2'
使用解析后的DSN
一旦解析,你可以使用 instanceof
检查来确定解析的DSN类型,并相应地操作。
$dsn = Zenstruck\Dsn::parse($someDsnString); // throws Zenstruck\Dsn\Exception\UnableToParse on failure switch (true) { case $dsn instanceof Zenstruck\Uri: // do something with the Uri object case $dsn instanceof Zenstruck\Uri\Mailto: // do something with the Mailto object case $dsn instanceof Decorated: // do something with the Decorated object (see api below) case $dsn instanceof Group: // do something with the Group object (see api below) }
使用示例
展示解析后的DSN如何用于有用的示例的最佳方式是使用示例。考虑一个具有多个 服务传输(smtp、mailchimp、postmark)和特殊 实用传输(round-robin(用于在多个传输之间分配工作负载)和 retry(在硬失败之前重试失败x次))的电子邮件抽象库。
您希望该库的最终用户能够使用自定义DSN语法创建传输。以下是一个传输DSN工厂的示例
use Zenstruck\Dsn\Decorated; use Zenstruck\Dsn\Group; use Zenstruck\Uri; class TransportFactory { public function create(\Stringable $dsn): TransportInterface { if ($dsn instanceof Uri && $dsn->scheme()->equals('smtp')) { return new SmtpTransport( host: $dsn->host()->toString(), user: $dsn->user(), password: $dsn->pass(), port: $dsn->port(), ); } if ($dsn instanceof Uri && $dsn->scheme()->equals('mailchimp')) { return new MailchimpTransport(apiKey: $dsn->user()); } if ($dsn instanceof Uri && $dsn->scheme()->equals('postmark')) { return new PostmarkTransport(apiKey: $dsn->user()); } if ($dsn instanceof Decorated && $dsn->scheme()->equals('retry')) { return new RetryTransport( transport: $this->create($dsn->inner()), // recursively build inner transport times: $dsn->query()->getInt('times', 5), // default to 5 retries if not set ); } if ($dsn instanceof Group && $dsn->scheme()->equals('round+robin')) { return new RoundRobinTransport( transports: array_map(fn($dsn) => $this->create($dsn), $dsn->children()), // recursively build inner transports strategy: $dsn->query()->get('strategy', 'random'), // default to "random" strategy if not set ); } throw new \LogicException("Unable to parse transport DSN: {$dsn}."); } }
该工厂的使用方法如下
use Zenstruck\Dsn; // SmtpTransport: $factory->create('smtp://kevin:p4ssword@localhost'); // RetryTransport wrapping SmtpTransport: $factory->create('retry(smtp://kevin:p4ssword@localhost)'); // RetryTransport (3 retries) wrapping RoundRobinTransport (sequential strategy) wrapping MailchimpTransport & PostmarkTransport $factory->create('retry(round+robin(mailchimp://key@default postmark://key@default)?strategy=sequential)?times=3');
高级使用
在底层,Zenstruck\Dsn::parse()
使用一个解析系统将DSN字符串转换为打包的 DSN对象。您可以通过让它们实现 Zenstruck\Dsn\Parser
接口来创建自己的解析器。
注意
Zenstruck\Dsn::parse()
是一个实用函数,它只使用 核心解析器。为了添加您的 自定义解析器,您需要手动设置一个包含它们的 链式解析器 并使用它来解析DSN。
核心解析器
UriParser
将看起来像URL的字符串转换为 Zenstruck\Uri
对象。
MailtoParser
将看起来像mailto的字符串转换为 Zenstruck\Uri\Mailto
对象。
WrappedParser
将看起来像dsn函数的字符串转换为 Zenstruck\Dsn\Decorated
或 Zenstruck\Dsn\Group
对象。
实用解析器
ChainParser
包装一串解析器,在 parse()
期间,它会遍历这些解析器并尝试找到一个成功解析DSN字符串的解析器。如果返回了一个 \Stringable
对象,则被认为是成功的。如果解析器抛出了 Zenstruck\Dsn\Exception\UnableToParse
异常,则尝试链中的下一个解析器。最后,如果所有解析器都抛出了 UnableToParse
,则抛出此异常。
$parser = new Zenstruck\Dsn\Parser\ChainParser([$customParser1, $customParser1]); $parser->parse('some-dsn'); // \Stringable object
CacheParser
包装另一个解析器和一个这些缓存接口之一的实例
Symfony\Contracts\Cache\CacheInterface
(Symfony缓存)Psr\Cache\CacheItemPoolInterface
(PSR-6缓存)Psr\SimpleCache\CacheInterface
(PSR-16缓存)
解析的对象将被缓存(键为DSN字符串),并且对同一字符串的后续解析将从缓存中检索。这为 复杂DSN 提供了一点点性能提升。
/** @var SymfonyCache|Psr6Cache|Psr16Cache $cache */ /** @var Zenstruck\Dsn\Parser $inner */ $parser = new \Zenstruck\Dsn\Parser\CacheParser($parser, $cache); $parser->parse('some-dsn'); // \Stringable (caches this object) $parser->parse('some-dsn'); // \Stringable (retrieved from cache)
自定义解析器
您可以通过创建一个实现 Zenstruck\Dsn\Parser
的对象来创建自己的解析器
use Zenstruck\Dsn\Exception\UnableToParse; use Zenstruck\Dsn\Parser; class MyParser implements Parser { public function parse(string $dsn): \Stringable { // determine if $dsn is parsable and return a \Stringable DSN object throw UnableToParse::value($dsn); // important when using in a chain parser } }
使用
// standalone $parser = new MyParser(); $parser->parse('some-dsn'); // add to ChainParser $parser = new Zenstruck\Dsn\Parser\ChainParser([new MyParser()]); $parser->parse('some-dsn');
Symfony Bundle
提供了一个Symfony Bundle,它添加了一个可自动注入的 Zenstruck\Dsn\Parser
服务。这是一个具有 parse(string $dsn)
方法的接口。它与 Zenstruck\Dsn::parse()
的工作方式相同,但通过使用 cache.system
缓存创建的 DSN对象 来提高一点点性能。
要使用,请启用该bundle
// config/bundles.php return [ // ... Zenstruck\Dsn\Bridge\SymfonyZenstruckDsnBundle::class => ['all' => true], ];
Zenstruck\Dsn\Parser
可以自动注入
use Zenstruck\Dsn\Parser; public function myAction(Parser $parser): Response { // ... $dsn = $parser->parse(...); // ... }
DSN服务工厂
您可以使用 Zenstruck\Dsn\Parser
服务作为服务工厂来创建DSN服务对象
# config/services.yaml services: mailer_dsn: factory: ['@Zenstruck\Dsn\Parser', 'parse'] arguments: ['%env(MAILER_DSN)%']
mailer_dsn
服务将是一个解析后的DSN对象的实例。类型取决于 MAILER_DSN
环境变量的值。
使用上面的 mailer传输工厂,我们可以通过使用 mailer_dsn
的服务工厂来创建传输
# config/services.yaml services: App\Mailer\TransportFactory: ~ App\Mailer\TransportInterface: factory: ['@App\Mailer\TransportFactory', 'create'] arguments: ['@mailer_dsn']
现在,当注入 App\Mailer\TransportInterface
时,传输将通过 App\Mailer\TransportFactory
使用您的 MAILER_DSN
环境变量来创建。