-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #7252 from kenjis/feat-SiteURL
feat: add SiteURI class
- Loading branch information
Showing
6 changed files
with
976 additions
and
1 deletion.
There are no files selected for viewing
This file contains 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 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.