diff --git a/src/adapter/ServerRequestAdapter.php b/src/adapter/ServerRequestAdapter.php index cd645384..000a6d04 100644 --- a/src/adapter/ServerRequestAdapter.php +++ b/src/adapter/ServerRequestAdapter.php @@ -281,40 +281,6 @@ public function getRawBody(): string return $body->getContents(); } - /** - * Retrieves the script URL from server parameters, supporting both SAPI and worker environments. - * - * Returns the value of 'SCRIPT_NAME' from server parameters for traditional SAPI-based PSR-7 Application. - * - * For worker-based environments (such as RoadRunner, FrankenPHP), returns an empty string to prevent URL - * duplication, as routing is handled internally and no script file exists. - * - * This method ensures compatibility with both classic and modern PHP runtimes, providing the correct script URL - * context for Yii2 Routing and Request processing. - * - * @param bool $workerMode Whether the application is running in worker mode (RoadRunner, FrankenPHP, etc.). - * - * @return string Script URL of SAPI environments, or empty string for worker mode. - * - * Usage example: - * ```php - * $scriptUrl = $adapter->getScriptUrl(false); - * ``` - */ - public function getScriptUrl(bool $workerMode): string - { - $serverParams = $this->getServerParams(); - - // for traditional PSR-7 apps where 'SCRIPT_NAME' is available - if ($workerMode === false && isset($serverParams['SCRIPT_NAME']) && is_string($serverParams['SCRIPT_NAME'])) { - return $serverParams['SCRIPT_NAME']; - } - - // for PSR-7 workers (RoadRunner, FrankenPHP, etc.) where no script file exists - // return empty to prevent URL duplication as routing is handled internally - return ''; - } - /** * Retrieves server parameters from the PSR-7 ServerRequestInterface. * diff --git a/src/http/Request.php b/src/http/Request.php index 35ac17db..c32d683f 100644 --- a/src/http/Request.php +++ b/src/http/Request.php @@ -82,16 +82,16 @@ final class Request extends \yii\web\Request * attempts to extract and decode credentials from the 'Authorization' header, handling Apache php-cgi scenarios and * validating the decoded data for UTF-8 encoding and proper format. * - * Usage example: - * ```php - * [$username, $password] = $request->getAuthCredentials(); - * ``` - * * @return array Contains exactly two elements. * - 0: username sent via HTTP authentication, `null` if the username is not given. * - 1: password sent via HTTP authentication, `null` if the password is not given. * * @phpstan-return array{0: string|null, 1: string|null} + * + * Usage example: + * ```php + * [$username, $password] = $request->getAuthCredentials(); + * ``` */ public function getAuthCredentials(): array { @@ -155,7 +155,7 @@ public function getAuthCredentials(): array * * @return array|object Request body parameters with the method override parameter removed if present. * - * @phpstan-return array|object + * @phpstan-return array|object * * Usage example: * ```php @@ -317,7 +317,7 @@ public function getMethod(): string * * @throws InvalidConfigException if the configuration is invalid or incomplete. * - * @phpstan-return array|object|null + * @phpstan-return array|object|null * * Usage example: * ```php @@ -512,18 +512,19 @@ public function getRemoteIP(): string|null } /** - * Retrieves the script URL of the current request, supporting PSR-7 and Yii2 fallback. + * Retrieves the script URL for the current request, supporting PSR-7 and Yii2 fallback. * - * Returns the script URL as determined by the PSR-7 adapter if present, using the configured worker mode flag. + * In worker environments (RoadRunner, FrankenPHP, etc.) with a PSR-7 adapter set, returns an empty string, since no + * script file exists and routing is handled by the worker. * - * If no adapter is set, falls back to the parent implementation. + * In traditional mode, returns 'SCRIPT_NAME'. Falls back to the parent implementation if no PSR-7 adapter is set. * - * This method enables seamless access to the script URL in both PSR-7 and Yii2 environments, supporting - * interoperability with modern HTTP stacks and legacy workflows. + * This method enables seamless interoperability with both PSR-7 and Yii2 environments, ensuring the correct script + * URL resolution for modern HTTP stacks and legacy workflows. * - * @throws InvalidConfigException if the configuration is invalid or incomplete. + * @throws InvalidConfigException if unable to determine the entry script URL. * - * @return string Script URL of the current request. + * @return string Script URL for the current request, or an empty string in worker mode. * * Usage example: * ```php @@ -533,7 +534,7 @@ public function getRemoteIP(): string|null public function getScriptUrl(): string { if ($this->adapter !== null) { - return $this->adapter->getScriptUrl($this->workerMode); + return $this->workerMode ? '' : $this->getScriptName(); } return parent::getScriptUrl(); @@ -590,7 +591,9 @@ public function getServerName(): string|null */ public function getServerParam(string $name, mixed $default = null): mixed { - return array_key_exists($name, $this->getServerParams()) ? $this->getServerParams()[$name] : $default; + $params = $this->getServerParams(); + + return array_key_exists($name, $params) ? $params[$name] : $default; } /** @@ -633,7 +636,7 @@ public function getServerParams(): array * * @return array Array of uploaded files for the current request. * - * @phpstan-return array, mixed> + * @phpstan-return array, mixed> * * Usage example: * ```php @@ -776,9 +779,9 @@ public function setPsr7Request(ServerRequestInterface $request): void * * @return array Converted array of Yii2 UploadedFile instances, preserving keys and nesting. * - * @phpstan-param array $uploadedFiles Array of uploaded files or nested arrays to convert. + * @phpstan-param array $uploadedFiles Array of uploaded files or nested arrays to convert. * - * @phpstan-return array, mixed> + * @phpstan-return array, mixed> */ private function convertPsr7ToUploadedFiles(array $uploadedFiles): array { @@ -820,4 +823,22 @@ private function createUploadedFile(UploadedFileInterface $psrFile): UploadedFil ], ); } + + /** + * Retrieves the script name from the current server parameters. + * + * Returns the value of the 'SCRIPT_NAME' server parameter as a string, or an empty string if not set or not a + * string. + * + * This method provides a type-safe way to access the script name for the current request, supporting + * interoperability with modern HTTP stacks and legacy workflows. + * + * @return string Script name from the server parameters, or an empty string if unavailable. + */ + private function getScriptName(): string + { + $scriptUrl = $this->getServerParam('SCRIPT_NAME'); + + return is_string($scriptUrl) ? $scriptUrl : ''; + } } diff --git a/tests/adapter/ServerParamsPsr7Test.php b/tests/adapter/ServerParamsPsr7Test.php index b04f11bb..cf3aaccc 100644 --- a/tests/adapter/ServerParamsPsr7Test.php +++ b/tests/adapter/ServerParamsPsr7Test.php @@ -5,6 +5,7 @@ namespace yii2\extensions\psrbridge\tests\adapter; use PHPUnit\Framework\Attributes\{DataProviderExternal, Group}; +use yii\base\InvalidConfigException; use yii2\extensions\psrbridge\http\Request; use yii2\extensions\psrbridge\tests\provider\RequestProvider; use yii2\extensions\psrbridge\tests\support\FactoryHelper; @@ -92,6 +93,40 @@ public function testResetRemoteHostAfterRequestReset(): void ); } + /** + * @throws InvalidConfigException if the configuration is invalid or incomplete. + */ + public function testReturnEmptyScriptUrlWhenAdapterIsSetInTraditionalModeWithoutScriptName(): void + { + $request = new Request(['workerMode' => false]); + + $request->setPsr7Request( + FactoryHelper::createRequest('GET', '/test'), + ); + + self::assertEmpty( + $request->getScriptUrl(), + "Script URL should be empty when adapter is set in traditional mode without 'SCRIPT_NAME'.", + ); + } + + /** + * @throws InvalidConfigException if the configuration is invalid or incomplete. + */ + public function testReturnEmptyScriptUrlWhenAdapterIsSetInWorkerMode(): void + { + $request = new Request(); + + $request->setPsr7Request( + FactoryHelper::createRequest('GET', '/test'), + ); + + self::assertEmpty( + $request->getScriptUrl(), + "Script URL should be empty when adapter is set in 'worker' mode (default).", + ); + } + #[Group('server-params')] public function testReturnEmptyServerParamsWhenAdapterIsSet(): void { @@ -184,6 +219,26 @@ public function testReturnRemoteIPFromServerParamsCases(mixed $serverValue, stri ); } + /** + * @throws InvalidConfigException if the configuration is invalid or incomplete. + */ + public function testReturnScriptNameWhenAdapterIsSetInTraditionalMode(): void + { + $expectedScriptName = '/app/public/index.php'; + + $request = new Request(['workerMode' => false]); + + $request->setPsr7Request( + FactoryHelper::createRequest('GET', '/test', serverParams: ['SCRIPT_NAME' => $expectedScriptName]), + ); + + self::assertSame( + $expectedScriptName, + $request->getScriptUrl(), + "Script URL should return 'SCRIPT_NAME' when adapter is set in traditional mode.", + ); + } + #[DataProviderExternal(RequestProvider::class, 'serverNameCases')] #[Group('server-name')] public function testReturnServerNameFromServerParamsCases(mixed $serverValue, string|null $expected): void diff --git a/tests/adapter/ServerRequestAdapterTest.php b/tests/adapter/ServerRequestAdapterTest.php index 7cc13541..972cb71d 100644 --- a/tests/adapter/ServerRequestAdapterTest.php +++ b/tests/adapter/ServerRequestAdapterTest.php @@ -477,40 +477,6 @@ public function testReturnEmptyQueryStringWhenAdapterIsSetWithNoQuery(): void ); } - /** - * @throws InvalidConfigException if the configuration is invalid or incomplete. - */ - public function testReturnEmptyScriptUrlWhenAdapterIsSetInTraditionalModeWithoutScriptName(): void - { - $request = new Request(['workerMode' => false]); - - $request->setPsr7Request( - FactoryHelper::createRequest('GET', '/test'), - ); - - self::assertEmpty( - $request->getScriptUrl(), - "Script URL should be empty when adapter is set in traditional mode without 'SCRIPT_NAME'.", - ); - } - - /** - * @throws InvalidConfigException if the configuration is invalid or incomplete. - */ - public function testReturnEmptyScriptUrlWhenAdapterIsSetInWorkerMode(): void - { - $request = new Request(); - - $request->setPsr7Request( - FactoryHelper::createRequest('GET', '/test'), - ); - - self::assertEmpty( - $request->getScriptUrl(), - "Script URL should be empty when adapter is set in 'worker' mode (default).", - ); - } - public function testReturnEmptyStringFromHeaderWhenCsrfHeaderPresentButEmpty(): void { $request = new Request(); @@ -1179,26 +1145,6 @@ public function testReturnRawBodyWhenAdapterIsSetWithEmptyBody(): void ); } - /** - * @throws InvalidConfigException if the configuration is invalid or incomplete. - */ - public function testReturnScriptNameWhenAdapterIsSetInTraditionalMode(): void - { - $expectedScriptName = '/app/public/index.php'; - - $request = new Request(['workerMode' => false]); - - $request->setPsr7Request( - FactoryHelper::createRequest('GET', '/test', serverParams: ['SCRIPT_NAME' => $expectedScriptName]), - ); - - self::assertSame( - $expectedScriptName, - $request->getScriptUrl(), - "Script URL should return 'SCRIPT_NAME' when adapter is set in traditional mode.", - ); - } - public function testReturnUploadedFilesRecursivelyConvertsNestedArrays(): void { $file1 = dirname(__DIR__) . '/support/stub/files/test1.txt';