diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8778be5..c0e1824 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,7 +23,7 @@ name: build jobs: phpunit: - uses: yii-tools/actions/.github/workflows/build.yml@main + uses: yii-tools/actions/.github/workflows/codeception.yml@main with: extensions: fileinfo, intl os: >- diff --git a/composer.json b/composer.json index 8ef8a54..61cd650 100644 --- a/composer.json +++ b/composer.json @@ -13,6 +13,7 @@ "prefer-stable": true, "require": { "php": "^8.1", + "httpsoft/http-server-request": "^1.1", "psr/container": "^2.0", "psr/http-factory": "^1.0", "psr/http-message": "^1.1", @@ -38,12 +39,11 @@ "yiisoft/yii-http": "^1.0" }, "require-dev": { - "codeception/c3": "^2.7", + "codeception/c3": "^2.8", "codeception/codeception": "^5.0", "codeception/module-asserts": "^3.0", "codeception/module-cli": "^2.0", "codeception/module-phpbrowser": "^3.0", - "httpsoft/http-message": "^1.1", "maglnet/composer-require-checker": "^4.6", "symfony/process": "^6.3", "vimeo/psalm": "^5.9", diff --git a/public/index-test.php b/public/index-test.php new file mode 100644 index 0000000..5a03c1b --- /dev/null +++ b/public/index-test.php @@ -0,0 +1,40 @@ +withDebug(false)->withRootPath(\dirname(__DIR__))->run(); diff --git a/public/index.php b/public/index.php index d7944cb..cbea957 100644 --- a/public/index.php +++ b/public/index.php @@ -4,13 +4,6 @@ use Yii\Framework\Runner\HttpApplication; -if (getenv('YII_C3')) { - $c3 = dirname(__DIR__) . '/c3.php'; - if (file_exists($c3)) { - require_once $c3; - } -} - /** * @psalm-var string $_SERVER['REQUEST_URI'] * diff --git a/src/Framework/Runner/HttpApplication.php b/src/Framework/Runner/HttpApplication.php index ed420ca..0139857 100644 --- a/src/Framework/Runner/HttpApplication.php +++ b/src/Framework/Runner/HttpApplication.php @@ -5,6 +5,7 @@ namespace Yii\Framework\Runner; use ErrorException; +use HttpSoft\ServerRequest\ServerRequestCreator; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; use Psr\Http\Message\ResponseInterface; @@ -108,10 +109,9 @@ public function run(): void $application = $container->get(Application::class); /** - * @var ServerRequestFactory $serverRequestFactory + * @var ServerRequestInterface $serverRequestFactory */ - $serverRequestFactory = $this->get(ServerRequestFactory::class); - $serverRequestFactory = $serverRequestFactory->createFromGlobals(); + $serverRequestFactory = ServerRequestCreator::createFromGlobals(); $serverRequest = $serverRequestFactory->withAttribute('applicationStartTime', $startTime); try { diff --git a/src/Framework/Runner/ServerRequestFactory.php b/src/Framework/Runner/ServerRequestFactory.php deleted file mode 100644 index 4b65708..0000000 --- a/src/Framework/Runner/ServerRequestFactory.php +++ /dev/null @@ -1,289 +0,0 @@ - $_SERVER */ - return $this->createFromParameters( - $_SERVER, - $this->getHeadersFromGlobals(), - $_COOKIE, - $_GET, - $_POST, - $_FILES, - fopen('php://input', 'rb') ?: null - ); - } - - /** - * Creates an instance of a server request from custom parameters. - * - * @param resource|StreamInterface|string|null $body - * - * @psalm-param array $server - * @psalm-param array $headers - * @psalm-param mixed $body - * - * @return ServerRequestInterface The server request instance. - */ - public function createFromParameters( - array $server, - array $headers = [], - array $cookies = [], - array $get = [], - array $post = [], - array $files = [], - mixed $body = null - ): ServerRequestInterface { - $method = $server['REQUEST_METHOD'] ?? null; - - if ($method === null) { - throw new RuntimeException('Unable to determine HTTP request method.'); - } - - $uri = $this->getUri($server); - $request = $this->serverRequestFactory->createServerRequest($method, $uri, $server); - - foreach ($headers as $name => $value) { - if ($name === 'Host' && $request->hasHeader('Host')) { - continue; - } - - $request = $request->withAddedHeader($name, $value); - } - - $protocol = '1.1'; - if (array_key_exists('SERVER_PROTOCOL', $server) && $server['SERVER_PROTOCOL'] !== '') { - $protocol = str_replace('HTTP/', '', $server['SERVER_PROTOCOL']); - } - - $request = $request - ->withProtocolVersion($protocol) - ->withQueryParams($get) - ->withParsedBody($post) - ->withCookieParams($cookies) - ->withUploadedFiles($this->getUploadedFilesArray($files)) - ; - - if ($body === null) { - return $request; - } - - if ($body instanceof StreamInterface) { - return $request->withBody($body); - } - - if (is_string($body)) { - return $request->withBody($this->streamFactory->createStream($body)); - } - - if (is_resource($body)) { - return $request->withBody($this->streamFactory->createStreamFromResource($body)); - } - - throw new InvalidArgumentException( - 'Body parameter for "ServerRequestFactory::createFromParameters()"' - . 'must be instance of StreamInterface, resource or null.', - ); - } - - /** - * @psalm-param array $server - */ - private function getUri(array $server): UriInterface - { - $uri = $this->uriFactory->createUri(); - - if (array_key_exists('HTTPS', $server) && $server['HTTPS'] !== '' && $server['HTTPS'] !== 'off') { - $uri = $uri->withScheme('https'); - } else { - $uri = $uri->withScheme('http'); - } - - $uri = isset($server['SERVER_PORT']) - ? $uri->withPort((int)$server['SERVER_PORT']) - : $uri->withPort($uri->getScheme() === 'https' ? 443 : 80); - - if (isset($server['HTTP_HOST'])) { - $uri = preg_match('/^(.+):(\d+)$/', $server['HTTP_HOST'], $matches) === 1 - ? $uri - ->withHost($matches[1]) - ->withPort((int) $matches[2]) - : $uri->withHost($server['HTTP_HOST']) - ; - } elseif (isset($server['SERVER_NAME'])) { - $uri = $uri->withHost($server['SERVER_NAME']); - } - - if (isset($server['REQUEST_URI'])) { - $uri = $uri->withPath(explode('?', $server['REQUEST_URI'])[0]); - } - - if (isset($server['QUERY_STRING'])) { - $uri = $uri->withQuery($server['QUERY_STRING']); - } - - return $uri; - } - - /** - * @psalm-return array - */ - private function getHeadersFromGlobals(): array - { - if (function_exists('getallheaders') && ($headers = getallheaders()) !== false) { - /** @psalm-var array $headers */ - return $headers; - } - - $headers = []; - - /** - * @var string $name - * @var string $value - */ - foreach ($_SERVER as $name => $value) { - if (str_starts_with($name, 'REDIRECT_')) { - $name = substr($name, 9); - - if (array_key_exists($name, $_SERVER)) { - continue; - } - } - - if (str_starts_with($name, 'HTTP_')) { - $headers[$this->normalizeHeaderName(substr($name, 5))] = $value; - continue; - } - - if (str_starts_with($name, 'CONTENT_')) { - $headers[$this->normalizeHeaderName($name)] = $value; - } - } - - return $headers; - } - - private function normalizeHeaderName(string $name): string - { - return str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $name)))); - } - - private function getUploadedFilesArray(array $filesArray): array - { - $files = []; - - /** @var array $info */ - foreach ($filesArray as $class => $info) { - $files[$class] = []; - $this->populateUploadedFileRecursive( - $files[$class], - $info['name'], - $info['tmp_name'], - $info['type'], - $info['size'], - $info['error'], - ); - } - - return $files; - } - - /** - * Populates uploaded files array from $_FILE data structure recursively. - * - * @param array $files Uploaded files array to be populated. - * @param mixed $names File names provided by PHP. - * @param mixed $tempNames Temporary file names provided by PHP. - * @param mixed $types File types provided by PHP. - * @param mixed $sizes File sizes provided by PHP. - * @param mixed $errors Uploading issues provided by PHP. - * - * @psalm-suppress MixedArgument, ReferenceConstraintViolation - */ - private function populateUploadedFileRecursive( - array &$files, - mixed $names, - mixed $tempNames, - mixed $types, - mixed $sizes, - mixed $errors - ): void { - if (is_array($names)) { - /** @var array|string $name */ - foreach ($names as $i => $name) { - $files[$i] = []; - /** @psalm-suppress MixedArrayAccess */ - $this->populateUploadedFileRecursive( - $files[$i], - $name, - $tempNames[$i], - $types[$i], - $sizes[$i], - $errors[$i], - ); - } - - return; - } - - try { - $stream = $this->streamFactory->createStreamFromFile($tempNames); - } catch (RuntimeException) { - $stream = $this->streamFactory->createStream(); - } - - $files = $this->uploadedFileFactory->createUploadedFile( - $stream, - (int) $sizes, - (int) $errors, - $names, - $types - ); - } -} diff --git a/tests/Acceptance.suite.yml b/tests/Acceptance.suite.yml index db58a4a..5dcf7d6 100644 --- a/tests/Acceptance.suite.yml +++ b/tests/Acceptance.suite.yml @@ -2,10 +2,10 @@ actor: AcceptanceTester extensions: enabled: - Codeception\Extension\RunProcess: - 0: php -d variables_order=EGPCS -S 127.0.0.1:8881 -t public + 0: php -d variables_order=EGPCS -S 127.0.0.1:8080 public/index-test.php -t public sleep: 1 modules: enabled: - PhpBrowser: - url: http://127.0.0.1:8881 + url: http://127.0.0.1:8080 step_decorators: ~