Dsn.php 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. <?php
  2. declare(strict_types=1);
  3. namespace Enqueue\Dsn;
  4. class Dsn
  5. {
  6. /**
  7. * @var string
  8. */
  9. private $scheme;
  10. /**
  11. * @var string
  12. */
  13. private $schemeProtocol;
  14. /**
  15. * @var string[]
  16. */
  17. private $schemeExtensions;
  18. /**
  19. * @var string|null
  20. */
  21. private $user;
  22. /**
  23. * @var string|null
  24. */
  25. private $password;
  26. /**
  27. * @var string|null
  28. */
  29. private $host;
  30. /**
  31. * @var int|null
  32. */
  33. private $port;
  34. /**
  35. * @var string|null
  36. */
  37. private $path;
  38. /**
  39. * @var string|null
  40. */
  41. private $queryString;
  42. /**
  43. * @var QueryBag
  44. */
  45. private $queryBag;
  46. public function __construct(
  47. string $scheme,
  48. string $schemeProtocol,
  49. array $schemeExtensions,
  50. ?string $user,
  51. ?string $password,
  52. ?string $host,
  53. ?int $port,
  54. ?string $path,
  55. ?string $queryString,
  56. array $query
  57. ) {
  58. $this->scheme = $scheme;
  59. $this->schemeProtocol = $schemeProtocol;
  60. $this->schemeExtensions = $schemeExtensions;
  61. $this->user = $user;
  62. $this->password = $password;
  63. $this->host = $host;
  64. $this->port = $port;
  65. $this->path = $path;
  66. $this->queryString = $queryString;
  67. $this->queryBag = new QueryBag($query);
  68. }
  69. public function getScheme(): string
  70. {
  71. return $this->scheme;
  72. }
  73. public function getSchemeProtocol(): string
  74. {
  75. return $this->schemeProtocol;
  76. }
  77. public function getSchemeExtensions(): array
  78. {
  79. return $this->schemeExtensions;
  80. }
  81. public function hasSchemeExtension(string $extension): bool
  82. {
  83. return in_array($extension, $this->schemeExtensions, true);
  84. }
  85. public function getUser(): ?string
  86. {
  87. return $this->user;
  88. }
  89. public function getPassword(): ?string
  90. {
  91. return $this->password;
  92. }
  93. public function getHost(): ?string
  94. {
  95. return $this->host;
  96. }
  97. public function getPort(): ?int
  98. {
  99. return $this->port;
  100. }
  101. public function getPath(): ?string
  102. {
  103. return $this->path;
  104. }
  105. public function getQueryString(): ?string
  106. {
  107. return $this->queryString;
  108. }
  109. public function getQueryBag(): QueryBag
  110. {
  111. return $this->queryBag;
  112. }
  113. public function getQuery(): array
  114. {
  115. return $this->queryBag->toArray();
  116. }
  117. public function getString(string $name, string $default = null): ?string
  118. {
  119. return $this->queryBag->getString($name, $default);
  120. }
  121. public function getDecimal(string $name, int $default = null): ?int
  122. {
  123. return $this->queryBag->getDecimal($name, $default);
  124. }
  125. public function getOctal(string $name, int $default = null): ?int
  126. {
  127. return $this->queryBag->getOctal($name, $default);
  128. }
  129. public function getFloat(string $name, float $default = null): ?float
  130. {
  131. return $this->queryBag->getFloat($name, $default);
  132. }
  133. public function getBool(string $name, bool $default = null): ?bool
  134. {
  135. return $this->queryBag->getBool($name, $default);
  136. }
  137. public function getArray(string $name, array $default = []): QueryBag
  138. {
  139. return $this->queryBag->getArray($name, $default);
  140. }
  141. public function toArray()
  142. {
  143. return [
  144. 'scheme' => $this->scheme,
  145. 'schemeProtocol' => $this->schemeProtocol,
  146. 'schemeExtensions' => $this->schemeExtensions,
  147. 'user' => $this->user,
  148. 'password' => $this->password,
  149. 'host' => $this->host,
  150. 'port' => $this->port,
  151. 'path' => $this->path,
  152. 'queryString' => $this->queryString,
  153. 'query' => $this->queryBag->toArray(),
  154. ];
  155. }
  156. public static function parseFirst(string $dsn): ?self
  157. {
  158. return self::parse($dsn)[0];
  159. }
  160. /**
  161. * @param string $dsn
  162. *
  163. * @return Dsn[]
  164. */
  165. public static function parse(string $dsn): array
  166. {
  167. if (false === strpos($dsn, ':')) {
  168. throw new \LogicException(sprintf('The DSN is invalid. It does not have scheme separator ":".'));
  169. }
  170. list($scheme, $dsnWithoutScheme) = explode(':', $dsn, 2);
  171. $scheme = strtolower($scheme);
  172. if (false == preg_match('/^[a-z\d+-.]*$/', $scheme)) {
  173. throw new \LogicException('The DSN is invalid. Scheme contains illegal symbols.');
  174. }
  175. $schemeParts = explode('+', $scheme);
  176. $schemeProtocol = $schemeParts[0];
  177. unset($schemeParts[0]);
  178. $schemeExtensions = array_values($schemeParts);
  179. $user = parse_url($dsn, PHP_URL_USER) ?: null;
  180. if (is_string($user)) {
  181. $user = rawurldecode($user);
  182. }
  183. $password = parse_url($dsn, PHP_URL_PASS) ?: null;
  184. if (is_string($password)) {
  185. $password = rawurldecode($password);
  186. }
  187. $path = parse_url($dsn, PHP_URL_PATH) ?: null;
  188. if ($path) {
  189. $path = rawurldecode($path);
  190. }
  191. $query = [];
  192. $queryString = parse_url($dsn, PHP_URL_QUERY) ?: null;
  193. if (is_string($queryString)) {
  194. $query = self::httpParseQuery($queryString, '&', PHP_QUERY_RFC3986);
  195. }
  196. $hostsPorts = '';
  197. if (0 === strpos($dsnWithoutScheme, '//')) {
  198. $dsnWithoutScheme = substr($dsnWithoutScheme, 2);
  199. $dsnWithoutUserPassword = explode('@', $dsnWithoutScheme, 2);
  200. $dsnWithoutUserPassword = 2 === count($dsnWithoutUserPassword) ?
  201. $dsnWithoutUserPassword[1] :
  202. $dsnWithoutUserPassword[0]
  203. ;
  204. list($hostsPorts) = explode('#', $dsnWithoutUserPassword, 2);
  205. list($hostsPorts) = explode('?', $hostsPorts, 2);
  206. list($hostsPorts) = explode('/', $hostsPorts, 2);
  207. }
  208. if (empty($hostsPorts)) {
  209. return [
  210. new self(
  211. $scheme,
  212. $schemeProtocol,
  213. $schemeExtensions,
  214. null,
  215. null,
  216. null,
  217. null,
  218. $path,
  219. $queryString,
  220. $query
  221. ),
  222. ];
  223. }
  224. $dsns = [];
  225. $hostParts = explode(',', $hostsPorts);
  226. foreach ($hostParts as $key => $hostPart) {
  227. unset($hostParts[$key]);
  228. $parts = explode(':', $hostPart, 2);
  229. $host = $parts[0];
  230. $port = null;
  231. if (isset($parts[1])) {
  232. $port = (int) $parts[1];
  233. }
  234. $dsns[] = new self(
  235. $scheme,
  236. $schemeProtocol,
  237. $schemeExtensions,
  238. $user,
  239. $password,
  240. $host,
  241. $port,
  242. $path,
  243. $queryString,
  244. $query
  245. );
  246. }
  247. return $dsns;
  248. }
  249. /**
  250. * based on http://php.net/manual/en/function.parse-str.php#119484 with some slight modifications.
  251. */
  252. private static function httpParseQuery(string $queryString, string $argSeparator = '&', int $decType = PHP_QUERY_RFC1738): array
  253. {
  254. $result = [];
  255. $parts = explode($argSeparator, $queryString);
  256. foreach ($parts as $part) {
  257. list($paramName, $paramValue) = explode('=', $part, 2);
  258. switch ($decType) {
  259. case PHP_QUERY_RFC3986:
  260. $paramName = rawurldecode($paramName);
  261. $paramValue = rawurldecode($paramValue);
  262. break;
  263. case PHP_QUERY_RFC1738:
  264. default:
  265. $paramName = urldecode($paramName);
  266. $paramValue = urldecode($paramValue);
  267. break;
  268. }
  269. if (preg_match_all('/\[([^\]]*)\]/m', $paramName, $matches)) {
  270. $paramName = substr($paramName, 0, strpos($paramName, '['));
  271. $keys = array_merge([$paramName], $matches[1]);
  272. } else {
  273. $keys = [$paramName];
  274. }
  275. $target = &$result;
  276. foreach ($keys as $index) {
  277. if ('' === $index) {
  278. if (is_array($target)) {
  279. $intKeys = array_filter(array_keys($target), 'is_int');
  280. $index = count($intKeys) ? max($intKeys) + 1 : 0;
  281. } else {
  282. $target = [$target];
  283. $index = 1;
  284. }
  285. } elseif (isset($target[$index]) && !is_array($target[$index])) {
  286. $target[$index] = [$target[$index]];
  287. }
  288. $target = &$target[$index];
  289. }
  290. if (is_array($target)) {
  291. $target[] = $paramValue;
  292. } else {
  293. $target = $paramValue;
  294. }
  295. }
  296. return $result;
  297. }
  298. }