├── .github └── workflows │ └── ci.yml ├── Dsn.php ├── InvalidQueryParameterTypeException.php ├── LICENSE ├── QueryBag.php ├── README.md └── composer.json /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - master 7 | jobs: 8 | tests: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | php: ['7.4', '8.0', '8.1', '8.2'] 14 | 15 | name: PHP ${{ matrix.php }} tests 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | 20 | - uses: shivammathur/setup-php@v2 21 | with: 22 | php-version: ${{ matrix.php }} 23 | coverage: none 24 | 25 | - uses: "ramsey/composer-install@v1" 26 | with: 27 | composer-options: "--prefer-source" 28 | 29 | - run: vendor/bin/phpunit 30 | -------------------------------------------------------------------------------- /Dsn.php: -------------------------------------------------------------------------------- 1 | scheme = $scheme; 72 | $this->schemeProtocol = $schemeProtocol; 73 | $this->schemeExtensions = $schemeExtensions; 74 | $this->user = $user; 75 | $this->password = $password; 76 | $this->host = $host; 77 | $this->port = $port; 78 | $this->path = $path; 79 | $this->queryString = $queryString; 80 | $this->queryBag = new QueryBag($query); 81 | } 82 | 83 | public function getScheme(): string 84 | { 85 | return $this->scheme; 86 | } 87 | 88 | public function getSchemeProtocol(): string 89 | { 90 | return $this->schemeProtocol; 91 | } 92 | 93 | public function getSchemeExtensions(): array 94 | { 95 | return $this->schemeExtensions; 96 | } 97 | 98 | public function hasSchemeExtension(string $extension): bool 99 | { 100 | return in_array($extension, $this->schemeExtensions, true); 101 | } 102 | 103 | public function getUser(): ?string 104 | { 105 | return $this->user; 106 | } 107 | 108 | public function getPassword(): ?string 109 | { 110 | return $this->password; 111 | } 112 | 113 | public function getHost(): ?string 114 | { 115 | return $this->host; 116 | } 117 | 118 | public function getPort(): ?int 119 | { 120 | return $this->port; 121 | } 122 | 123 | public function getPath(): ?string 124 | { 125 | return $this->path; 126 | } 127 | 128 | public function getQueryString(): ?string 129 | { 130 | return $this->queryString; 131 | } 132 | 133 | public function getQueryBag(): QueryBag 134 | { 135 | return $this->queryBag; 136 | } 137 | 138 | public function getQuery(): array 139 | { 140 | return $this->queryBag->toArray(); 141 | } 142 | 143 | public function getString(string $name, ?string $default = null): ?string 144 | { 145 | return $this->queryBag->getString($name, $default); 146 | } 147 | 148 | public function getDecimal(string $name, ?int $default = null): ?int 149 | { 150 | return $this->queryBag->getDecimal($name, $default); 151 | } 152 | 153 | public function getOctal(string $name, ?int $default = null): ?int 154 | { 155 | return $this->queryBag->getOctal($name, $default); 156 | } 157 | 158 | public function getFloat(string $name, ?float $default = null): ?float 159 | { 160 | return $this->queryBag->getFloat($name, $default); 161 | } 162 | 163 | public function getBool(string $name, ?bool $default = null): ?bool 164 | { 165 | return $this->queryBag->getBool($name, $default); 166 | } 167 | 168 | public function getArray(string $name, array $default = []): QueryBag 169 | { 170 | return $this->queryBag->getArray($name, $default); 171 | } 172 | 173 | public function toArray() 174 | { 175 | return [ 176 | 'scheme' => $this->scheme, 177 | 'schemeProtocol' => $this->schemeProtocol, 178 | 'schemeExtensions' => $this->schemeExtensions, 179 | 'user' => $this->user, 180 | 'password' => $this->password, 181 | 'host' => $this->host, 182 | 'port' => $this->port, 183 | 'path' => $this->path, 184 | 'queryString' => $this->queryString, 185 | 'query' => $this->queryBag->toArray(), 186 | ]; 187 | } 188 | 189 | public static function parseFirst(string $dsn): ?self 190 | { 191 | return self::parse($dsn)[0]; 192 | } 193 | 194 | /** 195 | * @return Dsn[] 196 | */ 197 | public static function parse(string $dsn): array 198 | { 199 | if (!str_contains($dsn, ':')) { 200 | throw new \LogicException('The DSN is invalid. It does not have scheme separator ":".'); 201 | } 202 | 203 | list($scheme, $dsnWithoutScheme) = explode(':', $dsn, 2); 204 | 205 | $scheme = strtolower($scheme); 206 | if (false == preg_match('/^[a-z\d+-.]*$/', $scheme)) { 207 | throw new \LogicException('The DSN is invalid. Scheme contains illegal symbols.'); 208 | } 209 | 210 | $schemeParts = explode('+', $scheme); 211 | $schemeProtocol = $schemeParts[0]; 212 | 213 | unset($schemeParts[0]); 214 | $schemeExtensions = array_values($schemeParts); 215 | 216 | $user = parse_url($dsn, \PHP_URL_USER) ?: null; 217 | if (is_string($user)) { 218 | $user = rawurldecode($user); 219 | } 220 | 221 | $password = parse_url($dsn, \PHP_URL_PASS) ?: null; 222 | if (is_string($password)) { 223 | $password = rawurldecode($password); 224 | } 225 | 226 | $path = parse_url($dsn, \PHP_URL_PATH) ?: null; 227 | if ($path) { 228 | $path = rawurldecode($path); 229 | } 230 | 231 | $query = []; 232 | $queryString = parse_url($dsn, \PHP_URL_QUERY) ?: null; 233 | if (is_string($queryString)) { 234 | $query = self::httpParseQuery($queryString, '&', \PHP_QUERY_RFC3986); 235 | } 236 | $hostsPorts = ''; 237 | if (str_starts_with($dsnWithoutScheme, '//')) { 238 | $dsnWithoutScheme = substr($dsnWithoutScheme, 2); 239 | $dsnWithoutUserPassword = explode('@', $dsnWithoutScheme, 2); 240 | $dsnWithoutUserPassword = 2 === count($dsnWithoutUserPassword) ? 241 | $dsnWithoutUserPassword[1] : 242 | $dsnWithoutUserPassword[0] 243 | ; 244 | 245 | list($hostsPorts) = explode('#', $dsnWithoutUserPassword, 2); 246 | list($hostsPorts) = explode('?', $hostsPorts, 2); 247 | list($hostsPorts) = explode('/', $hostsPorts, 2); 248 | } 249 | 250 | if (empty($hostsPorts)) { 251 | return [ 252 | new self( 253 | $scheme, 254 | $schemeProtocol, 255 | $schemeExtensions, 256 | null, 257 | null, 258 | null, 259 | null, 260 | $path, 261 | $queryString, 262 | $query 263 | ), 264 | ]; 265 | } 266 | 267 | $dsns = []; 268 | $hostParts = explode(',', $hostsPorts); 269 | foreach ($hostParts as $key => $hostPart) { 270 | unset($hostParts[$key]); 271 | 272 | $parts = explode(':', $hostPart, 2); 273 | $host = $parts[0]; 274 | 275 | $port = null; 276 | if (isset($parts[1])) { 277 | $port = (int) $parts[1]; 278 | } 279 | 280 | $dsns[] = new self( 281 | $scheme, 282 | $schemeProtocol, 283 | $schemeExtensions, 284 | $user, 285 | $password, 286 | $host, 287 | $port, 288 | $path, 289 | $queryString, 290 | $query 291 | ); 292 | } 293 | 294 | return $dsns; 295 | } 296 | 297 | /** 298 | * based on http://php.net/manual/en/function.parse-str.php#119484 with some slight modifications. 299 | */ 300 | private static function httpParseQuery(string $queryString, string $argSeparator = '&', int $decType = \PHP_QUERY_RFC1738): array 301 | { 302 | $result = []; 303 | $parts = explode($argSeparator, $queryString); 304 | 305 | foreach ($parts as $part) { 306 | list($paramName, $paramValue) = explode('=', $part, 2); 307 | 308 | switch ($decType) { 309 | case \PHP_QUERY_RFC3986: 310 | $paramName = rawurldecode($paramName); 311 | $paramValue = rawurldecode($paramValue); 312 | break; 313 | case \PHP_QUERY_RFC1738: 314 | default: 315 | $paramName = urldecode($paramName); 316 | $paramValue = urldecode($paramValue); 317 | break; 318 | } 319 | 320 | if (preg_match_all('/\[([^\]]*)\]/m', $paramName, $matches)) { 321 | $paramName = substr($paramName, 0, strpos($paramName, '[')); 322 | $keys = array_merge([$paramName], $matches[1]); 323 | } else { 324 | $keys = [$paramName]; 325 | } 326 | 327 | $target = &$result; 328 | 329 | foreach ($keys as $index) { 330 | if ('' === $index) { 331 | if (is_array($target)) { 332 | $intKeys = array_filter(array_keys($target), 'is_int'); 333 | $index = count($intKeys) ? max($intKeys) + 1 : 0; 334 | } else { 335 | $target = [$target]; 336 | $index = 1; 337 | } 338 | } elseif (isset($target[$index]) && !is_array($target[$index])) { 339 | $target[$index] = [$target[$index]]; 340 | } 341 | 342 | $target = &$target[$index]; 343 | } 344 | 345 | if (is_array($target)) { 346 | $target[] = $paramValue; 347 | } else { 348 | $target = $paramValue; 349 | } 350 | } 351 | 352 | return $result; 353 | } 354 | } 355 | -------------------------------------------------------------------------------- /InvalidQueryParameterTypeException.php: -------------------------------------------------------------------------------- 1 | query = $query; 17 | } 18 | 19 | public function toArray(): array 20 | { 21 | return $this->query; 22 | } 23 | 24 | public function getString(string $name, ?string $default = null): ?string 25 | { 26 | return array_key_exists($name, $this->query) ? $this->query[$name] : $default; 27 | } 28 | 29 | public function getDecimal(string $name, ?int $default = null): ?int 30 | { 31 | $value = $this->getString($name); 32 | if (null === $value) { 33 | return $default; 34 | } 35 | 36 | if (false == preg_match('/^[\+\-]?[0-9]*$/', $value)) { 37 | throw InvalidQueryParameterTypeException::create($name, 'decimal'); 38 | } 39 | 40 | return (int) $value; 41 | } 42 | 43 | public function getOctal(string $name, ?int $default = null): ?int 44 | { 45 | $value = $this->getString($name); 46 | if (null === $value) { 47 | return $default; 48 | } 49 | 50 | if (false == preg_match('/^0[\+\-]?[0-7]*$/', $value)) { 51 | throw InvalidQueryParameterTypeException::create($name, 'octal'); 52 | } 53 | 54 | return intval($value, 8); 55 | } 56 | 57 | public function getFloat(string $name, ?float $default = null): ?float 58 | { 59 | $value = $this->getString($name); 60 | if (null === $value) { 61 | return $default; 62 | } 63 | 64 | if (false == is_numeric($value)) { 65 | throw InvalidQueryParameterTypeException::create($name, 'float'); 66 | } 67 | 68 | return (float) $value; 69 | } 70 | 71 | public function getBool(string $name, ?bool $default = null): ?bool 72 | { 73 | $value = $this->getString($name); 74 | if (null === $value) { 75 | return $default; 76 | } 77 | 78 | if (in_array($value, ['', '0', 'false'], true)) { 79 | return false; 80 | } 81 | 82 | if (in_array($value, ['1', 'true'], true)) { 83 | return true; 84 | } 85 | 86 | throw InvalidQueryParameterTypeException::create($name, 'bool'); 87 | } 88 | 89 | public function getArray(string $name, array $default = []): self 90 | { 91 | if (false == array_key_exists($name, $this->query)) { 92 | return new self($default); 93 | } 94 | 95 | $value = $this->query[$name]; 96 | 97 | if (is_array($value)) { 98 | return new self($value); 99 | } 100 | 101 | throw InvalidQueryParameterTypeException::create($name, 'array'); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Supporting Enqueue

2 | 3 | Enqueue is an MIT-licensed open source project with its ongoing development made possible entirely by the support of community and our customers. If you'd like to join them, please consider: 4 | 5 | - [Become a sponsor](https://www.patreon.com/makasim) 6 | - [Become our client](http://forma-pro.com/) 7 | 8 | --- 9 | 10 | # Enqueue. Parse DSN class 11 | 12 | ## Resources 13 | 14 | * [Site](https://enqueue.forma-pro.com/) 15 | * [Documentation](https://github.com/php-enqueue/enqueue-dev/blob/master/docs/dsn.md) 16 | * [Questions](https://gitter.im/php-enqueue/Lobby) 17 | * [Issue Tracker](https://github.com/php-enqueue/enqueue-dev/issues) 18 | 19 | ## Developed by Forma-Pro 20 | 21 | Forma-Pro is a full stack development company which interests also spread to open source development. 22 | Being a team of strong professionals we have an aim an ability to help community by developing cutting edge solutions in the areas of e-commerce, docker & microservice oriented architecture where we have accumulated a huge many-years experience. 23 | Our main specialization is Symfony framework based solution, but we are always looking to the technologies that allow us to do our job the best way. We are committed to creating solutions that revolutionize the way how things are developed in aspects of architecture & scalability. 24 | 25 | If you have any questions and inquires about our open source development, this product particularly or any other matter feel free to contact at opensource@forma-pro.com 26 | 27 | ## License 28 | 29 | It is released under the [MIT License](LICENSE). 30 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "enqueue/dsn", 3 | "type": "library", 4 | "description": "Parse DSN", 5 | "keywords": ["dsn", "parse"], 6 | "homepage": "https://enqueue.forma-pro.com/", 7 | "license": "MIT", 8 | "require": { 9 | "php": "^8.0" 10 | }, 11 | "require-dev": { 12 | "phpunit/phpunit": "^9.5" 13 | }, 14 | "support": { 15 | "email": "opensource@forma-pro.com", 16 | "issues": "https://github.com/php-enqueue/enqueue-dev/issues", 17 | "forum": "https://gitter.im/php-enqueue/Lobby", 18 | "source": "https://github.com/php-enqueue/enqueue-dev", 19 | "docs": "https://github.com/php-enqueue/enqueue-dev/blob/master/docs/index.md" 20 | }, 21 | "autoload": { 22 | "psr-4": { "Enqueue\\Dsn\\": "" }, 23 | "exclude-from-classmap": [ 24 | "/Tests/" 25 | ] 26 | }, 27 | "minimum-stability": "dev", 28 | "extra": { 29 | "branch-alias": { 30 | "dev-master": "0.10.x-dev" 31 | } 32 | } 33 | } 34 | --------------------------------------------------------------------------------