1# DSN parser 2 3[![Latest Version](https://img.shields.io/github/release/Nyholm/dsn.svg?style=flat-square)](https://github.com/Nyholm/dsn/releases) 4[![Quality Score](https://img.shields.io/scrutinizer/g/Nyholm/dsn.svg?style=flat-square)](https://scrutinizer-ci.com/g/Nyholm/dsn) 5[![SymfonyInsight](https://insight.symfony.com/projects/fe1a70b7-6ba9-424d-9217-53833e47b07f/mini.svg)](https://insight.symfony.com/projects/fe1a70b7-6ba9-424d-9217-53833e47b07f) 6[![Total Downloads](https://img.shields.io/packagist/dt/nyholm/dsn.svg?style=flat-square)](https://packagist.org/packages/nyholm/dsn) 7 8Parse DSN strings into value objects to make them easier to use, pass around and 9manipulate. 10 11## Install 12 13Via Composer 14 15``` bash 16composer require nyholm/dsn 17``` 18 19## Quick usage 20 21```php 22use Nyholm\Dsn\DsnParser; 23 24$dsn = DsnParser::parse('http://127.0.0.1/foo/bar?key=value'); 25echo get_class($dsn); // "Nyholm\Dsn\Configuration\Url" 26echo $dsn->getHost(); // "127.0.0.1" 27echo $dsn->getPath(); // "/foo/bar" 28echo $dsn->getPort(); // null 29``` 30 31## The DSN string format 32A DSN is a string used to configure many services. A common DSN may look like a 33URL, other look like a file path. 34 35```text 36memcached://127.0.0.1 37mysql://user:password@127.0.0.1:3306/my_table 38memcached:///var/local/run/memcached.socket?weight=25 39``` 40 41Both types can have parameters, user, password. The exact definition we are using 42is found [at the bottom of the page](#definition). 43 44### DSN Functions 45 46A DSN may contain zero or more functions. The DSN parser supports a function syntax 47but not functionality itself. The function arguments must be separated with space 48or comma. Here are some example functions. 49 50``` 51failover(dummy://a dummy://a) 52failover(dummy://a,dummy://a) 53failover:(dummy://a,dummy://a) 54roundrobin(dummy://a failover(dummy://b dummy://a) dummy://b) 55``` 56 57## Parsing 58 59There are two methods for parsing; `DsnParser::parse()` and `DsnParser::parseFunc()`. 60The latter is for situations where DSN functions are supported. 61 62```php 63use Nyholm\Dsn\DsnParser; 64 65$dsn = DsnParser::parse('scheme://127.0.0.1/foo/bar?key=value'); 66echo get_class($dsn); // "Nyholm\Dsn\Configuration\Url" 67echo $dsn->getHost(); // "127.0.0.1" 68echo $dsn->getPath(); // "/foo/bar" 69echo $dsn->getPort(); // null 70``` 71 72If functions are supported (like in the Symfony Mailer component) we can use `DsnParser::parseFunc()`: 73 74```php 75use Nyholm\Dsn\DsnParser; 76 77$func = DsnParser::parseFunc('failover(sendgrid://KEY@default smtp://127.0.0.1)'); 78echo $func->getName(); // "failover" 79echo get_class($func->first()); // "Nyholm\Dsn\Configuration\Url" 80echo $func->first()->getHost(); // "default" 81echo $func->first()->getUser(); // "KEY" 82``` 83 84```php 85use Nyholm\Dsn\DsnParser; 86 87$func = DsnParser::parseFunc('foo(udp://localhost failover:(tcp://localhost:61616,tcp://remotehost:61616)?initialReconnectDelay=100)?start=now'); 88echo $func->getName(); // "foo" 89echo $func->getParameters()['start']; // "now" 90 91$args = $func->getArguments(); 92echo get_class($args[0]); // "Nyholm\Dsn\Configuration\Url" 93echo $args[0]->getScheme(); // "udp" 94echo $args[0]->getHost(); // "localhost" 95 96echo get_class($args[1]); // "Nyholm\Dsn\Configuration\DsnFunction" 97``` 98 99When using `DsnParser::parseFunc()` on a string that does not contain any DSN functions, 100the parser will automatically add a default "dsn" function. This is added to provide 101a consistent return type of the method. 102 103The string `redis://127.0.0.1` will automatically be converted to `dsn(redis://127.0.0.1)` 104when using `DsnParser::parseFunc()`. 105 106```php 107use Nyholm\Dsn\DsnParser; 108 109$func = DsnParser::parseFunc('smtp://127.0.0.1'); 110echo $func->getName(); // "dsn" 111echo get_class($func->first()); // "Nyholm\Dsn\Configuration\Url" 112echo $func->first()->getHost(); // "127.0.0.1" 113 114 115$func = DsnParser::parseFunc('dsn(smtp://127.0.0.1)'); 116echo $func->getName(); // "dsn" 117echo get_class($func->first()); // "Nyholm\Dsn\Configuration\Url" 118echo $func->first()->getHost(); // "127.0.0.1" 119``` 120 121### Parsing invalid DSN 122 123If you try to parse an invalid DSN string a `InvalidDsnException` will be thrown. 124 125```php 126use Nyholm\Dsn\DsnParser; 127use Nyholm\Dsn\Exception\InvalidDsnException; 128 129try { 130 DsnParser::parse('foobar'); 131} catch (InvalidDsnException $e) { 132 echo $e->getMessage(); 133} 134``` 135 136## Consuming 137 138The result of parsing a DSN string is a `DsnFunction` or `Dsn`. A `DsnFunction` has 139a `name`, `argument` and may have `parameters`. An argument is either a `DsnFunction` 140or a `Dsn`. 141 142A `Dsn` could be a `Path` or `Url`. All 3 objects has methods for getting parts of 143the DSN string. 144 145- `getScheme()` 146- `getUser()` 147- `getPassword()` 148- `getHost()` 149- `getPort()` 150- `getPath()` 151- `getParameters()` 152 153You may also replace parts of the DSN with the `with*` methods. A DSN is immutable 154and you will get a new object back. 155 156```php 157use Nyholm\Dsn\DsnParser; 158 159$dsn = DsnParser::parse('scheme://127.0.0.1/foo/bar?key=value'); 160 161echo $dsn->getHost(); // "127.0.0.1" 162$new = $dsn->withHost('nyholm.tech'); 163 164echo $dsn->getHost(); // "127.0.0.1" 165echo $new->getHost(); // "nyholm.tech" 166``` 167 168## Not supported 169 170### Smart merging of options 171 172The current DSN is valid, but it is up to the consumer to make sure both host1 and 173host2 has `global_option`. 174 175``` 176redis://(host1:1234,host2:1234?node2_option=a)?global_option=b 177``` 178 179### Special DSN 180 181The following DSN syntax are not supported. 182 183``` 184// Rust 185pgsql://user:pass@tcp(localhost:5555)/dbname 186 187// Java 188jdbc:informix-sqli://<server>[:<port>]/<databaseName>:informixserver=<dbservername> 189 190``` 191 192We do not support DSN strings for ODBC connections like: 193 194``` 195Driver={ODBC Driver 13 for SQL Server};server=localhost;database=WideWorldImporters;trusted_connection=Yes; 196``` 197 198However, we do support "only parameters": 199 200``` 201ocdb://?Driver=ODBC+Driver+13+for+SQL+Server&server=localhost&database=WideWorldImporters&trusted_connection=Yes 202``` 203 204## Definition 205 206There is no official DSN RFC. We have defined a DSN configuration string as 207using the following definition. The "URL looking" parts of a DSN is based from 208[RFC 3986](https://tools.ietf.org/html/rfc3986). 209 210 211``` 212configuration: 213 { function | dsn } 214 215function: 216 function_name[:](configuration[,configuration])[?query] 217 218function_name: 219 REGEX: [a-zA-Z0-9\+-]+ 220 221dsn: 222 { scheme:[//]authority[path][?query] | scheme:[//][userinfo]path[?query] | host:port[path][?query] } 223 224scheme: 225 REGEX: [a-zA-Z0-9\+-\.]+ 226 227authority: 228 [userinfo@]host[:port] 229 230userinfo: 231 { user[:password] | :password } 232 233path: 234 "Normal" URL path according to RFC3986 section 3.3. 235 REGEX: (/? | (/[a-zA-Z0-9-\._~%!\$&'\(\}\*\+,;=:@]+)+) 236 237query: 238 "Normal" URL query according to RFC3986 section 3.4. 239 REGEX: [a-zA-Z0-9-\._~%!\$&'\(\}\*\+,;=:@]+ 240 241user: 242 This value can be URL encoded. 243 REGEX: [a-zA-Z0-9-\._~%!\$&'\(\}\*\+,;=]+ 244 245password: 246 This value can be URL encoded. 247 REGEX: [a-zA-Z0-9-\._~%!\$&'\(\}\*\+,;=]+ 248 249host: 250 REGEX: [a-zA-Z0-9-\._~%!\$&'\(\}\*\+,;=]+ 251 252post: 253 REGEX: [0-9]+ 254 255``` 256 257Example of formats that are supported: 258 259- scheme://127.0.0.1/foo/bar?key=value 260- scheme://user:pass@127.0.0.1/foo/bar?key=value 261- scheme:///var/local/run/memcached.socket?weight=25 262- scheme://user:pass@/var/local/run/memcached.socket?weight=25 263- scheme:?host[localhost]&host[localhost:12345]=3 264- scheme://a 265- scheme:// 266- server:80 267