66
77final readonly class ConnectionParameters
88{
9+ /**
10+ * @param array<string, string> $options
11+ */
912 private function __construct (
10- public string $ connectionString ,
13+ private string $ host ,
14+ private int $ port ,
15+ private string $ database ,
16+ private ?string $ user ,
17+ #[\SensitiveParameter]
18+ private ?string $ password ,
19+ private array $ options ,
1120 ) {
1221 }
1322
@@ -25,44 +34,212 @@ public static function fromParams(
2534 ?string $ password = null ,
2635 array $ options = [],
2736 ) : self {
28- $ parts = [
29- \sprintf ('host=%s ' , $ host ),
30- \sprintf ('port=%d ' , $ port ),
31- \sprintf ('dbname=%s ' , $ database ),
32- ];
37+ return new self (
38+ host: $ host ,
39+ port: $ port ,
40+ database: $ database ,
41+ user: $ user ,
42+ password: $ password ,
43+ options: $ options ,
44+ );
45+ }
3346
34- if ($ user !== null ) {
35- $ parts [] = \sprintf ('user=%s ' , $ user );
36- }
47+ /**
48+ * Create from a PostgreSQL libpq connection string.
49+ *
50+ * Parses connection strings in libpq format: key=value pairs separated by spaces.
51+ * Supports quoted values for values containing spaces (e.g., password='my secret').
52+ *
53+ * @throws \InvalidArgumentException if dbname is missing
54+ */
55+ public static function fromString (#[\SensitiveParameter] string $ connectionString ) : self
56+ {
57+ $ parts = [];
58+ $ pattern = '/(\w+)=(?: \'([^ \']*) \'|([^\s]*))/ ' ;
59+ \preg_match_all ($ pattern , $ connectionString , $ matches , \PREG_SET_ORDER );
3760
38- if ($ password !== null ) {
39- $ parts [] = \sprintf ('password=%s ' , $ password );
61+ foreach ($ matches as $ match ) {
62+ $ key = $ match [1 ];
63+ $ quotedValue = $ match [2 ] ?? '' ;
64+ $ unquotedValue = $ match [3 ] ?? '' ;
65+ $ value = $ quotedValue !== '' ? $ quotedValue : $ unquotedValue ;
66+ $ parts [$ key ] = $ value ;
4067 }
4168
42- foreach ( $ options as $ key => $ value ) {
43- $ parts [] = \sprintf ( ' %s=%s ' , $ key , $ value );
69+ if (! isset ( $ parts [ ' dbname ' ]) ) {
70+ throw new \ InvalidArgumentException ( ' Missing dbname in connection string ' );
4471 }
4572
46- return new self (\implode (' ' , $ parts ));
73+ return new self (
74+ host: $ parts ['host ' ] ?? 'localhost ' ,
75+ port: isset ($ parts ['port ' ]) ? (int ) $ parts ['port ' ] : 5432 ,
76+ database: $ parts ['dbname ' ],
77+ user: $ parts ['user ' ] ?? null ,
78+ password: $ parts ['password ' ] ?? null ,
79+ options: \array_diff_key ($ parts , \array_flip (['host ' , 'port ' , 'dbname ' , 'user ' , 'password ' ])),
80+ );
4781 }
4882
4983 /**
50- * Create from a PostgreSQL connection string.
84+ * Mask password in debug output to prevent accidental exposure.
85+ *
86+ * @return array<string, mixed>
5187 */
52- public static function fromString (#[\SensitiveParameter] string $ connectionString ) : self
88+ public function __debugInfo () : array
89+ {
90+ return [
91+ 'host ' => $ this ->host ,
92+ 'port ' => $ this ->port ,
93+ 'database ' => $ this ->database ,
94+ 'user ' => $ this ->user ,
95+ 'password ' => $ this ->password !== null ? '*** ' : null ,
96+ 'options ' => $ this ->options ,
97+ ];
98+ }
99+
100+ public function database () : string
101+ {
102+ return $ this ->database ;
103+ }
104+
105+ public function host () : string
53106 {
54- return new self ( $ connectionString ) ;
107+ return $ this -> host ;
55108 }
56109
57110 /**
58- * Mask password in debug output to prevent accidental exposure.
59- *
60111 * @return array<string, string>
61112 */
62- public function __debugInfo () : array
113+ public function options () : array
63114 {
64- return [
65- 'connectionString ' => \preg_replace ('/password=[^\s]+/ ' , 'password=*** ' , $ this ->connectionString ) ?? $ this ->connectionString ,
115+ return $ this ->options ;
116+ }
117+
118+ public function password () : ?string
119+ {
120+ return $ this ->password ;
121+ }
122+
123+ public function port () : int
124+ {
125+ return $ this ->port ;
126+ }
127+
128+ /**
129+ * Convert connection parameters to a libpq connection string.
130+ */
131+ public function toString () : string
132+ {
133+ $ parts = [
134+ \sprintf ('host=%s ' , $ this ->host ),
135+ \sprintf ('port=%d ' , $ this ->port ),
136+ \sprintf ('dbname=%s ' , $ this ->database ),
66137 ];
138+
139+ if ($ this ->user !== null ) {
140+ $ parts [] = \sprintf ('user=%s ' , $ this ->user );
141+ }
142+
143+ if ($ this ->password !== null ) {
144+ $ parts [] = \sprintf ('password=%s ' , $ this ->password );
145+ }
146+
147+ foreach ($ this ->options as $ key => $ value ) {
148+ $ parts [] = \sprintf ('%s=%s ' , $ key , $ value );
149+ }
150+
151+ return \implode (' ' , $ parts );
152+ }
153+
154+ public function user () : ?string
155+ {
156+ return $ this ->user ;
157+ }
158+
159+ public function withDatabase (string $ database ) : self
160+ {
161+ return new self (
162+ host: $ this ->host ,
163+ port: $ this ->port ,
164+ database: $ database ,
165+ user: $ this ->user ,
166+ password: $ this ->password ,
167+ options: $ this ->options ,
168+ );
169+ }
170+
171+ public function withHost (string $ host ) : self
172+ {
173+ return new self (
174+ host: $ host ,
175+ port: $ this ->port ,
176+ database: $ this ->database ,
177+ user: $ this ->user ,
178+ password: $ this ->password ,
179+ options: $ this ->options ,
180+ );
181+ }
182+
183+ public function withOption (string $ key , string $ value ) : self
184+ {
185+ return new self (
186+ host: $ this ->host ,
187+ port: $ this ->port ,
188+ database: $ this ->database ,
189+ user: $ this ->user ,
190+ password: $ this ->password ,
191+ options: \array_merge ($ this ->options , [$ key => $ value ]),
192+ );
193+ }
194+
195+ /**
196+ * @param array<string, string> $options
197+ */
198+ public function withOptions (array $ options ) : self
199+ {
200+ return new self (
201+ host: $ this ->host ,
202+ port: $ this ->port ,
203+ database: $ this ->database ,
204+ user: $ this ->user ,
205+ password: $ this ->password ,
206+ options: $ options ,
207+ );
208+ }
209+
210+ public function withPassword (#[\SensitiveParameter] ?string $ password ) : self
211+ {
212+ return new self (
213+ host: $ this ->host ,
214+ port: $ this ->port ,
215+ database: $ this ->database ,
216+ user: $ this ->user ,
217+ password: $ password ,
218+ options: $ this ->options ,
219+ );
220+ }
221+
222+ public function withPort (int $ port ) : self
223+ {
224+ return new self (
225+ host: $ this ->host ,
226+ port: $ port ,
227+ database: $ this ->database ,
228+ user: $ this ->user ,
229+ password: $ this ->password ,
230+ options: $ this ->options ,
231+ );
232+ }
233+
234+ public function withUser (?string $ user ) : self
235+ {
236+ return new self (
237+ host: $ this ->host ,
238+ port: $ this ->port ,
239+ database: $ this ->database ,
240+ user: $ user ,
241+ password: $ this ->password ,
242+ options: $ this ->options ,
243+ );
67244 }
68245}
0 commit comments