-
Notifications
You must be signed in to change notification settings - Fork 2k
feat: add SiteURI class #7252
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
feat: add SiteURI class #7252
Changes from all commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
7ac527d
feat: add SiteURI class
kenjis 7eec7bb
chore: add system/HTTP/SiteURI.php
kenjis 5593f8a
docs: add @deprecated
kenjis 8a66de5
test: fix incorrect test
kenjis 406128f
refactor: fix typo in method name
kenjis 7d91c0e
docs: update PHPDocs
kenjis b728948
docs: add @deprecated
kenjis 9af4994
feat: add validation for baseURL
kenjis 5fa5305
feat: add getBaseURL() implementation
kenjis c8d5eef
feat: add param $relativePath to constructor
kenjis b30035b
feat: add param $host to constructor
kenjis 9d82338
feat: add param $scheme to constructor
kenjis b231d1c
style: break long line
kenjis 4932572
fix: remove unneeded methods
kenjis c3628d1
fix: change default values to null
kenjis 57afaf9
fix: handling fragment
kenjis 246eea2
fix: host in baseURL may be wrong
kenjis 08bbc18
fix: disable setBaseURL() method
kenjis ab6da61
test: add assertions
kenjis 82a567d
refactor: SiteURI::__construct()
kenjis ccfd79d
test: add test cases for path starting with slash
kenjis e733417
fix: change behavior
kenjis 33fc028
test: use dataProviders
kenjis ba35e57
docs: update comments
kenjis fe754f3
fix: change behavior
kenjis 3f4a9fd
fix: remove $allowedHostnames check
kenjis ecf96eb
test: add test cases
kenjis 5193414
refactor: remove unneeded override
kenjis b24c01c
docs: fix typo in comments
kenjis 3e349e1
docs: make URI::setSilent() deprecated
kenjis e529291
feat: add URI::withScheme() and deprecate URI::setScheme()
kenjis File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,366 @@ | ||
| <?php | ||
|
|
||
| /** | ||
| * This file is part of CodeIgniter 4 framework. | ||
| * | ||
| * (c) CodeIgniter Foundation <[email protected]> | ||
| * | ||
| * For the full copyright and license information, please view | ||
| * the LICENSE file that was distributed with this source code. | ||
| */ | ||
|
|
||
| namespace CodeIgniter\HTTP; | ||
|
|
||
| use BadMethodCallException; | ||
| use CodeIgniter\Exceptions\ConfigException; | ||
| use CodeIgniter\HTTP\Exceptions\HTTPException; | ||
| use Config\App; | ||
|
|
||
| /** | ||
| * URI for the application site | ||
| */ | ||
| class SiteURI extends URI | ||
| { | ||
| /** | ||
| * The current baseURL. | ||
| */ | ||
| private URI $baseURL; | ||
|
|
||
| /** | ||
| * The path part of baseURL. | ||
| * | ||
| * The baseURL "http://example.com/" → '/' | ||
| * The baseURL "http://localhost:8888/ci431/public/" → '/ci431/public/' | ||
| */ | ||
| private string $basePathWithoutIndexPage; | ||
|
|
||
| /** | ||
| * The Index File. | ||
| */ | ||
| private string $indexPage; | ||
|
|
||
| /** | ||
| * List of URI segments in baseURL and indexPage. | ||
| * | ||
| * If the URI is "http://localhost:8888/ci431/public/index.php/test?a=b", | ||
| * and the baseURL is "http://localhost:8888/ci431/public/", then: | ||
| * $baseSegments = [ | ||
| * 0 => 'ci431', | ||
| * 1 => 'public', | ||
| * 2 => 'index.php', | ||
| * ]; | ||
| */ | ||
| private array $baseSegments; | ||
|
|
||
| /** | ||
| * List of URI segments after indexPage. | ||
| * | ||
| * The word "URI Segments" originally means only the URI path part relative | ||
| * to the baseURL. | ||
| * | ||
| * If the URI is "http://localhost:8888/ci431/public/index.php/test?a=b", | ||
| * and the baseURL is "http://localhost:8888/ci431/public/", then: | ||
| * $segments = [ | ||
| * 0 => 'test', | ||
| * ]; | ||
| * | ||
| * @var array | ||
| * | ||
| * @deprecated This property will be private. | ||
| */ | ||
| protected $segments; | ||
|
|
||
| /** | ||
| * URI path relative to baseURL. | ||
| * | ||
| * If the baseURL contains sub folders, this value will be different from | ||
| * the current URI path. | ||
| * | ||
| * This value never starts with '/'. | ||
| */ | ||
| private string $routePath; | ||
|
|
||
| /** | ||
| * @param string $relativePath URI path relative to baseURL. May include | ||
| * queries or fragments. | ||
| * @param string|null $host Optional current hostname. | ||
| * @param string|null $scheme Optional scheme. 'http' or 'https'. | ||
| * @phpstan-param 'http'|'https'|null $scheme | ||
| */ | ||
| public function __construct( | ||
| App $configApp, | ||
| string $relativePath = '', | ||
| ?string $host = null, | ||
| ?string $scheme = null | ||
| ) { | ||
| $this->indexPage = $configApp->indexPage; | ||
|
|
||
| $this->baseURL = $this->determineBaseURL($configApp, $host, $scheme); | ||
|
|
||
| $this->setBasePath(); | ||
|
|
||
| // Fix routePath, query, fragment | ||
| [$routePath, $query, $fragment] = $this->parseRelativePath($relativePath); | ||
|
|
||
| // Fix indexPage and routePath | ||
| $indexPageRoutePath = $this->getIndexPageRoutePath($routePath); | ||
|
|
||
| // Fix the current URI | ||
| $uri = $this->baseURL . $indexPageRoutePath; | ||
|
|
||
| // applyParts | ||
| $parts = parse_url($uri); | ||
| if ($parts === false) { | ||
| throw HTTPException::forUnableToParseURI($uri); | ||
| } | ||
| $parts['query'] = $query; | ||
| $parts['fragment'] = $fragment; | ||
| $this->applyParts($parts); | ||
|
|
||
| $this->setRoutePath($routePath); | ||
| } | ||
|
|
||
| private function parseRelativePath(string $relativePath): array | ||
| { | ||
| $parts = parse_url('http://dummy/' . $relativePath); | ||
| if ($parts === false) { | ||
| throw HTTPException::forUnableToParseURI($relativePath); | ||
| } | ||
|
|
||
| $routePath = $relativePath === '/' ? '/' : ltrim($parts['path'], '/'); | ||
|
|
||
| $query = $parts['query'] ?? ''; | ||
| $fragment = $parts['fragment'] ?? ''; | ||
|
|
||
| return [$routePath, $query, $fragment]; | ||
| } | ||
|
|
||
| private function determineBaseURL( | ||
| App $configApp, | ||
| ?string $host, | ||
| ?string $scheme | ||
| ): URI { | ||
| $baseURL = $this->normalizeBaseURL($configApp); | ||
|
|
||
| $uri = new URI($baseURL); | ||
|
|
||
| // Update scheme | ||
| if ($scheme !== null) { | ||
| $uri->setScheme($scheme); | ||
| } elseif ($configApp->forceGlobalSecureRequests) { | ||
| $uri->setScheme('https'); | ||
| } | ||
|
|
||
| // Update host | ||
| if ($host !== null) { | ||
| $uri->setHost($host); | ||
| } | ||
|
|
||
| return $uri; | ||
| } | ||
|
|
||
| private function getIndexPageRoutePath(string $routePath): string | ||
| { | ||
| // Remove starting slash unless it is `/`. | ||
| if ($routePath !== '' && $routePath[0] === '/' && $routePath !== '/') { | ||
| $routePath = ltrim($routePath, '/'); | ||
| } | ||
|
|
||
| // Check for an index page | ||
| $indexPage = ''; | ||
| if ($this->indexPage !== '') { | ||
| $indexPage = $this->indexPage; | ||
|
|
||
| // Check if we need a separator | ||
| if ($routePath !== '' && $routePath[0] !== '/' && $routePath[0] !== '?') { | ||
| $indexPage .= '/'; | ||
| } | ||
| } | ||
|
|
||
| $indexPageRoutePath = $indexPage . $routePath; | ||
|
|
||
| if ($indexPageRoutePath === '/') { | ||
| $indexPageRoutePath = ''; | ||
| } | ||
|
|
||
| return $indexPageRoutePath; | ||
| } | ||
|
|
||
| private function normalizeBaseURL(App $configApp): string | ||
| { | ||
| // It's possible the user forgot a trailing slash on their | ||
| // baseURL, so let's help them out. | ||
| $baseURL = rtrim($configApp->baseURL, '/ ') . '/'; | ||
|
|
||
| // Validate baseURL | ||
| if (filter_var($baseURL, FILTER_VALIDATE_URL) === false) { | ||
| throw new ConfigException( | ||
| 'Config\App::$baseURL is invalid.' | ||
| ); | ||
| } | ||
|
|
||
| return $baseURL; | ||
| } | ||
|
|
||
| /** | ||
| * Sets basePathWithoutIndexPage and baseSegments. | ||
| */ | ||
| private function setBasePath(): void | ||
| { | ||
| $this->basePathWithoutIndexPage = $this->baseURL->getPath(); | ||
|
|
||
| $this->baseSegments = $this->convertToSegments($this->basePathWithoutIndexPage); | ||
|
|
||
| if ($this->indexPage) { | ||
| $this->baseSegments[] = $this->indexPage; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @deprecated | ||
| */ | ||
| public function setBaseURL(string $baseURL): void | ||
| { | ||
| throw new BadMethodCallException('Cannot use this method.'); | ||
| } | ||
|
|
||
| /** | ||
| * @deprecated | ||
| */ | ||
| public function setURI(?string $uri = null) | ||
| { | ||
| throw new BadMethodCallException('Cannot use this method.'); | ||
| } | ||
|
|
||
| /** | ||
| * Returns the baseURL. | ||
| * | ||
| * @interal | ||
| */ | ||
| public function getBaseURL(): string | ||
| { | ||
| return (string) $this->baseURL; | ||
| } | ||
|
|
||
| /** | ||
| * Returns the URI path relative to baseURL. | ||
| * | ||
| * @return string The Route path. | ||
| */ | ||
| public function getRoutePath(): string | ||
| { | ||
| return $this->routePath; | ||
| } | ||
|
|
||
| /** | ||
| * Formats the URI as a string. | ||
| */ | ||
| public function __toString(): string | ||
| { | ||
| return static::createURIString( | ||
| $this->getScheme(), | ||
| $this->getAuthority(), | ||
| $this->getPath(), | ||
| $this->getQuery(), | ||
| $this->getFragment() | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Sets the route path (and segments). | ||
| * | ||
| * @return $this | ||
| */ | ||
| public function setPath(string $path) | ||
| { | ||
| $this->setRoutePath($path); | ||
|
|
||
| return $this; | ||
| } | ||
|
|
||
| /** | ||
| * Sets the route path (and segments). | ||
| */ | ||
| private function setRoutePath(string $routePath): void | ||
| { | ||
| $routePath = $this->filterPath($routePath); | ||
|
|
||
| $indexPageRoutePath = $this->getIndexPageRoutePath($routePath); | ||
|
|
||
| $this->path = $this->basePathWithoutIndexPage . $indexPageRoutePath; | ||
|
|
||
| $this->routePath = ltrim($routePath, '/'); | ||
|
|
||
| $this->segments = $this->convertToSegments($this->routePath); | ||
| } | ||
|
|
||
| /** | ||
| * Converts path to segments | ||
| */ | ||
| private function convertToSegments(string $path): array | ||
| { | ||
| $tempPath = trim($path, '/'); | ||
|
|
||
| return ($tempPath === '') ? [] : explode('/', $tempPath); | ||
| } | ||
|
|
||
| /** | ||
| * Sets the path portion of the URI based on segments. | ||
| * | ||
| * @return $this | ||
| * | ||
| * @deprecated This method will be private. | ||
| */ | ||
| public function refreshPath() | ||
| { | ||
| $allSegments = array_merge($this->baseSegments, $this->segments); | ||
| $this->path = '/' . $this->filterPath(implode('/', $allSegments)); | ||
|
|
||
| if ($this->routePath === '/' && $this->path !== '/') { | ||
| $this->path .= '/'; | ||
| } | ||
|
|
||
| $this->routePath = $this->filterPath(implode('/', $this->segments)); | ||
|
|
||
| return $this; | ||
| } | ||
|
|
||
| /** | ||
| * Saves our parts from a parse_url() call. | ||
| */ | ||
| protected function applyParts(array $parts) | ||
| { | ||
| if (! empty($parts['host'])) { | ||
| $this->host = $parts['host']; | ||
| } | ||
| if (! empty($parts['user'])) { | ||
| $this->user = $parts['user']; | ||
| } | ||
| if (isset($parts['path']) && $parts['path'] !== '') { | ||
| $this->path = $this->filterPath($parts['path']); | ||
| } | ||
| if (! empty($parts['query'])) { | ||
| $this->setQuery($parts['query']); | ||
| } | ||
| if (! empty($parts['fragment'])) { | ||
| $this->fragment = $parts['fragment']; | ||
| } | ||
|
|
||
| // Scheme | ||
| if (isset($parts['scheme'])) { | ||
| $this->setScheme(rtrim($parts['scheme'], ':/')); | ||
| } else { | ||
| $this->setScheme('http'); | ||
| } | ||
|
|
||
| // Port | ||
| if (isset($parts['port']) && $parts['port'] !== null) { | ||
| // Valid port numbers are enforced by earlier parse_url() or setPort() | ||
| $this->port = $parts['port']; | ||
| } | ||
|
|
||
| if (isset($parts['pass'])) { | ||
| $this->password = $parts['pass']; | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.