Parse DSN strings into value objects to make them easier to use, pass around and manipulate

Related tags

Miscellaneous dsn
Overview

DSN parser

Latest Version Quality Score SymfonyInsight Total Downloads

Parse DSN strings into value objects to make them easier to use, pass around and manipulate.

Install

Via Composer

composer require nyholm/dsn

Quick usage

getHost(); // "127.0.0.1" echo $dsn->getPath(); // "/foo/bar" echo $dsn->getPort(); // null">
use Nyholm\Dsn\DsnParser;

$dsn = DsnParser::parse('http://127.0.0.1/foo/bar?key=value');
echo get_class($dsn); // "Nyholm\Dsn\Configuration\Url"
echo $dsn->getHost(); // "127.0.0.1"
echo $dsn->getPath(); // "/foo/bar"
echo $dsn->getPort(); // null

The DSN string format

A DSN is a string used to configure many services. A common DSN may look like a URL, other look like a file path.

memcached://127.0.0.1
mysql://user:[email protected]:3306/my_table
memcached:///var/local/run/memcached.socket?weight=25

Both types can have parameters, user, password. The exact definition we are using is found at the bottom of the page.

DSN Functions

A DSN may contain zero or more functions. The DSN parser supports a function syntax but not functionality itself. The function arguments must be separated with space or comma. Here are some example functions.

failover(dummy://a dummy://a)
failover(dummy://a,dummy://a)
failover:(dummy://a,dummy://a)
roundrobin(dummy://a failover(dummy://b dummy://a) dummy://b)

Parsing

There are two methods for parsing; DsnParser::parse() and DsnParser::parseFunc(). The latter is for situations where DSN functions are supported.

getHost(); // "127.0.0.1" echo $dsn->getPath(); // "/foo/bar" echo $dsn->getPort(); // null">
use Nyholm\Dsn\DsnParser;

$dsn = DsnParser::parse('scheme://127.0.0.1/foo/bar?key=value');
echo get_class($dsn); // "Nyholm\Dsn\Configuration\Url"
echo $dsn->getHost(); // "127.0.0.1"
echo $dsn->getPath(); // "/foo/bar"
echo $dsn->getPort(); // null

If functions are supported (like in the Symfony Mailer component) we can use DsnParser::parseFunc():

first()); // "Nyholm\Dsn\Configuration\Url" echo $func->first()->getHost(); // "default" echo $func->first()->getUser(); // "KEY"">
use Nyholm\Dsn\DsnParser;

$func = DsnParser::parseFunc('failover(sendgrid://KEY@default smtp://127.0.0.1)');
echo $func->getName(); // "failover"
echo get_class($func->first()); // "Nyholm\Dsn\Configuration\Url"
echo $func->first()->getHost(); // "default"
echo $func->first()->getUser(); // "KEY"
getParameters()['start']; // "now" $args = $func->getArguments(); echo get_class($args[0]); // "Nyholm\Dsn\Configuration\Url" echo $args[0]->getScheme(); // "udp" echo $args[0]->getHost(); // "localhost" echo get_class($args[1]); // "Nyholm\Dsn\Configuration\DsnFunction"">
use Nyholm\Dsn\DsnParser;

$func = DsnParser::parseFunc('foo(udp://localhost failover:(tcp://localhost:61616,tcp://remotehost:61616)?initialReconnectDelay=100)?start=now');
echo $func->getName(); // "foo"
echo $func->getParameters()['start']; // "now"

$args = $func->getArguments();
echo get_class($args[0]); // "Nyholm\Dsn\Configuration\Url"
echo $args[0]->getScheme(); // "udp"
echo $args[0]->getHost(); // "localhost"

echo get_class($args[1]); // "Nyholm\Dsn\Configuration\DsnFunction"

When using DsnParser::parseFunc() on a string that does not contain any DSN functions, the parser will automatically add a default "dsn" function. This is added to provide a consistent return type of the method.

The string redis://127.0.0.1 will automatically be converted to dsn(redis://127.0.0.1) when using DsnParser::parseFunc().

first()); // "Nyholm\Dsn\Configuration\Url" echo $func->first()->getHost(); // "127.0.0.1" $func = DsnParser::parseFunc('dsn(smtp://127.0.0.1)'); echo $func->getName(); // "dsn" echo get_class($func->first()); // "Nyholm\Dsn\Configuration\Url" echo $func->first()->getHost(); // "127.0.0.1"">
use Nyholm\Dsn\DsnParser;

$func = DsnParser::parseFunc('smtp://127.0.0.1');
echo $func->getName(); // "dsn"
echo get_class($func->first()); // "Nyholm\Dsn\Configuration\Url"
echo $func->first()->getHost(); // "127.0.0.1"


$func = DsnParser::parseFunc('dsn(smtp://127.0.0.1)');
echo $func->getName(); // "dsn"
echo get_class($func->first()); // "Nyholm\Dsn\Configuration\Url"
echo $func->first()->getHost(); // "127.0.0.1"

Parsing invalid DSN

If you try to parse an invalid DSN string a InvalidDsnException will be thrown.

use Nyholm\Dsn\DsnParser;
use Nyholm\Dsn\Exception\InvalidDsnException;

try {
  DsnParser::parse('foobar');
} catch (InvalidDsnException $e) {
  echo $e->getMessage();
}

Consuming

The result of parsing a DSN string is a DsnFunction or Dsn. A DsnFunction has a name, argument and may have parameters. An argument is either a DsnFunction or a Dsn.

A Dsn could be a Path or Url. All 3 objects has methods for getting parts of the DSN string.

  • getScheme()
  • getUser()
  • getPassword()
  • getHost()
  • getPort()
  • getPath()
  • getParameters()

You may also replace parts of the DSN with the with* methods. A DSN is immutable and you will get a new object back.

withHost('nyholm.tech'); echo $dsn->getHost(); // "127.0.0.1" echo $new->getHost(); // "nyholm.tech"">
use Nyholm\Dsn\DsnParser;

$dsn = DsnParser::parse('scheme://127.0.0.1/foo/bar?key=value');

echo $dsn->getHost(); // "127.0.0.1"
$new = $dsn->withHost('nyholm.tech');

echo $dsn->getHost(); // "127.0.0.1"
echo $new->getHost(); // "nyholm.tech"

Not supported

Smart merging of options

The current DSN is valid, but it is up to the consumer to make sure both host1 and host2 has global_option.

redis://(host1:1234,host2:1234?node2_option=a)?global_option=b

Special DSN

The following DSN syntax are not supported.

// Rust
pgsql://user:pass@tcp(localhost:5555)/dbname

// Java
jdbc:informix-sqli://[:]/:informixserver=

We do not support DSN strings for ODBC connections like:

Driver={ODBC Driver 13 for SQL Server};server=localhost;database=WideWorldImporters;trusted_connection=Yes;

However, we do support "only parameters":

ocdb://?Driver=ODBC+Driver+13+for+SQL+Server&server=localhost&database=WideWorldImporters&trusted_connection=Yes

Definition

There is no official DSN RFC. We have defined a DSN configuration string as using the following definition. The "URL looking" parts of a DSN is based from RFC 3986.

configuration:
  { function | dsn }

function:
  function_name[:](configuration[,configuration])[?query]

function_name:
  REGEX: [a-zA-Z0-9\+-]+

dsn:
  { scheme:[//]authority[path][?query] | scheme:[//][userinfo]path[?query] | host:port[path][?query] }

scheme:
  REGEX: [a-zA-Z0-9\+-\.]+

authority:
  [userinfo@]host[:port]

userinfo:
  { user[:password] | :password }

path:
  "Normal" URL path according to RFC3986 section 3.3.
  REGEX: (/? | (/[a-zA-Z0-9-\._~%!\$&'\(\}\*\+,;=:@]+)+)

query:
  "Normal" URL query according to RFC3986 section 3.4.
  REGEX: [a-zA-Z0-9-\._~%!\$&'\(\}\*\+,;=:@]+

user:
  This value can be URL encoded.
  REGEX: [a-zA-Z0-9-\._~%!\$&'\(\}\*\+,;=]+

password:
  This value can be URL encoded.
  REGEX: [a-zA-Z0-9-\._~%!\$&'\(\}\*\+,;=]+

host:
  REGEX: [a-zA-Z0-9-\._~%!\$&'\(\}\*\+,;=]+

post:
  REGEX: [0-9]+

Example of formats that are supported:

  • scheme://127.0.0.1/foo/bar?key=value
  • scheme://user:[email protected]/foo/bar?key=value
  • scheme:///var/local/run/memcached.socket?weight=25
  • scheme://user:pass@/var/local/run/memcached.socket?weight=25
  • scheme:?host[localhost]&host[localhost:12345]=3
  • scheme://a
  • scheme://
  • server:80
Comments
  • PHP 7.3 incompatibility

    PHP 7.3 incompatibility

    After upgrading to PHP 7.3 on a project with this library as a dependency, the following error is thrown:

    In DSN.php line 208:
                                                                                                 
      [ErrorException]                                                                           
      Warning: preg_match_all(): Compilation failed: invalid range in character class at offset  
       12   
    
    Exception trace:
     () at /project/vendor/nyholm/dsn/src/DSN.php:208
     Nyholm\DSN->parseHosts() at /project/vendor/nyholm/dsn/src/DSN.php:194
     Nyholm\DSN->parseDsn() at /project/vendor/nyholm/dsn/src/DSN.php:50
     Nyholm\DSN->__construct() at /project/vendor/backup-manager/symfony/Factory/ConfigFactory.php:24
     BM\BackupManagerBundle\Factory\ConfigFactory::createConfig() at /project/var/cache/dev/ContainerDttfvUA/getBackupManagerService.php:28
     require() at /project/var/cache/dev/ContainerDttfvUA/srcApp_KernelDevDebugContainer.php:891
     ContainerDttfvUA\srcApp_KernelDevDebugContainer->load() at /project/var/cache/dev/ContainerDttfvUA/getBackupManager_Command_BackupService.php:12
     require() at /project/var/cache/dev/ContainerDttfvUA/srcApp_KernelDevDebugContainer.php:891
     ContainerDttfvUA\srcApp_KernelDevDebugContainer->load() at /project/vendor/symfony/dependency-injection/Container.php:433
     Symfony\Component\DependencyInjection\Container->getService() at /project/vendor/symfony/dependency-injection/Argument/ServiceLocator.php:38
     Symfony\Component\DependencyInjection\Argument\ServiceLocator->get() at /project/vendor/symfony/console/CommandLoader/ContainerCommandLoader.php:37
     Symfony\Component\Console\CommandLoader\ContainerCommandLoader->get() at /project/vendor/symfony/console/Application.php:526
     Symfony\Component\Console\Application->has() at /project/vendor/symfony/console/Application.php:497
     Symfony\Component\Console\Application->get() at /project/vendor/symfony/framework-bundle/Console/Application.php:119
     Symfony\Bundle\FrameworkBundle\Console\Application->get() at /project/vendor/symfony/console/Application.php:667
     Symfony\Component\Console\Application->find() at /project/vendor/symfony/framework-bundle/Console/Application.php:109
     Symfony\Bundle\FrameworkBundle\Console\Application->find() at /project/vendor/symfony/console/Application.php:226
     Symfony\Component\Console\Application->doRun() at /project/vendor/symfony/framework-bundle/Console/Application.php:75
     Symfony\Bundle\FrameworkBundle\Console\Application->doRun() at /project/vendor/symfony/console/Application.php:145
     Symfony\Component\Console\Application->run() at /project/bin/console:39
    

    I suspect that the error is caused by PHP 7.3's migraton to PCRE2 as described here, but I'm not sufficiently well-versed in regular expressions to confirm it.

    opened by nmeirik 6
  • Decode parameters in URL

    Decode parameters in URL

    A DSN is a URL and therefore URL-encoded. The DSN parser, therefore, needs to decode parameters with a potential of special characters: database name, user, password.

    Take for example the following URL: mysql://test:aA%263yuA%[email protected]:8889/test The correct password for this DSN is aA&3yuA?123-2ABC.

    This PR fixes the parsing of DSNs with special characters, i.e. urlencoded URLs.

    opened by richardhj 2
  • Test enhancement, split tests, use data provider

    Test enhancement, split tests, use data provider

    Changed log

    • Split the tests are from the same test method because the one method I think do many things.
    • Use the data provider to make the test readable.
    opened by peter279k 2
  • Wrong parsing of nested functions with arguments

    Wrong parsing of nested functions with arguments

    Parsing a nested function with arguments followed by another DSN make the latter inside the argument of the former.

    For example, this DSN:

    foo(failover:(tcp://localhost:61616,tcp://remotehost:61616)?initialReconnectDelay=100 udp://localhost)?start=now`
    

    gives:

    ^ Nyholm\Dsn\Configuration\DsnFunction^ {#110
      -name: "foo"
      -arguments: array:1 [
        0 => Nyholm\Dsn\Configuration\DsnFunction^ {#72
          -name: "failover"
          -arguments: array:2 [
            0 => Nyholm\Dsn\Configuration\Url^ {#85
              -host: "localhost"
              -port: 61616
              -path: null
              -scheme: "tcp"
              -parameters: []
              -authentication: array:2 [
                "user" => null
                "password" => null
              ]
            }
            1 => Nyholm\Dsn\Configuration\Url^ {#84
              -host: "remotehost"
              -port: 61616
              -path: null
              -scheme: "tcp"
              -parameters: []
              -authentication: array:2 [
                "user" => null
                "password" => null
              ]
            }
          ]
          -parameters: array:1 [
            "initialReconnectDelay" => "100 udp://localhost" <----------- PROBLEM HERE
          ]
        }
      ]
      -parameters: array:1 [
        "start" => "now"
      ]
    }
    

    It doesn't matter if we use a comma instead of a space to separate the two arguments of foo().

    opened by chapa 1
  • Fix release notes for 2.0.0

    Fix release notes for 2.0.0

    Hi,

    The release notes for 2.0.0 show:

    // After
    $dsn = new \Nyholm\Dsn\DsnParser::parse('mysql://localhost');
    

    instead of:

    // After
    $dsn = \Nyholm\Dsn\DsnParser::parse('mysql://localhost');
    
    opened by BenMorel 1
  • Sub-delimiters incorrectly matched

    Sub-delimiters incorrectly matched

    The closing brace } is NOT allowed as a valid "sub-delim" character, but the closing parenthese ) is missing.

    See RFC RFC3986 annex A, and pull request https://github.com/Nyholm/dsn/pull/26

    opened by verdy-p 1
Releases(2.0.1)
Owner
Tobias Nyholm
Symfony Core team, certified Symfony developer. Speaker, writer, podcaster. Maintainer for many awesome libraries.
Tobias Nyholm
Share value objects that contain the same primitive value as a singleton

sharable-value-objects Share value objects that contain the same primitive value as a singleton. Singletons are kept under WeakReference objects. Inst

mpyw 5 Nov 14, 2021
PHP package to make your objects strict and throw exception when you try to access or set some undefined property in your objects.

?? Yell PHP package to make your objects strict and throw exception when you try to access or set some undefined property in your objects. Requirement

Zeeshan Ahmad 20 Dec 8, 2018
A bunch of general-purpose value objects you can use in your Laravel application.

Laravel Value Objects A bunch of general-purpose value objects you can use in your Laravel application. The package requires PHP ^8.0 and Laravel ^9.7

Michael Rubél 136 Jan 4, 2023
WordPlate is a wrapper around WordPress. It makes developers life easier. It is just like building any other WordPress website with themes and plugins. Just with sprinkles on top.

WordPlate is simply a wrapper around WordPress. It makes developers life easier. It is just like building any other WordPress website with themes and plugins. Just with sprinkles on top.

WordPlate 1.7k Dec 24, 2022
A set of classes to create and manipulate HTML objects abstractions

HTMLObject HTMLObject is a set of classes to create and manipulate HTML objects abstractions. Static calls to the classes echo Element::p('text')->cla

Emma Fabre 128 Dec 22, 2022
Decimal handling as value object instead of plain strings.

Decimal Object Decimal value object for PHP. Background When working with monetary values, normal data types like int or float are not suitable for ex

Spryker 16 Oct 24, 2022
A PHP package to make the Chronopost API easier to use.

Chronopost API A PHP package to make the Chronopost API easier to use. Table of Contents Requirements Installation Usage Testing Requirements PHP 7.3,

HOUIS Mathis 9 Oct 27, 2022
Allows generate class files parse from json and map json to php object, including multi-level and complex objects;

nixihz/php-object Allows generate class files parse from json and map json to php object, including multi-level and complex objects; Installation You

zhixin 2 Sep 9, 2022
Dobren Dragojević 6 Jun 11, 2023
⭐ It is an platform for people to help them get connected with the like minding folks around the globe.

Meetups It is an Platform for people to help them get connected with the like minded folks around the globe. Live on Web: Cick here ?? Meet and Connec

Hardik Kaushik 5 Apr 26, 2022
Collection of value objects that represent the types of the PHP type system

sebastian/type Collection of value objects that represent the types of the PHP type system. Installation You can add this library as a local, per-proj

Sebastian Bergmann 1.1k Dec 29, 2022
A PHP 7 value objects helper library.

valueobjects Requirements Requires PHP >= 7.1 Installation Through Composer, obviously: composer require funeralzone/valueobjects Extensions This lib

Funeral Zone 56 Dec 16, 2022
A plugin to make life easier for users who need to edit specific functions of a world and also create, rename and delete worlds quickly using commands or the world management menu.

A plugin to make life easier for users who need to edit specific functions of a world and also create, rename and delete worlds quickly using commands or the world management menu.

ImperaZim 0 Nov 6, 2022
A PHP Library To Make Your Work Work Easier/Faster

This Is A Php Library To Make Your Work Easier/Faster,

functionality 2 Dec 30, 2022
A trait to make building your own custom Laravel Model Searches a lot easier.

BrekiTomasson\LaravelModelFinder A simple trait that makes building your own custom Laravel Model Searches a lot easier and safer. It ensures that you

Breki Tomasson 3 Nov 27, 2022
Safely break down arrays or objects, and put them back together in new shapes.

traverse/reshape traverse() and reshape() are companion functions that safely break down arrays or objects and put them back together in new shapes. t

Alley Interactive 2 Aug 4, 2022
Creating data transfer objects with the power of php objects. No php attributes, no reflection api, and no other under the hook work.

Super Simple DTO Creating data transfer objects with the power of php objects. No php attributes, no reflection api, and no other under the hook work.

Mohammed Manssour 8 Jun 8, 2023
PHP library that helps to map any input into a strongly-typed value object structure.

Valinor • PHP object mapper with strong type support Valinor is a PHP library that helps to map any input into a strongly-typed value object structure

Team CuyZ 873 Jan 7, 2023