PHPackages                             zenstruck/dsn - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. zenstruck/dsn

ActiveLibrary

zenstruck/dsn
=============

DSN parsing library with support for complex expressions.

v1.0.0(5mo ago)430.3k—3.2%32MITPHPPHP &gt;=8.1CI passing

Since Jul 27Pushed 3mo ago2 watchersCompare

[ Source](https://github.com/zenstruck/dsn)[ Packagist](https://packagist.org/packages/zenstruck/dsn)[ Docs](https://github.com/zenstruck/dsn)[ GitHub Sponsors](https://github.com/kbond)[ GitHub Sponsors](https://github.com/nikophil)[ RSS](/packages/zenstruck-dsn/feed)WikiDiscussions 1.x Synced 1mo ago

READMEChangelog (3)Dependencies (7)Versions (4)Used By (2)

zenstruck/dsn
=============

[](#zenstruckdsn)

[![CI](https://github.com/zenstruck/dsn/actions/workflows/ci.yml/badge.svg)](https://github.com/zenstruck/dsn/actions/workflows/ci.yml)[![codecov](https://camo.githubusercontent.com/c22a540b17e2529d82a8736b57c18bba2dae78d4cb26db778fa9694aa0e85af9/68747470733a2f2f636f6465636f762e696f2f67682f7a656e73747275636b2f64736e2f6272616e63682f312e782f67726170682f62616467652e7376673f746f6b656e3d75586b35786750683749)](https://codecov.io/gh/zenstruck/dsn)

DSN parsing library with support for complex expressions:

1. [**URI**](#uri): `http://example.com?foo=bar#baz`
2. [**Mailto**](#mailto): `mailto:sam@example.com?cc=jane@example.com`
3. *DSN Functions*:
    1. [**Decorated**](#decorated): `retry(inner://dsn)?times=5`
    2. [**Group**](#group): `round+robin(inner://dsn1 inner://dsn2)`
    3. [**Complex**](#complex-dsns): `fail+over(rount+robin(inner://dsn1 inner://dsn2) inner://dsn3)`

Installation
------------

[](#installation)

```
composer require zenstruck/dsn
```

Usage
-----

[](#usage)

### Parsing DSNs

[](#parsing-dsns)

For basic usage, you can use `Zenstruck\Dsn::parse($mydsn)`. This takes a `string`and returns *one of* the following objects:

- [`Zenstruck\Uri`](#uri)
- [`Zenstruck\Uri\Mailto`](#mailto)
- [`Zenstruck\Dsn\Decorated`](#decorated)
- [`Zenstruck\Dsn\Group`](#group)

The only thing in common with these returned objects is that they are all `\Stringable`.

If the parsing fails, a `Zenstruck\Dsn\Exception\UnableToParse` exception will be thrown.

> **Note**See [`zenstruck/uri`](https://github.com/zenstruck/uri) to view the API for `Uri|Mailto`.

#### URI

[](#uri)

This DSN object is an instance of `Zenstruck\Uri`. View it's [full API documentation](https://github.com/zenstruck/uri#parsingreading-uris).

```
$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

[](#mailto)

This DSN object is an instance of `Zenstruck\Uri\Mailto`. View it's [full API documentation](https://github.com/zenstruck/uri#mailto-uris).

```
$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'
```

#### Decorated

[](#decorated)

This is a *DSN Function* that wraps a single *inner* DSN:

```
retry(inner://dsn)?times=5

```

The above example would parse to a `Zenstruck\Dsn\Decorated` object with the following properties:

- *Scheme/Function Name*: `retry`
- *Query*: `['times' => '5']`
- *Inner DSN*: This will be an instance of `Zenstruck\Uri` in this case but could be any [*DSN Object*](#parsing-dsns).

```
$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'
```

#### Group

[](#group)

This is a *DSN Function* that wraps a multiple *inner* DSNs (space separated):

```
round+robin(inner://dsn1 inner://dsn2)?strategy=random

```

The above example would parse to a `Zenstruck\Dsn\Group` object with the following properties:

- *Scheme/Function Name*: `round+robin`
- *Query*: `['strategy' => 'random']`
- *Child DSNs*: This will be an `array` of *2* `Zenstruck\Uri` objects in this case but could an array of any [*DSN Objects*](#parsing-dsns).

```
$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'
```

#### Complex DSNs

[](#complex-dsns)

You can nest [Group](#group) and [Decorated](#decorated) DSNs to create complex expressions:

```
$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'
```

### Using Parsed DSNs

[](#using-parsed-dsns)

Once parsed, you can use an `instanceof` check to determine the type of DSN that was parsed and act accordingly:

```
$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)
}
```

#### Usage Example

[](#usage-example)

The best way to show how the parsed DSN could be used for something useful is with an example. Consider an email abstraction library that has multiple *service transports* (**smtp**, **mailchimp**, **postmark**) and special *utility transports*: **round-robin** (for distributing workload between multiple transports) and **retry**(for retrying failures x times before hard-failing).

You'd like end user's of this library to be able to create transports from a custom DSN syntax. The following is an example of a transport DSN factory:

```
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}.");
    }
}
```

The usage of this factory is as follows:

```
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');
```

Advanced Usage
--------------

[](#advanced-usage)

Under the hood `Zenstruck\Dsn::parse()` uses a parsing system for converting DSN strings to the packaged [DSN objects](#parsing-dsns). You can create your own parsers by having them implement the `Zenstruck\Dsn\Parser` interface.

> **Note**`Zenstruck\Dsn::parse()` is a utility function that only uses the [core parsers](#core-parsers). In order to add your [own parsers](#custom-parsers), you'll need to manually wire up a [chain parser](#chainparser) that includes them and use this for parsing DSNs.

### Core Parsers

[](#core-parsers)

#### `UriParser`

[](#uriparser)

Converts url-looking strings to [`Zenstruck\Uri`](#uri) objects.

#### `MailtoParser`

[](#mailtoparser)

Converts mailto-looking strings to [`Zenstruck\Uri\Mailto`](#mailto) objects.

#### `WrappedParser`

[](#wrappedparser)

Converts dsn-function-looking strings to [`Zenstruck\Dsn\Decorated`](#decorated) or [`Zenstruck\Dsn\Group`](#group) objects.

### Utility Parsers

[](#utility-parsers)

#### `ChainParser`

[](#chainparser)

Wraps a chain of parsers, during `parse()` it loops through these and attempts to find one that successfully parses a DSN string. It is considered successful if a `\Stringable` object is returned. If the parser throws a `Zenstruck\Dsn\Exception\UnableToParse` exception, the next parser in the chain is tried. Finally, if all the parsers throw `UnableToParse`, this is thrown.

```
$parser = new Zenstruck\Dsn\Parser\ChainParser([$customParser1, $customParser1]);

$parser->parse('some-dsn'); // \Stringable object
```

> **Note**This parser always contains the [core parsers](#core-parsers) as the last items in the chain. [Custom parsers](#custom-parsers) you add to the constructor are attempted before these.

#### `CacheParser`

[](#cacheparser)

Wraps another parser and an instance of one of these cache interfaces:

- `Symfony\Contracts\Cache\CacheInterface` (Symfony cache)
- `Psr\Cache\CacheItemPoolInterface` (PSR-6 cache)
- `Psr\SimpleCache\CacheInterface` (PSR-16 cache)

The parsed object is cached (keyed by the DSN string) and subsequent parsing of the same string are retrieved from the cache. This gives a bit of a performance boost especially for [complex DSNs](#complex-dsns).

```
/** @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)
```

### Custom Parsers

[](#custom-parsers)

You can create your own parser by creating an object that implements `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
    }
}
```

Usage:

```
// 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)

A Symfony Bundle is provided that adds an autowireable `Zenstruck\Dsn\Parser` service. This is an interface with a `parse(string $dsn)` method. It works identically to `Zenstruck\Dsn::parse()` but caches the created *DSN object* (using `cache.system`) for a bit of a performance boost.

To use, enable the bundle:

```
// config/bundles.php

return [
    // ...
    Zenstruck\Dsn\Bridge\Symfony\ZenstruckDsnBundle::class => ['all' => true],
];
```

`Zenstruck\Dsn\Parser` can be autowired:

```
use Zenstruck\Dsn\Parser;

public function myAction(Parser $parser): Response
{
    // ...

    $dsn = $parser->parse(...);

    // ...
}
```

### DSN Service Factory

[](#dsn-service-factory)

You can use the `Zenstruck\Dsn\Parser` service as a service factory to create DSN service objects:

```
# config/services.yaml

services:
    mailer_dsn:
        factory: ['@Zenstruck\Dsn\Parser', 'parse']
        arguments: ['%env(MAILER_DSN)%']
```

The `mailer_dsn` service will be an instance of a parsed DSN object. The type depends on the value of the `MAILER_DSN` environment variable.

Using the [mailer transport factory above](#usage-example), we can create the transport via a service factory that uses the `mailer_dsn`:

```
# config/services.yaml

services:
    App\Mailer\TransportFactory: ~

    App\Mailer\TransportInterface:
        factory: ['@App\Mailer\TransportFactory', 'create']
        arguments: ['@mailer_dsn']
```

Now, when injecting `App\Mailer\TransportInterface`, the transport will be created by `App\Mailer\TransportFactory` using your `MAILER_DSN` environment variable.

###  Health Score

50

—

FairBetter than 96% of packages

Maintenance78

Regular maintenance activity

Popularity35

Limited adoption so far

Community17

Small or concentrated contributor base

Maturity58

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 90.9% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~433 days

Total

4

Last Release

92d ago

Major Versions

v0.2.0 → v1.0.02025-12-07

PHP version history (2 changes)v0.1.0PHP &gt;=8.0

v1.0.0PHP &gt;=8.1

### Community

Maintainers

![](https://www.gravatar.com/avatar/707369cc916e0ea1aacbf077dcba464f611cef879f024d8944311a54a15224b3?d=identicon)[kbond](/maintainers/kbond)

---

Top Contributors

[![kbond](https://avatars.githubusercontent.com/u/127811?v=4)](https://github.com/kbond "kbond (30 commits)")[![OskarStark](https://avatars.githubusercontent.com/u/995707?v=4)](https://github.com/OskarStark "OskarStark (2 commits)")[![tacman](https://avatars.githubusercontent.com/u/619585?v=4)](https://github.com/tacman "tacman (1 commits)")

---

Tags

dsn

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/zenstruck-dsn/health.svg)

```
[![Health](https://phpackages.com/badges/zenstruck-dsn/health.svg)](https://phpackages.com/packages/zenstruck-dsn)
```

###  Alternatives

[enqueue/dsn

Parse DSN

12525.5M45](/packages/enqueue-dsn)[nyholm/dsn

Parse your DSN strings in a powerful and flexible way

10622.9M34](/packages/nyholm-dsn)[friendsofopentelemetry/opentelemetry-bundle

Traces, metrics, and logs instrumentation within your Symfony application

638.6k](/packages/friendsofopentelemetry-opentelemetry-bundle)[galata90/php-mail-bounce-handler

PHP class to help webmasters handle bounce-back, feedback loop and ARF mails in standard DSN (Delivery Status Notification, RFC-1894).

122.4k](/packages/galata90-php-mail-bounce-handler)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
