Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
1 | <?php |
||
11 | class UriTest extends TestCase |
||
12 | { |
||
13 | const RFC3986_BASE = 'http://a/b/c/d;p?q'; |
||
14 | |||
15 | public function testParsesProvidedUri() |
||
16 | { |
||
17 | $uri = new Uri('https://user:[email protected]:8080/path/123?q=abc#test'); |
||
18 | |||
19 | $this->assertSame('https', $uri->getScheme()); |
||
20 | $this->assertSame('user:[email protected]:8080', $uri->getAuthority()); |
||
21 | $this->assertSame('user:pass', $uri->getUserInfo()); |
||
22 | $this->assertSame('example.com', $uri->getHost()); |
||
23 | $this->assertSame(8080, $uri->getPort()); |
||
24 | $this->assertSame('/path/123', $uri->getPath()); |
||
25 | $this->assertSame('q=abc', $uri->getQuery()); |
||
26 | $this->assertSame('test', $uri->getFragment()); |
||
27 | $this->assertSame('https://user:[email protected]:8080/path/123?q=abc#test', (string) $uri); |
||
28 | } |
||
29 | |||
30 | public function testCanTransformAndRetrievePartsIndividually() |
||
31 | { |
||
32 | $uri = (new Uri()) |
||
33 | ->withScheme('https') |
||
34 | ->withUserInfo('user', 'pass') |
||
35 | ->withHost('example.com') |
||
36 | ->withPort(8080) |
||
37 | ->withPath('/path/123') |
||
38 | ->withQuery('q=abc') |
||
39 | ->withFragment('test'); |
||
40 | |||
41 | $this->assertSame('https', $uri->getScheme()); |
||
42 | $this->assertSame('user:[email protected]:8080', $uri->getAuthority()); |
||
43 | $this->assertSame('user:pass', $uri->getUserInfo()); |
||
44 | $this->assertSame('example.com', $uri->getHost()); |
||
45 | $this->assertSame(8080, $uri->getPort()); |
||
46 | $this->assertSame('/path/123', $uri->getPath()); |
||
47 | $this->assertSame('q=abc', $uri->getQuery()); |
||
48 | $this->assertSame('test', $uri->getFragment()); |
||
49 | $this->assertSame('https://user:[email protected]:8080/path/123?q=abc#test', (string) $uri); |
||
50 | } |
||
51 | |||
52 | /** |
||
53 | * @dataProvider getValidUris |
||
54 | */ |
||
55 | public function testValidUrisStayValid($input) |
||
56 | { |
||
57 | $uri = new Uri($input); |
||
58 | |||
59 | $this->assertSame($input, (string) $uri); |
||
60 | } |
||
61 | |||
62 | public function getValidUris() |
||
63 | { |
||
64 | return [ |
||
65 | ['urn:path-rootless'], |
||
66 | ['urn:path:with:colon'], |
||
67 | ['urn:/path-absolute'], |
||
68 | ['urn:/'], |
||
69 | // only scheme with empty path |
||
70 | ['urn:'], |
||
71 | // only path |
||
72 | ['/'], |
||
73 | ['relative/'], |
||
74 | ['0'], |
||
75 | // same document reference |
||
76 | [''], |
||
77 | // network path without scheme |
||
78 | ['//example.org'], |
||
79 | ['//example.org/'], |
||
80 | ['//example.org?q#h'], |
||
81 | // only query |
||
82 | ['?q'], |
||
83 | ['?q=abc&foo=bar'], |
||
84 | // only fragment |
||
85 | ['#fragment'], |
||
86 | // dot segments are not removed automatically |
||
87 | ['./foo/../bar'], |
||
88 | ]; |
||
89 | } |
||
90 | |||
91 | /** |
||
92 | * @dataProvider getInvalidUris |
||
93 | */ |
||
94 | public function testInvalidUrisThrowException($invalidUri) |
||
95 | { |
||
96 | $this->expectException(\InvalidArgumentException::class); |
||
97 | $this->expectExceptionMessage('Unable to parse URI'); |
||
98 | |||
99 | new Uri($invalidUri); |
||
100 | } |
||
101 | |||
102 | public function getInvalidUris() |
||
103 | { |
||
104 | return [ |
||
105 | // parse_url() requires the host component which makes sense for http(s) |
||
106 | // but not when the scheme is not known or different. So '//' or '///' is |
||
107 | // currently invalid as well but should not according to RFC 3986. |
||
108 | ['http://'], |
||
109 | ['urn://host:with:colon'], // host cannot contain ":" |
||
110 | ]; |
||
111 | } |
||
112 | |||
113 | public function testPortMustBeValid() |
||
114 | { |
||
115 | $this->expectException(\InvalidArgumentException::class); |
||
116 | $this->expectExceptionMessage('Invalid port: 100000. Must be between 1 and 65535'); |
||
117 | |||
118 | (new Uri())->withPort(100000); |
||
119 | } |
||
120 | |||
121 | public function testWithPortCannotBeZero() |
||
122 | { |
||
123 | $this->expectException(\InvalidArgumentException::class); |
||
124 | $this->expectExceptionMessage('Invalid port: 0. Must be between 1 and 65535'); |
||
125 | |||
126 | (new Uri())->withPort(0); |
||
127 | } |
||
128 | |||
129 | public function testParseUriPortCannotBeZero() |
||
130 | { |
||
131 | $this->expectException(\InvalidArgumentException::class); |
||
132 | $this->expectExceptionMessage('Unable to parse URI'); |
||
133 | |||
134 | new Uri('//example.com:0'); |
||
135 | } |
||
136 | |||
137 | public function testSchemeMustHaveCorrectType() |
||
138 | { |
||
139 | $this->expectException(\InvalidArgumentException::class); |
||
140 | $this->expectExceptionMessage('Scheme must be a string'); |
||
141 | |||
142 | (new Uri())->withScheme([]); |
||
143 | } |
||
144 | |||
145 | public function testHostMustHaveCorrectType() |
||
146 | { |
||
147 | $this->expectException(\InvalidArgumentException::class); |
||
148 | $this->expectExceptionMessage('Host must be a string'); |
||
149 | |||
150 | (new Uri())->withHost([]); |
||
151 | } |
||
152 | |||
153 | public function testPathMustHaveCorrectType() |
||
154 | { |
||
155 | $this->expectException(\InvalidArgumentException::class); |
||
156 | $this->expectExceptionMessage('Path must be a string'); |
||
157 | |||
158 | (new Uri())->withPath([]); |
||
159 | } |
||
160 | |||
161 | public function testQueryMustHaveCorrectType() |
||
162 | { |
||
163 | $this->expectException(\InvalidArgumentException::class); |
||
164 | $this->expectExceptionMessage('Query and fragment must be a string'); |
||
165 | |||
166 | (new Uri())->withQuery([]); |
||
167 | } |
||
168 | |||
169 | public function testFragmentMustHaveCorrectType() |
||
170 | { |
||
171 | $this->expectException(\InvalidArgumentException::class); |
||
172 | $this->expectExceptionMessage('Query and fragment must be a string'); |
||
173 | |||
174 | (new Uri())->withFragment([]); |
||
175 | } |
||
176 | |||
177 | public function testCanParseFalseyUriParts() |
||
178 | { |
||
179 | $uri = new Uri('0://0:0@0/0?0#0'); |
||
180 | |||
181 | $this->assertSame('0', $uri->getScheme()); |
||
182 | $this->assertSame('0:0@0', $uri->getAuthority()); |
||
183 | $this->assertSame('0:0', $uri->getUserInfo()); |
||
184 | $this->assertSame('0', $uri->getHost()); |
||
185 | $this->assertSame('/0', $uri->getPath()); |
||
186 | $this->assertSame('0', $uri->getQuery()); |
||
187 | $this->assertSame('0', $uri->getFragment()); |
||
188 | $this->assertSame('0://0:0@0/0?0#0', (string) $uri); |
||
189 | } |
||
190 | |||
191 | public function testCanConstructFalseyUriParts() |
||
192 | { |
||
193 | $uri = (new Uri()) |
||
194 | ->withScheme('0') |
||
195 | ->withUserInfo('0', '0') |
||
196 | ->withHost('0') |
||
197 | ->withPath('/0') |
||
198 | ->withQuery('0') |
||
199 | ->withFragment('0'); |
||
200 | |||
201 | $this->assertSame('0', $uri->getScheme()); |
||
202 | $this->assertSame('0:0@0', $uri->getAuthority()); |
||
203 | $this->assertSame('0:0', $uri->getUserInfo()); |
||
204 | $this->assertSame('0', $uri->getHost()); |
||
205 | $this->assertSame('/0', $uri->getPath()); |
||
206 | $this->assertSame('0', $uri->getQuery()); |
||
207 | $this->assertSame('0', $uri->getFragment()); |
||
208 | $this->assertSame('0://0:0@0/0?0#0', (string) $uri); |
||
209 | } |
||
210 | |||
211 | public function getResolveTestCases() |
||
212 | { |
||
213 | return [ |
||
214 | [self::RFC3986_BASE, 'g:h', 'g:h'], |
||
215 | [self::RFC3986_BASE, 'g', 'http://a/b/c/g'], |
||
216 | [self::RFC3986_BASE, './g', 'http://a/b/c/g'], |
||
217 | [self::RFC3986_BASE, 'g/', 'http://a/b/c/g/'], |
||
218 | [self::RFC3986_BASE, '/g', 'http://a/g'], |
||
219 | [self::RFC3986_BASE, '//g', 'http://g'], |
||
220 | [self::RFC3986_BASE, '?y', 'http://a/b/c/d;p?y'], |
||
221 | [self::RFC3986_BASE, 'g?y', 'http://a/b/c/g?y'], |
||
222 | [self::RFC3986_BASE, '#s', 'http://a/b/c/d;p?q#s'], |
||
223 | [self::RFC3986_BASE, 'g#s', 'http://a/b/c/g#s'], |
||
224 | [self::RFC3986_BASE, 'g?y#s', 'http://a/b/c/g?y#s'], |
||
225 | [self::RFC3986_BASE, ';x', 'http://a/b/c/;x'], |
||
226 | [self::RFC3986_BASE, 'g;x', 'http://a/b/c/g;x'], |
||
227 | [self::RFC3986_BASE, 'g;x?y#s', 'http://a/b/c/g;x?y#s'], |
||
228 | [self::RFC3986_BASE, '', self::RFC3986_BASE], |
||
229 | [self::RFC3986_BASE, '.', 'http://a/b/c/'], |
||
230 | [self::RFC3986_BASE, './', 'http://a/b/c/'], |
||
231 | [self::RFC3986_BASE, '..', 'http://a/b/'], |
||
232 | [self::RFC3986_BASE, '../', 'http://a/b/'], |
||
233 | [self::RFC3986_BASE, '../g', 'http://a/b/g'], |
||
234 | [self::RFC3986_BASE, '../..', 'http://a/'], |
||
235 | [self::RFC3986_BASE, '../../', 'http://a/'], |
||
236 | [self::RFC3986_BASE, '../../g', 'http://a/g'], |
||
237 | [self::RFC3986_BASE, '../../../g', 'http://a/g'], |
||
238 | [self::RFC3986_BASE, '../../../../g', 'http://a/g'], |
||
239 | [self::RFC3986_BASE, '/./g', 'http://a/g'], |
||
240 | [self::RFC3986_BASE, '/../g', 'http://a/g'], |
||
241 | [self::RFC3986_BASE, 'g.', 'http://a/b/c/g.'], |
||
242 | [self::RFC3986_BASE, '.g', 'http://a/b/c/.g'], |
||
243 | [self::RFC3986_BASE, 'g..', 'http://a/b/c/g..'], |
||
244 | [self::RFC3986_BASE, '..g', 'http://a/b/c/..g'], |
||
245 | [self::RFC3986_BASE, './../g', 'http://a/b/g'], |
||
246 | [self::RFC3986_BASE, 'foo////g', 'http://a/b/c/foo////g'], |
||
247 | [self::RFC3986_BASE, './g/.', 'http://a/b/c/g/'], |
||
248 | [self::RFC3986_BASE, 'g/./h', 'http://a/b/c/g/h'], |
||
249 | [self::RFC3986_BASE, 'g/../h', 'http://a/b/c/h'], |
||
250 | [self::RFC3986_BASE, 'g;x=1/./y', 'http://a/b/c/g;x=1/y'], |
||
251 | [self::RFC3986_BASE, 'g;x=1/../y', 'http://a/b/c/y'], |
||
252 | // dot-segments in the query or fragment |
||
253 | [self::RFC3986_BASE, 'g?y/./x', 'http://a/b/c/g?y/./x'], |
||
254 | [self::RFC3986_BASE, 'g?y/../x', 'http://a/b/c/g?y/../x'], |
||
255 | [self::RFC3986_BASE, 'g#s/./x', 'http://a/b/c/g#s/./x'], |
||
256 | [self::RFC3986_BASE, 'g#s/../x', 'http://a/b/c/g#s/../x'], |
||
257 | [self::RFC3986_BASE, 'g#s/../x', 'http://a/b/c/g#s/../x'], |
||
258 | [self::RFC3986_BASE, '?y#s', 'http://a/b/c/d;p?y#s'], |
||
259 | ['http://a/b/c/d;p?q#s', '?y', 'http://a/b/c/d;p?y'], |
||
260 | ['http://u@a/b/c/d;p?q', '.', 'http://u@a/b/c/'], |
||
261 | ['http://u:p@a/b/c/d;p?q', '.', 'http://u:p@a/b/c/'], |
||
262 | ['http://a/b/c/d/', 'e', 'http://a/b/c/d/e'], |
||
263 | ['urn:no-slash', 'e', 'urn:e'], |
||
264 | // falsey relative parts |
||
265 | [self::RFC3986_BASE, '//0', 'http://0'], |
||
266 | [self::RFC3986_BASE, '0', 'http://a/b/c/0'], |
||
267 | [self::RFC3986_BASE, '?0', 'http://a/b/c/d;p?0'], |
||
268 | [self::RFC3986_BASE, '#0', 'http://a/b/c/d;p?q#0'], |
||
269 | ]; |
||
270 | } |
||
271 | |||
272 | public function testSchemeIsNormalizedToLowercase() |
||
273 | { |
||
274 | $uri = new Uri('HTTP://example.com'); |
||
275 | |||
276 | $this->assertSame('http', $uri->getScheme()); |
||
277 | $this->assertSame('http://example.com', (string) $uri); |
||
278 | |||
279 | $uri = (new Uri('//example.com'))->withScheme('HTTP'); |
||
280 | |||
281 | $this->assertSame('http', $uri->getScheme()); |
||
282 | $this->assertSame('http://example.com', (string) $uri); |
||
283 | } |
||
284 | |||
285 | public function testHostIsNormalizedToLowercase() |
||
286 | { |
||
287 | $uri = new Uri('//eXaMpLe.CoM'); |
||
288 | |||
289 | $this->assertSame('example.com', $uri->getHost()); |
||
290 | $this->assertSame('//example.com', (string) $uri); |
||
291 | |||
292 | $uri = (new Uri())->withHost('eXaMpLe.CoM'); |
||
293 | |||
294 | $this->assertSame('example.com', $uri->getHost()); |
||
295 | $this->assertSame('//example.com', (string) $uri); |
||
296 | } |
||
297 | |||
298 | public function testPortIsNullIfStandardPortForScheme() |
||
299 | { |
||
300 | // HTTPS standard port |
||
301 | $uri = new Uri('https://example.com:443'); |
||
302 | $this->assertNull($uri->getPort()); |
||
303 | $this->assertSame('example.com', $uri->getAuthority()); |
||
304 | |||
305 | $uri = (new Uri('https://example.com'))->withPort(443); |
||
306 | $this->assertNull($uri->getPort()); |
||
307 | $this->assertSame('example.com', $uri->getAuthority()); |
||
308 | |||
309 | // HTTP standard port |
||
310 | $uri = new Uri('http://example.com:80'); |
||
311 | $this->assertNull($uri->getPort()); |
||
312 | $this->assertSame('example.com', $uri->getAuthority()); |
||
313 | |||
314 | $uri = (new Uri('http://example.com'))->withPort(80); |
||
315 | $this->assertNull($uri->getPort()); |
||
316 | $this->assertSame('example.com', $uri->getAuthority()); |
||
317 | } |
||
318 | |||
319 | public function testPortIsReturnedIfSchemeUnknown() |
||
320 | { |
||
321 | $uri = (new Uri('//example.com'))->withPort(80); |
||
322 | |||
323 | $this->assertSame(80, $uri->getPort()); |
||
324 | $this->assertSame('example.com:80', $uri->getAuthority()); |
||
325 | } |
||
326 | |||
327 | public function testStandardPortIsNullIfSchemeChanges() |
||
328 | { |
||
329 | $uri = new Uri('http://example.com:443'); |
||
330 | $this->assertSame('http', $uri->getScheme()); |
||
331 | $this->assertSame(443, $uri->getPort()); |
||
332 | |||
333 | $uri = $uri->withScheme('https'); |
||
334 | $this->assertNull($uri->getPort()); |
||
335 | } |
||
336 | |||
337 | public function testPortPassedAsStringIsCastedToInt() |
||
338 | { |
||
339 | $uri = (new Uri('//example.com'))->withPort('8080'); |
||
340 | |||
341 | $this->assertSame(8080, $uri->getPort(), 'Port is returned as integer'); |
||
342 | $this->assertSame('example.com:8080', $uri->getAuthority()); |
||
343 | } |
||
344 | |||
345 | public function testPortCanBeRemoved() |
||
346 | { |
||
347 | $uri = (new Uri('http://example.com:8080'))->withPort(null); |
||
348 | |||
349 | $this->assertNull($uri->getPort()); |
||
350 | $this->assertSame('http://example.com', (string) $uri); |
||
351 | } |
||
352 | |||
353 | public function testAuthorityWithUserInfoButWithoutHost() |
||
354 | { |
||
355 | $uri = (new Uri())->withUserInfo('user', 'pass'); |
||
356 | |||
357 | $this->assertSame('user:pass', $uri->getUserInfo()); |
||
358 | $this->assertSame('', $uri->getAuthority()); |
||
359 | } |
||
360 | |||
361 | public function uriComponentsEncodingProvider() |
||
362 | { |
||
363 | $unreserved = 'a-zA-Z0-9.-_~!$&\'()*+,;=:@'; |
||
364 | |||
365 | return [ |
||
366 | // Percent encode spaces |
||
367 | ['/pa th?q=va lue#frag ment', '/pa%20th', 'q=va%20lue', 'frag%20ment', '/pa%20th?q=va%20lue#frag%20ment'], |
||
368 | // Percent encode multibyte |
||
369 | ['/€?€#€', '/%E2%82%AC', '%E2%82%AC', '%E2%82%AC', '/%E2%82%AC?%E2%82%AC#%E2%82%AC'], |
||
370 | // Don't encode something that's already encoded |
||
371 | ['/pa%20th?q=va%20lue#frag%20ment', '/pa%20th', 'q=va%20lue', 'frag%20ment', '/pa%20th?q=va%20lue#frag%20ment'], |
||
372 | // Percent encode invalid percent encodings |
||
373 | ['/pa%2-th?q=va%2-lue#frag%2-ment', '/pa%252-th', 'q=va%252-lue', 'frag%252-ment', '/pa%252-th?q=va%252-lue#frag%252-ment'], |
||
374 | // Don't encode path segments |
||
375 | ['/pa/th//two?q=va/lue#frag/ment', '/pa/th//two', 'q=va/lue', 'frag/ment', '/pa/th//two?q=va/lue#frag/ment'], |
||
376 | // Don't encode unreserved chars or sub-delimiters |
||
377 | ["/$unreserved?$unreserved#$unreserved", "/$unreserved", $unreserved, $unreserved, "/$unreserved?$unreserved#$unreserved"], |
||
378 | // Encoded unreserved chars are not decoded |
||
379 | ['/p%61th?q=v%61lue#fr%61gment', '/p%61th', 'q=v%61lue', 'fr%61gment', '/p%61th?q=v%61lue#fr%61gment'], |
||
380 | ]; |
||
381 | } |
||
382 | |||
383 | /** |
||
384 | * @dataProvider uriComponentsEncodingProvider |
||
385 | */ |
||
386 | public function testUriComponentsGetEncodedProperly($input, $path, $query, $fragment, $output) |
||
387 | { |
||
388 | $uri = new Uri($input); |
||
389 | $this->assertSame($path, $uri->getPath()); |
||
390 | $this->assertSame($query, $uri->getQuery()); |
||
391 | $this->assertSame($fragment, $uri->getFragment()); |
||
392 | $this->assertSame($output, (string) $uri); |
||
393 | } |
||
394 | |||
395 | public function testWithPathEncodesProperly() |
||
396 | { |
||
397 | $uri = (new Uri())->withPath('/baz?#€/b%61r'); |
||
398 | // Query and fragment delimiters and multibyte chars are encoded. |
||
399 | $this->assertSame('/baz%3F%23%E2%82%AC/b%61r', $uri->getPath()); |
||
400 | $this->assertSame('/baz%3F%23%E2%82%AC/b%61r', (string) $uri); |
||
401 | } |
||
402 | |||
403 | public function testWithQueryEncodesProperly() |
||
404 | { |
||
405 | $uri = (new Uri())->withQuery('?=#&€=/&b%61r'); |
||
406 | // A query starting with a "?" is valid and must not be magically removed. Otherwise it would be impossible to |
||
407 | // construct such an URI. Also the "?" and "/" does not need to be encoded in the query. |
||
408 | $this->assertSame('?=%23&%E2%82%AC=/&b%61r', $uri->getQuery()); |
||
409 | $this->assertSame('??=%23&%E2%82%AC=/&b%61r', (string) $uri); |
||
410 | } |
||
411 | |||
412 | public function testWithFragmentEncodesProperly() |
||
413 | { |
||
414 | $uri = (new Uri())->withFragment('#€?/b%61r'); |
||
415 | // A fragment starting with a "#" is valid and must not be magically removed. Otherwise it would be impossible to |
||
416 | // construct such an URI. Also the "?" and "/" does not need to be encoded in the fragment. |
||
417 | $this->assertSame('%23%E2%82%AC?/b%61r', $uri->getFragment()); |
||
418 | $this->assertSame('#%23%E2%82%AC?/b%61r', (string) $uri); |
||
419 | } |
||
420 | |||
421 | public function testAllowsForRelativeUri() |
||
422 | { |
||
423 | $uri = (new Uri())->withPath('foo'); |
||
424 | $this->assertSame('foo', $uri->getPath()); |
||
425 | $this->assertSame('foo', (string) $uri); |
||
426 | } |
||
427 | |||
428 | public function testAddsSlashForRelativeUriStringWithHost() |
||
429 | { |
||
430 | // If the path is rootless and an authority is present, the path MUST |
||
431 | // be prefixed by "/". |
||
432 | $uri = (new Uri())->withPath('foo')->withHost('example.com'); |
||
433 | $this->assertSame('foo', $uri->getPath()); |
||
434 | // concatenating a relative path with a host doesn't work: "//example.comfoo" would be wrong |
||
435 | $this->assertSame('//example.com/foo', (string) $uri); |
||
436 | } |
||
437 | |||
438 | public function testRemoveExtraSlashesWihoutHost() |
||
439 | { |
||
440 | // If the path is starting with more than one "/" and no authority is |
||
441 | // present, the starting slashes MUST be reduced to one. |
||
442 | $uri = (new Uri())->withPath('//foo'); |
||
443 | $this->assertSame('//foo', $uri->getPath()); |
||
444 | // URI "//foo" would be interpreted as network reference and thus change the original path to the host |
||
445 | $this->assertSame('/foo', (string) $uri); |
||
446 | } |
||
447 | |||
448 | public function testDefaultReturnValuesOfGetters() |
||
449 | { |
||
450 | $uri = new Uri(); |
||
451 | |||
452 | $this->assertSame('', $uri->getScheme()); |
||
453 | $this->assertSame('', $uri->getAuthority()); |
||
454 | $this->assertSame('', $uri->getUserInfo()); |
||
455 | $this->assertSame('', $uri->getHost()); |
||
456 | $this->assertNull($uri->getPort()); |
||
457 | $this->assertSame('', $uri->getPath()); |
||
458 | $this->assertSame('', $uri->getQuery()); |
||
459 | $this->assertSame('', $uri->getFragment()); |
||
460 | } |
||
461 | |||
462 | public function testImmutability() |
||
463 | { |
||
464 | $uri = new Uri(); |
||
465 | |||
466 | $this->assertNotSame($uri, $uri->withScheme('https')); |
||
467 | $this->assertNotSame($uri, $uri->withUserInfo('user', 'pass')); |
||
468 | $this->assertNotSame($uri, $uri->withHost('example.com')); |
||
469 | $this->assertNotSame($uri, $uri->withPort(8080)); |
||
470 | $this->assertNotSame($uri, $uri->withPath('/path/123')); |
||
471 | $this->assertNotSame($uri, $uri->withQuery('q=abc')); |
||
472 | $this->assertNotSame($uri, $uri->withFragment('test')); |
||
473 | } |
||
474 | } |
||
475 |