From c39e37d8b7fc86692538ae8cc1b0371fcdee409e Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 10 Dec 2025 17:44:06 +0530 Subject: [PATCH] feat: support parsing scheme-only DSNs like local:// --- src/DSN/DSN.php | 27 ++++++++++++++++++--------- tests/DSN/DSNTest.php | 32 +++++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 10 deletions(-) diff --git a/src/DSN/DSN.php b/src/DSN/DSN.php index fd1111b..9eb54f2 100644 --- a/src/DSN/DSN.php +++ b/src/DSN/DSN.php @@ -20,9 +20,9 @@ class DSN protected ?string $password; /** - * @var string + * @var ?string */ - protected string $host; + protected ?string $host; /** * @var ?string @@ -55,6 +55,19 @@ public function __construct(string $dsn) { $parts = \parse_url($dsn); + // Handle scheme-only DSNs like "local://" which parse_url returns false for + if (!$parts && \str_ends_with($dsn, '://') && \strlen($dsn) > 3) { + $this->scheme = \substr($dsn, 0, -3); + $this->user = null; + $this->password = null; + $this->host = null; + $this->port = null; + $this->path = ''; + $this->query = null; + + return; + } + if (!$parts) { throw new \InvalidArgumentException("Unable to parse DSN: $dsn"); } @@ -63,14 +76,10 @@ public function __construct(string $dsn) throw new \InvalidArgumentException('Unable to parse DSN: scheme is required'); } - if (empty($parts['host'])) { - throw new \InvalidArgumentException('Unable to parse DSN: host is required'); - } - $this->scheme = $parts['scheme']; $this->user = isset($parts['user']) ? \urldecode($parts['user']) : null; $this->password = isset($parts['pass']) ? \urldecode($parts['pass']) : null; - $this->host = $parts['host']; + $this->host = $parts['host'] ?? null; $this->port = $parts['port'] ?? null; $this->path = isset($parts['path']) ? ltrim((string) $parts['path'], '/') : ''; $this->query = $parts['query'] ?? null; @@ -109,9 +118,9 @@ public function getPassword(): ?string /** * Return the host * - * @return string + * @return ?string */ - public function getHost(): string + public function getHost(): ?string { return $this->host; } diff --git a/tests/DSN/DSNTest.php b/tests/DSN/DSNTest.php index 4521f37..19ef0bd 100644 --- a/tests/DSN/DSNTest.php +++ b/tests/DSN/DSNTest.php @@ -148,9 +148,39 @@ public function testGetParam(): void $this->assertEquals('us-east-2', $dsn->getParam('region', 'us-east-2')); } + public function testSchemeOnly(): void + { + $dsn = new DSN('local://'); + $this->assertEquals('local', $dsn->getScheme()); + $this->assertNull($dsn->getUser()); + $this->assertNull($dsn->getPassword()); + $this->assertNull($dsn->getHost()); + $this->assertNull($dsn->getPort()); + $this->assertEmpty($dsn->getPath()); + $this->assertNull($dsn->getQuery()); + + $dsn = new DSN('memory://'); + $this->assertEquals('memory', $dsn->getScheme()); + $this->assertNull($dsn->getUser()); + $this->assertNull($dsn->getPassword()); + $this->assertNull($dsn->getHost()); + $this->assertNull($dsn->getPort()); + $this->assertEmpty($dsn->getPath()); + $this->assertNull($dsn->getQuery()); + + $dsn = new DSN('file://'); + $this->assertEquals('file', $dsn->getScheme()); + $this->assertNull($dsn->getUser()); + $this->assertNull($dsn->getPassword()); + $this->assertNull($dsn->getHost()); + $this->assertNull($dsn->getPort()); + $this->assertEmpty($dsn->getPath()); + $this->assertNull($dsn->getQuery()); + } + public function testFail(): void { $this->expectException(\InvalidArgumentException::class); - new DSN('mariadb://'); + new DSN('invalid-dsn-without-scheme'); } }