• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

codeigniter4 / CodeIgniter4 / 14518647775

17 Apr 2025 02:59PM UTC coverage: 84.437% (-0.001%) from 84.438%
14518647775

Pull #9525

github

web-flow
Merge 162501b0f into 92ff74e4b
Pull Request #9525: refactor: fix phpstan errors in `URI` and `SiteURI`

39 of 39 new or added lines in 2 files covered. (100.0%)

1 existing line in 1 file now uncovered.

20812 of 24648 relevant lines covered (84.44%)

191.18 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.59
/system/HTTP/SiteURI.php
1
<?php
2

3
declare(strict_types=1);
4

5
/**
6
 * This file is part of CodeIgniter 4 framework.
7
 *
8
 * (c) CodeIgniter Foundation <admin@codeigniter.com>
9
 *
10
 * For the full copyright and license information, please view
11
 * the LICENSE file that was distributed with this source code.
12
 */
13

14
namespace CodeIgniter\HTTP;
15

16
use CodeIgniter\Exceptions\BadMethodCallException;
17
use CodeIgniter\Exceptions\ConfigException;
18
use CodeIgniter\HTTP\Exceptions\HTTPException;
19
use Config\App;
20

21
/**
22
 * URI for the application site
23
 *
24
 * @see \CodeIgniter\HTTP\SiteURITest
25
 */
26
class SiteURI extends URI
27
{
28
    /**
29
     * The current baseURL.
30
     */
31
    private readonly URI $baseURL;
32

33
    /**
34
     * The path part of baseURL.
35
     *
36
     * The baseURL "http://example.com/" → '/'
37
     * The baseURL "http://localhost:8888/ci431/public/" → '/ci431/public/'
38
     */
39
    private string $basePathWithoutIndexPage;
40

41
    /**
42
     * The Index File.
43
     */
44
    private readonly string $indexPage;
45

46
    /**
47
     * List of URI segments in baseURL and indexPage.
48
     *
49
     * If the URI is "http://localhost:8888/ci431/public/index.php/test?a=b",
50
     * and the baseURL is "http://localhost:8888/ci431/public/", then:
51
     *   $baseSegments = [
52
     *       0 => 'ci431',
53
     *       1 => 'public',
54
     *       2 => 'index.php',
55
     *   ];
56
     */
57
    private array $baseSegments;
58

59
    /**
60
     * List of URI segments after indexPage.
61
     *
62
     * The word "URI Segments" originally means only the URI path part relative
63
     * to the baseURL.
64
     *
65
     * If the URI is "http://localhost:8888/ci431/public/index.php/test?a=b",
66
     * and the baseURL is "http://localhost:8888/ci431/public/", then:
67
     *   $segments = [
68
     *       0 => 'test',
69
     *   ];
70
     *
71
     * @var array<int, string>
72
     *
73
     * @deprecated This property will be private.
74
     */
75
    protected $segments;
76

77
    /**
78
     * URI path relative to baseURL.
79
     *
80
     * If the baseURL contains sub folders, this value will be different from
81
     * the current URI path.
82
     *
83
     * This value never starts with '/'.
84
     */
85
    private string $routePath;
86

87
    /**
88
     * @param         string              $relativePath URI path relative to baseURL. May include
89
     *                                                  queries or fragments.
90
     * @param         string|null         $host         Optional current hostname.
91
     * @param         string|null         $scheme       Optional scheme. 'http' or 'https'.
92
     * @phpstan-param 'http'|'https'|null $scheme
93
     */
94
    public function __construct(
95
        App $configApp,
96
        string $relativePath = '',
97
        ?string $host = null,
98
        ?string $scheme = null,
99
    ) {
100
        $this->indexPage = $configApp->indexPage;
1,442✔
101

102
        $this->baseURL = $this->determineBaseURL($configApp, $host, $scheme);
1,442✔
103

104
        $this->setBasePath();
1,441✔
105

106
        // Fix routePath, query, fragment
107
        [$routePath, $query, $fragment] = $this->parseRelativePath($relativePath);
1,441✔
108

109
        // Fix indexPage and routePath
110
        $indexPageRoutePath = $this->getIndexPageRoutePath($routePath);
1,441✔
111

112
        // Fix the current URI
113
        $uri = $this->baseURL . $indexPageRoutePath;
1,441✔
114

115
        // applyParts
116
        $parts = parse_url($uri);
1,441✔
117
        if ($parts === false) {
1,441✔
118
            throw HTTPException::forUnableToParseURI($uri);
×
119
        }
120
        $parts['query']    = $query;
1,441✔
121
        $parts['fragment'] = $fragment;
1,441✔
122
        $this->applyParts($parts);
1,441✔
123

124
        $this->setRoutePath($routePath);
1,441✔
125
    }
126

127
    private function parseRelativePath(string $relativePath): array
128
    {
129
        $parts = parse_url('http://dummy/' . $relativePath);
1,441✔
130
        if ($parts === false) {
1,441✔
131
            throw HTTPException::forUnableToParseURI($relativePath);
×
132
        }
133

134
        $routePath = $relativePath === '/' ? '/' : ltrim($parts['path'], '/');
1,441✔
135

136
        $query    = $parts['query'] ?? '';
1,441✔
137
        $fragment = $parts['fragment'] ?? '';
1,441✔
138

139
        return [$routePath, $query, $fragment];
1,441✔
140
    }
141

142
    private function determineBaseURL(
143
        App $configApp,
144
        ?string $host,
145
        ?string $scheme,
146
    ): URI {
147
        $baseURL = $this->normalizeBaseURL($configApp);
1,442✔
148

149
        $uri = new URI($baseURL);
1,441✔
150

151
        // Update scheme
152
        if ($scheme !== null && $scheme !== '') {
1,441✔
153
            $uri->setScheme($scheme);
66✔
154
        } elseif ($configApp->forceGlobalSecureRequests) {
1,434✔
155
            $uri->setScheme('https');
6✔
156
        }
157

158
        // Update host
159
        if ($host !== null) {
1,441✔
160
            $uri->setHost($host);
105✔
161
        }
162

163
        return $uri;
1,441✔
164
    }
165

166
    private function getIndexPageRoutePath(string $routePath): string
167
    {
168
        // Remove starting slash unless it is `/`.
169
        if ($routePath !== '' && $routePath[0] === '/' && $routePath !== '/') {
1,441✔
170
            $routePath = ltrim($routePath, '/');
4✔
171
        }
172

173
        // Check for an index page
174
        $indexPage = '';
1,441✔
175
        if ($this->indexPage !== '') {
1,441✔
176
            $indexPage = $this->indexPage;
1,365✔
177

178
            // Check if we need a separator
179
            if ($routePath !== '' && $routePath[0] !== '/' && $routePath[0] !== '?') {
1,365✔
180
                $indexPage .= '/';
264✔
181
            }
182
        }
183

184
        $indexPageRoutePath = $indexPage . $routePath;
1,441✔
185

186
        if ($indexPageRoutePath === '/') {
1,441✔
187
            $indexPageRoutePath = '';
31✔
188
        }
189

190
        return $indexPageRoutePath;
1,441✔
191
    }
192

193
    private function normalizeBaseURL(App $configApp): string
194
    {
195
        // It's possible the user forgot a trailing slash on their
196
        // baseURL, so let's help them out.
197
        $baseURL = rtrim($configApp->baseURL, '/ ') . '/';
1,442✔
198

199
        // Validate baseURL
200
        if (filter_var($baseURL, FILTER_VALIDATE_URL) === false) {
1,442✔
201
            throw new ConfigException(
1✔
202
                'Config\App::$baseURL "' . $baseURL . '" is not a valid URL.',
1✔
203
            );
1✔
204
        }
205

206
        return $baseURL;
1,441✔
207
    }
208

209
    /**
210
     * Sets basePathWithoutIndexPage and baseSegments.
211
     */
212
    private function setBasePath(): void
213
    {
214
        $this->basePathWithoutIndexPage = $this->baseURL->getPath();
1,441✔
215

216
        $this->baseSegments = $this->convertToSegments($this->basePathWithoutIndexPage);
1,441✔
217

218
        if ($this->indexPage !== '') {
1,441✔
219
            $this->baseSegments[] = $this->indexPage;
1,365✔
220
        }
221
    }
222

223
    /**
224
     * @deprecated
225
     */
226
    public function setBaseURL(string $baseURL): void
227
    {
228
        throw new BadMethodCallException('Cannot use this method.');
1✔
229
    }
230

231
    /**
232
     * @deprecated
233
     */
234
    public function setURI(?string $uri = null)
235
    {
236
        throw new BadMethodCallException('Cannot use this method.');
1✔
237
    }
238

239
    /**
240
     * Returns the baseURL.
241
     *
242
     * @interal
243
     */
244
    public function getBaseURL(): string
245
    {
246
        return (string) $this->baseURL;
33✔
247
    }
248

249
    /**
250
     * Returns the URI path relative to baseURL.
251
     *
252
     * @return string The Route path.
253
     */
254
    public function getRoutePath(): string
255
    {
256
        return $this->routePath;
1,387✔
257
    }
258

259
    /**
260
     * Formats the URI as a string.
261
     */
262
    public function __toString(): string
263
    {
264
        return static::createURIString(
278✔
265
            $this->getScheme(),
278✔
266
            $this->getAuthority(),
278✔
267
            $this->getPath(),
278✔
268
            $this->getQuery(),
278✔
269
            $this->getFragment(),
278✔
270
        );
278✔
271
    }
272

273
    /**
274
     * Sets the route path (and segments).
275
     *
276
     * @return $this
277
     */
278
    public function setPath(string $path)
279
    {
280
        $this->setRoutePath($path);
66✔
281

282
        return $this;
66✔
283
    }
284

285
    /**
286
     * Sets the route path (and segments).
287
     */
288
    private function setRoutePath(string $routePath): void
289
    {
290
        $routePath = $this->filterPath($routePath);
1,441✔
291

292
        $indexPageRoutePath = $this->getIndexPageRoutePath($routePath);
1,441✔
293

294
        $this->path = $this->basePathWithoutIndexPage . $indexPageRoutePath;
1,441✔
295

296
        $this->routePath = ltrim($routePath, '/');
1,441✔
297

298
        $this->segments = $this->convertToSegments($this->routePath);
1,441✔
299
    }
300

301
    /**
302
     * Converts path to segments
303
     */
304
    private function convertToSegments(string $path): array
305
    {
306
        $tempPath = trim($path, '/');
1,441✔
307

308
        return ($tempPath === '') ? [] : explode('/', $tempPath);
1,441✔
309
    }
310

311
    /**
312
     * Sets the path portion of the URI based on segments.
313
     *
314
     * @return $this
315
     *
316
     * @deprecated This method will be private.
317
     */
318
    public function refreshPath()
319
    {
320
        $allSegments = array_merge($this->baseSegments, $this->segments);
5✔
321
        $this->path  = '/' . $this->filterPath(implode('/', $allSegments));
5✔
322

323
        if ($this->routePath === '/' && $this->path !== '/') {
5✔
324
            $this->path .= '/';
×
325
        }
326

327
        $this->routePath = $this->filterPath(implode('/', $this->segments));
5✔
328

329
        return $this;
5✔
330
    }
331

332
    /**
333
     * Saves our parts from a parse_url() call.
334
     *
335
     * @param array{
336
     *  host?: string,
337
     *  user?: string,
338
     *  path?: string,
339
     *  query?: string,
340
     *  fragment?: string,
341
     *  scheme?: string,
342
     *  port?: int,
343
     *  pass?: string,
344
     * } $parts
345
     */
346
    protected function applyParts(array $parts): void
347
    {
348
        if (isset($parts['host']) && $parts['host'] !== '') {
1,441✔
349
            $this->host = $parts['host'];
1,441✔
350
        }
351

352
        if (isset($parts['user']) && $parts['user'] !== '') {
1,441✔
UNCOV
353
            $this->user = $parts['user'];
×
354
        }
355

356
        if (isset($parts['path']) && $parts['path'] !== '') {
1,441✔
357
            $this->path = $this->filterPath($parts['path']);
1,441✔
358
        }
359

360
        if (isset($parts['query']) && $parts['query'] !== '') {
1,441✔
361
            $this->setQuery($parts['query']);
29✔
362
        }
363

364
        if (isset($parts['fragment']) && $parts['fragment'] !== '') {
1,441✔
365
            $this->fragment = $parts['fragment'];
18✔
366
        }
367

368
        if (isset($parts['scheme'])) {
1,441✔
369
            $this->setScheme(rtrim($parts['scheme'], ':/'));
1,441✔
370
        } else {
371
            $this->setScheme('http');
×
372
        }
373

374
        if (isset($parts['port'])) {
1,441✔
375
            // Valid port numbers are enforced by earlier parse_url or setPort()
376
            $this->port = $parts['port'];
77✔
377
        }
378

379
        if (isset($parts['pass'])) {
1,441✔
380
            $this->password = $parts['pass'];
×
381
        }
382
    }
383

384
    /**
385
     * For base_url() helper.
386
     *
387
     * @param array|string $relativePath URI string or array of URI segments.
388
     * @param string|null  $scheme       URI scheme. E.g., http, ftp. If empty
389
     *                                   string '' is set, a protocol-relative
390
     *                                   link is returned.
391
     */
392
    public function baseUrl($relativePath = '', ?string $scheme = null): string
393
    {
394
        $relativePath = $this->stringifyRelativePath($relativePath);
66✔
395

396
        $config            = clone config(App::class);
66✔
397
        $config->indexPage = '';
66✔
398

399
        $host = $this->getHost();
66✔
400

401
        $uri = new self($config, $relativePath, $host, $scheme);
66✔
402

403
        // Support protocol-relative links
404
        if ($scheme === '') {
66✔
405
            return substr((string) $uri, strlen($uri->getScheme()) + 1);
2✔
406
        }
407

408
        return (string) $uri;
64✔
409
    }
410

411
    /**
412
     * @param array|string $relativePath URI string or array of URI segments
413
     */
414
    private function stringifyRelativePath($relativePath): string
415
    {
416
        if (is_array($relativePath)) {
132✔
417
            $relativePath = implode('/', $relativePath);
2✔
418
        }
419

420
        return $relativePath;
132✔
421
    }
422

423
    /**
424
     * For site_url() helper.
425
     *
426
     * @param array|string $relativePath URI string or array of URI segments.
427
     * @param string|null  $scheme       URI scheme. E.g., http, ftp. If empty
428
     *                                   string '' is set, a protocol-relative
429
     *                                   link is returned.
430
     * @param App|null     $config       Alternate configuration to use.
431
     */
432
    public function siteUrl($relativePath = '', ?string $scheme = null, ?App $config = null): string
433
    {
434
        $relativePath = $this->stringifyRelativePath($relativePath);
122✔
435

436
        // Check current host.
437
        $host = $config instanceof App ? null : $this->getHost();
122✔
438

439
        $config ??= config(App::class);
122✔
440

441
        $uri = new self($config, $relativePath, $host, $scheme);
122✔
442

443
        // Support protocol-relative links
444
        if ($scheme === '') {
122✔
445
            return substr((string) $uri, strlen($uri->getScheme()) + 1);
2✔
446
        }
447

448
        return (string) $uri;
120✔
449
    }
450
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc