Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
54897bc
Introduce `Request` bridge implementation with tests.
terabytesoftw Jul 19, 2025
9f6805c
Apply fixes from StyleCI
StyleCIBot Jul 19, 2025
b05e8a9
Fix namespace casing for `Yii` application instantiation in `TestCase`.
terabytesoftw Jul 19, 2025
aa037f1
Remove unused imports from StyleCI configuration.
terabytesoftw Jul 19, 2025
8ff930b
Apply fixes from StyleCI
StyleCIBot Jul 19, 2025
97bceeb
Refactor TestCase to use aliases for Application classes and enable n…
terabytesoftw Jul 19, 2025
d443a55
Merge branch 'feature-2' of github-principal:yii2-extensions/psr-brid…
terabytesoftw Jul 19, 2025
77c76cf
refactor(ServerRequestAdapter, Request): update PHPStan return types …
terabytesoftw Jul 19, 2025
88d0823
Remove unused files: delete 'Contents', 'Page not found', and 'phpsta…
terabytesoftw Jul 19, 2025
59ae770
Add phpstan.neon configuration for static analysis.
terabytesoftw Jul 19, 2025
7c3f738
Add bootstrapFiles parameter to phpstan.neon for test initialization.
terabytesoftw Jul 19, 2025
2b24ad3
refactor(Request, ServerRequestAdapter): pass workerMode to getScript…
terabytesoftw Jul 19, 2025
5318805
refactor(Request, ServerRequestAdapter): simplify cookie handling and…
terabytesoftw Jul 20, 2025
ed38a3c
Apply fixes from StyleCI
StyleCIBot Jul 20, 2025
44b0add
fix(PSR7RequestTest): use consistent Json import for cookie signing.
terabytesoftw Jul 20, 2025
31143ff
fix(PSR7RequestTest): reorder Json import for consistency.
terabytesoftw Jul 20, 2025
19bf30a
feat(PSR7RequestTest): add CSRF token handling tests for header retri…
terabytesoftw Jul 20, 2025
663af6a
refactor(ServerRequestAdapter, Request): unify method retrieval and e…
terabytesoftw Jul 20, 2025
e1f1b7f
feat(PSR7RequestTest): add test for returning PSR-7 request instance …
terabytesoftw Jul 20, 2025
75c58b4
feat(PSR7RequestTest): add tests for `getParsedBody()` method with va…
terabytesoftw Jul 20, 2025
59a627d
feat(PSR7RequestTest): add tests for query parameters handling in PSR…
terabytesoftw Jul 20, 2025
b1e7e7e
feat(`Request`, `PSR7RequestTest`): add `getQueryString()` method to …
terabytesoftw Jul 20, 2025
aa61f02
feat(PSR7RequestTest): add tests for `getRawBody()` method with vario…
terabytesoftw Jul 20, 2025
b645221
feat(PSR7RequestTest): add tests for `getScriptUrl()` method in vario…
terabytesoftw Jul 20, 2025
b10e30f
Apply fixes from StyleCI
StyleCIBot Jul 20, 2025
e53934b
feat(Request, PSR7RequestTest): implement file upload handling and ad…
terabytesoftw Jul 20, 2025
85d144e
Merge branch 'feature-2' of github-principal:yii2-extensions/psr-brid…
terabytesoftw Jul 20, 2025
06cbbc2
feat(Request, PSR7RequestTest, RequestTest, RequestProvider): enhance…
terabytesoftw Jul 20, 2025
7ff30e2
feat(PSR7RequestTest, RequestTest): enhance file upload tests and imp…
terabytesoftw Jul 20, 2025
f299e78
feat(PSR7RequestTest, RequestTest, ServerRequestAdapter): remove unus…
terabytesoftw Jul 20, 2025
b0f3140
Apply fixes from StyleCI
StyleCIBot Jul 20, 2025
08d84cf
feat(PSR7RequestTest): add test for read-only cookie collection when …
terabytesoftw Jul 20, 2025
62a7bd7
fix(PSR7RequestTest): correct string interpolation in uploaded file s…
terabytesoftw Jul 20, 2025
2e44a25
chore: remove unused `test1.txt` file.
terabytesoftw Jul 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ includes:
- phar://phpstan.phar/conf/bleedingEdge.neon

parameters:
bootstrapFiles:
- tests/bootstrap.php

level: max

paths:
Expand Down
2 changes: 1 addition & 1 deletion phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
bootstrap="tests/bootstrap.php"
cacheDirectory="runtime/.phpunit.cache"
colors="true"
executionOrder="depends,defects"
Expand Down
211 changes: 211 additions & 0 deletions src/adapter/ServerRequestAdapter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
<?php

declare(strict_types=1);

namespace yii2\extensions\psrbridge\adapter;

use Psr\Http\Message\{ServerRequestInterface};
use Yii;
use yii\base\InvalidConfigException;
use yii\helpers\Json;
use yii\web\{Cookie, HeaderCollection};

final class ServerRequestAdapter
{
public function __construct(public ServerRequestInterface $psrRequest) {}

/**
* @phpstan-return array<mixed, mixed>|object
*/
public function getBodyParams(string $methodParam): array|object
{
$parsedBody = $this->psrRequest->getParsedBody();

// remove method parameter if present (same logic as parent)
if (is_array($parsedBody) && isset($parsedBody[$methodParam])) {
$bodyParams = $parsedBody;

unset($bodyParams[$methodParam]);

return $bodyParams;
}

return $parsedBody ?? [];
}

/**
* @phpstan-return array<Cookie>
*/
public function getCookies(bool $enableValidation = false, string $validationKey = ''): array

Check warning on line 39 in src/adapter/ServerRequestAdapter.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "FalseValue": @@ @@ /** * @phpstan-return array<Cookie> */ - public function getCookies(bool $enableValidation = false, string $validationKey = ''): array + public function getCookies(bool $enableValidation = true, string $validationKey = ''): array { return $enableValidation ? $this->getValidatedCookies($validationKey) : $this->getSimpleCookies(); }

Check warning on line 39 in src/adapter/ServerRequestAdapter.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "FalseValue": @@ @@ /** * @phpstan-return array<Cookie> */ - public function getCookies(bool $enableValidation = false, string $validationKey = ''): array + public function getCookies(bool $enableValidation = true, string $validationKey = ''): array { return $enableValidation ? $this->getValidatedCookies($validationKey) : $this->getSimpleCookies(); }
{
return $enableValidation
? $this->getValidatedCookies($validationKey)
: $this->getSimpleCookies();
}

public function getHeaders(): HeaderCollection
{
$headerCollection = new HeaderCollection();

foreach ($this->psrRequest->getHeaders() as $name => $values) {
$headerCollection->set((string) $name, implode(', ', $values));
}

return $headerCollection;
}

public function getMethod(string $methodParam = '_method'): string
{
$parsedBody = $this->psrRequest->getParsedBody();

// check for method override in body
if (
is_array($parsedBody) &&
isset($parsedBody[$methodParam]) &&
is_string($parsedBody[$methodParam])
) {
$methodOverride = strtoupper($parsedBody[$methodParam]);

Check warning on line 67 in src/adapter/ServerRequestAdapter.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "UnwrapStrToUpper": @@ @@ $parsedBody = $this->psrRequest->getParsedBody(); // check for method override in body if (is_array($parsedBody) && isset($parsedBody[$methodParam]) && is_string($parsedBody[$methodParam])) { - $methodOverride = strtoupper($parsedBody[$methodParam]); + $methodOverride = $parsedBody[$methodParam]; if (in_array($methodOverride, ['GET', 'HEAD', 'OPTIONS'], true) === false) { return $methodOverride; }

Check warning on line 67 in src/adapter/ServerRequestAdapter.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "UnwrapStrToUpper": @@ @@ $parsedBody = $this->psrRequest->getParsedBody(); // check for method override in body if (is_array($parsedBody) && isset($parsedBody[$methodParam]) && is_string($parsedBody[$methodParam])) { - $methodOverride = strtoupper($parsedBody[$methodParam]); + $methodOverride = $parsedBody[$methodParam]; if (in_array($methodOverride, ['GET', 'HEAD', 'OPTIONS'], true) === false) { return $methodOverride; }

if (in_array($methodOverride, ['GET', 'HEAD', 'OPTIONS'], true) === false) {
return $methodOverride;
}
}

// check for 'X-Http-Method-Override' header
if ($this->psrRequest->hasHeader('X-Http-Method-Override')) {
$overrideHeader = $this->psrRequest->getHeaderLine('X-Http-Method-Override');

if ($overrideHeader !== '') {
return strtoupper($overrideHeader);

Check warning on line 79 in src/adapter/ServerRequestAdapter.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "UnwrapStrToUpper": @@ @@ if ($this->psrRequest->hasHeader('X-Http-Method-Override')) { $overrideHeader = $this->psrRequest->getHeaderLine('X-Http-Method-Override'); if ($overrideHeader !== '') { - return strtoupper($overrideHeader); + return $overrideHeader; } } return $this->psrRequest->getMethod();

Check warning on line 79 in src/adapter/ServerRequestAdapter.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "UnwrapStrToUpper": @@ @@ if ($this->psrRequest->hasHeader('X-Http-Method-Override')) { $overrideHeader = $this->psrRequest->getHeaderLine('X-Http-Method-Override'); if ($overrideHeader !== '') { - return strtoupper($overrideHeader); + return $overrideHeader; } } return $this->psrRequest->getMethod();
}
}

return $this->psrRequest->getMethod();
}

/**
* @phpstan-return array<mixed, mixed>|object|null
*/
public function getParsedBody(): array|object|null
{
return $this->psrRequest->getParsedBody();
}

/**
* @phpstan-return array<mixed, mixed>
*/
public function getQueryParams(): array
{
return $this->psrRequest->getQueryParams();
}

public function getQueryString(): string
{
return $this->psrRequest->getUri()->getQuery();
}

public function getRawBody(): string
{
$body = $this->psrRequest->getBody();

$body->rewind();

return $body->getContents();
}

public function getScriptUrl(bool $workerMode): string
{
$serverParams = $this->psrRequest->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 '';
}

/**
* @phpstan-return array<mixed, mixed>
*/
public function getUploadedFiles(): array
{
return $this->psrRequest->getUploadedFiles();
}

public function getUrl(): string
{
$uri = $this->psrRequest->getUri();
$url = $uri->getPath();

if ($uri->getQuery() !== '') {
$url .= '?' . $uri->getQuery();
}

return $url;
}

/**
* @phpstan-return array<Cookie>
*/
private function getSimpleCookies(): array
{
$cookies = [];
$cookieParams = $this->psrRequest->getCookieParams();

foreach ($cookieParams as $name => $value) {
if ($value !== '') {
$cookies[$name] = new Cookie(
[

Check warning on line 161 in src/adapter/ServerRequestAdapter.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "ArrayItemRemoval": @@ @@ $cookieParams = $this->psrRequest->getCookieParams(); foreach ($cookieParams as $name => $value) { if ($value !== '') { - $cookies[$name] = new Cookie(['name' => $name, 'value' => $value, 'expire' => null]); + $cookies[$name] = new Cookie(['value' => $value, 'expire' => null]); } } return $cookies;

Check warning on line 161 in src/adapter/ServerRequestAdapter.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "ArrayItemRemoval": @@ @@ $cookieParams = $this->psrRequest->getCookieParams(); foreach ($cookieParams as $name => $value) { if ($value !== '') { - $cookies[$name] = new Cookie(['name' => $name, 'value' => $value, 'expire' => null]); + $cookies[$name] = new Cookie(['value' => $value, 'expire' => null]); } } return $cookies;
'name' => $name,
'value' => $value,
'expire' => null,
],
);
}
}

return $cookies;
}

/**
* @phpstan-return array<Cookie>
*/
private function getValidatedCookies(string $validationKey): array
{
if ($validationKey === '') {
throw new InvalidConfigException('Cookie validation key must be provided.');
}

$cookies = [];
$cookieParams = $this->psrRequest->getCookieParams();

foreach ($cookieParams as $name => $value) {
if (is_string($value) && $value !== '') {
$data = Yii::$app->getSecurity()->validateData($value, $validationKey);

if (is_string($data) === false) {
continue;

Check warning on line 190 in src/adapter/ServerRequestAdapter.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "Continue_": @@ @@ if (is_string($value) && $value !== '') { $data = Yii::$app->getSecurity()->validateData($value, $validationKey); if (is_string($data) === false) { - continue; + break; } $decodedData = Json::decode($data, true); if (is_array($decodedData) && isset($decodedData[0], $decodedData[1]) && $decodedData[0] === $name) {

Check warning on line 190 in src/adapter/ServerRequestAdapter.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "Continue_": @@ @@ if (is_string($value) && $value !== '') { $data = Yii::$app->getSecurity()->validateData($value, $validationKey); if (is_string($data) === false) { - continue; + break; } $decodedData = Json::decode($data, true); if (is_array($decodedData) && isset($decodedData[0], $decodedData[1]) && $decodedData[0] === $name) {
}

$decodedData = Json::decode($data, true);

Check warning on line 193 in src/adapter/ServerRequestAdapter.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "TrueValue": @@ @@ if (is_string($data) === false) { continue; } - $decodedData = Json::decode($data, true); + $decodedData = Json::decode($data, false); if (is_array($decodedData) && isset($decodedData[0], $decodedData[1]) && $decodedData[0] === $name) { $cookies[$name] = new Cookie(['name' => $name, 'value' => $decodedData[1], 'expire' => null]); }

Check warning on line 193 in src/adapter/ServerRequestAdapter.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "TrueValue": @@ @@ if (is_string($data) === false) { continue; } - $decodedData = Json::decode($data, true); + $decodedData = Json::decode($data, false); if (is_array($decodedData) && isset($decodedData[0], $decodedData[1]) && $decodedData[0] === $name) { $cookies[$name] = new Cookie(['name' => $name, 'value' => $decodedData[1], 'expire' => null]); }

if (is_array($decodedData) &&
isset($decodedData[0], $decodedData[1]) &&
$decodedData[0] === $name) {
$cookies[$name] = new Cookie(
[

Check warning on line 199 in src/adapter/ServerRequestAdapter.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "ArrayItemRemoval": @@ @@ } $decodedData = Json::decode($data, true); if (is_array($decodedData) && isset($decodedData[0], $decodedData[1]) && $decodedData[0] === $name) { - $cookies[$name] = new Cookie(['name' => $name, 'value' => $decodedData[1], 'expire' => null]); + $cookies[$name] = new Cookie(['value' => $decodedData[1], 'expire' => null]); } } }

Check warning on line 199 in src/adapter/ServerRequestAdapter.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "ArrayItemRemoval": @@ @@ } $decodedData = Json::decode($data, true); if (is_array($decodedData) && isset($decodedData[0], $decodedData[1]) && $decodedData[0] === $name) { - $cookies[$name] = new Cookie(['name' => $name, 'value' => $decodedData[1], 'expire' => null]); + $cookies[$name] = new Cookie(['value' => $decodedData[1], 'expire' => null]); } } }
'name' => $name,
'value' => $decodedData[1],
'expire' => null,
],
);
}
}
}

return $cookies;

Check warning on line 209 in src/adapter/ServerRequestAdapter.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "ArrayOneItem": @@ @@ } } } - return $cookies; + return count($cookies) > 1 ? array_slice($cookies, 0, 1, true) : $cookies; } }

Check warning on line 209 in src/adapter/ServerRequestAdapter.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "ArrayOneItem": @@ @@ } } } - return $cookies; + return count($cookies) > 1 ? array_slice($cookies, 0, 1, true) : $cookies; } }
}
}
2 changes: 1 addition & 1 deletion src/emitter/SapiEmitter.php
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ private function emitBodyRange(StreamInterface $body, int $first, int $last): vo
private function emitHeaders(ResponseInterface $response): void
{
foreach ($response->getHeaders() as $name => $values) {
$name = str_replace(' ', '-', ucwords(str_replace('-', ' ', strtolower((string) $name)), ' -'));
$name = str_replace(' ', '-', ucwords(strtolower(str_replace('-', ' ', (string) $name))));

match ($name) {
'Set-Cookie' => array_map(
Expand Down
Loading