diff --git a/bootstrap.php b/bootstrap.php new file mode 100644 index 0000000..7b959b1 --- /dev/null +++ b/bootstrap.php @@ -0,0 +1,3 @@ +=7.3 | ~8", "symfony/dependency-injection": "^4.0 || ^5.0", @@ -46,9 +43,17 @@ "symfony/dotenv": "^4.0 || ^5.0", "symfony/expression-language": "4.0 || ^5.0", "twig/twig": "~1.0", - "proklung/base-exception": "^1.0" + "proklung/base-exception": "^1.0", + "symfony/psr-http-message-bridge": "^2.1", + "nyholm/psr7": "^1.4", + "guzzlehttp/psr7": "^1.8" }, "require-dev": { - "proklung/phpunit-testing-tools": "^1.1" + "proklung/bitrix-phpunit-testing-tools": "^1.1" + }, + "extra": { + "installer-paths": { + "vendor/sheerockoff/bitrix-ci/files/bitrix/modules/{$name}/": ["type:bitrix-module"] + } } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..b71e4e7 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,17 @@ + + + + + ./src + + + + + Tests/Cases + + + + + + + diff --git a/src/Services/AppRequest.php b/src/Services/AppRequest.php index 6a7c10c..eb31e60 100644 --- a/src/Services/AppRequest.php +++ b/src/Services/AppRequest.php @@ -11,7 +11,7 @@ class AppRequest { /** - * @var Request $request Объект Request. + * @var Request $request Объект PsrRequest. */ private $request; @@ -25,7 +25,7 @@ public function __construct() } /** - * Объект Request. + * Объект PsrRequest. * * @return Request */ diff --git a/src/Services/PSR/BitrixRequestConvertor.php b/src/Services/PSR/BitrixRequestConvertor.php new file mode 100644 index 0000000..9083c55 --- /dev/null +++ b/src/Services/PSR/BitrixRequestConvertor.php @@ -0,0 +1,50 @@ +bitrixRequest = $bitrixRequest; + $this->psrRequest = new ServerPsrRequest($this->bitrixRequest); + } + + /** + * Request. + * + * @return Request + */ + public function request() : Request + { + $httpFoundationFactory = new HttpFoundationFactory(); + + return $httpFoundationFactory->createRequest($this->psrRequest); + } +} \ No newline at end of file diff --git a/src/Services/PSR/BitrixResponseConvertor.php b/src/Services/PSR/BitrixResponseConvertor.php new file mode 100644 index 0000000..7c6b085 --- /dev/null +++ b/src/Services/PSR/BitrixResponseConvertor.php @@ -0,0 +1,50 @@ +bitrixResponse = $bitrixResponse; + $this->psrResponse = new PsrResponse($this->bitrixResponse); + } + + /** + * Response. + * + * @return Response + */ + public function response() : Response + { + $httpFoundationFactory = new HttpFoundationFactory(); + + return $httpFoundationFactory->createResponse($this->psrResponse); + } +} \ No newline at end of file diff --git a/src/Services/PSR/PSR7/Message.php b/src/Services/PSR/PSR7/Message.php new file mode 100644 index 0000000..127cef2 --- /dev/null +++ b/src/Services/PSR/PSR7/Message.php @@ -0,0 +1,229 @@ +request = $request; + $this->httpVersion = $httpVersion; + $this->body = $body; + if (empty($this->body) && $this->needCheckBody($request)) { + $rawInput = fopen('php://input', 'r'); + $tempStream = fopen('php://temp', 'r+'); + stream_copy_to_stream($rawInput, $tempStream); + rewind($tempStream); + $this->body = Utils::streamFor($tempStream); + } + $this->uri = new Uri($this->getCurrentLink()); + $this->attributes = $attributes; + } + + /** + * @param HttpRequest $request + * + * @return boolean + */ + private function needCheckBody(HttpRequest $request) + { + $method = strtolower($request->getRequestMethod()); + return in_array($method, ['post', 'put']); + } + + private function getCurrentLink() + { + $server = $this->request->getServer(); + return ($server->get('HTTPS') === 'on' ? "https" : "http"). + "://". + $server->get('HTTP_HOST'). + $server->get('REQUEST_URI'); + } + + /** + * @return string + */ + public function getProtocolVersion() + { + if (!empty($this->httpVersion)) { + return $this->httpVersion; + } + + $version = $this->request->getServer()->get('SERVER_PROTOCOL') ?? static::DEFAULT_HTTP_VERSION; + return $this->httpVersion = str_replace(['HTTP', '/'], '', $version); + } + + + /** + * @param string $version + * @return $this|Message + */ + public function withProtocolVersion($version) + { + return new static($this->request, $version, $this->body, $this->attributes); + } + + /** + * @return array|string[][] + */ + public function getHeaders() + { + $headers = $this->request->getHeaders()->toArray(); + foreach ($headers as &$value) { + $value = (array)($value ?? []); + } + unset($value); + + return $headers; + } + + /** + * @param string $name + * @return bool + */ + public function hasHeader($name) + { + return !empty($this->getHeader($name)); + } + + public function getHeader($name) + { + return (array)($this->request->getHeader($name) ?? []); + } + + /** + * @param string $name + * @return string + */ + public function getHeaderLine($name) + { + $value = $this->getHeader($name); + if (empty($value)) { + return ''; + } + + return implode(',', (array)$value); + } + + /** + * @param string $name + * @param string|string[] $value + * @return $this|Message + */ + public function withHeader($name, $value) + { + $newRequest = $this->getClonedRequest(); + $newRequest->getHeaders()->add($name, $value); + return new static($newRequest, $this->httpVersion, $this->body, $this->attributes); + } + + /** + * @param string $name + * @param string|string[] $value + * @return $this|Message + */ + public function withAddedHeader($name, $value) + { + if ($this->hasHeader($name)) { + return $this; + } + + $newRequest = $this->getClonedRequest(); + $newRequest->getHeaders()->add($name, $value); + + return new static($newRequest, $this->httpVersion, $this->body, $this->attributes); + } + + /** + * @param string $name + * @return $this|Message + */ + public function withoutHeader($name) + { + if (!$this->hasHeader($name)) { + return $this; + } + + $newRequest = $this->getClonedRequest(); + $newRequest->getHeaders()->delete($name); + + return new static($newRequest, $this->httpVersion, $this->body, $this->attributes); + } + + /** + * @return StreamInterface + */ + public function getBody() + { + if (!$this->body) { + $this->body = Utils::streamFor(''); + } + + return $this->body; + } + + /** + * @param StreamInterface $body + * @return $this|Message + */ + public function withBody(StreamInterface $body) + { + if ($body === $this->body) { + return $this; + } + + return new static($this->request, $this->httpVersion, $body, $this->attributes); + } +} diff --git a/src/Services/PSR/PSR7/PsrRequest.php b/src/Services/PSR/PSR7/PsrRequest.php new file mode 100644 index 0000000..4630977 --- /dev/null +++ b/src/Services/PSR/PSR7/PsrRequest.php @@ -0,0 +1,85 @@ +request->getRequestUri(); + } + + /** + * @param mixed $requestTarget + * + * @return $this|PsrRequest + */ + public function withRequestTarget($requestTarget) + { + $newRequest = $this->getClonedRequest(); + $newRequest->getServer()->set('REQUEST_URI', $requestTarget); + + return new static($newRequest, $this->httpVersion, $this->body, $this->attributes); + } + + /** + * @return string|null + */ + public function getMethod() + { + return $this->request->getRequestMethod(); + } + + /** + * @param string $method + * + * @return $this|PsrRequest + */ + public function withMethod($method) + { + $newRequest = $this->getClonedRequest(); + $newRequest->getServer()->set('REQUEST_METHOD', $method); + + return new static($newRequest, $this->httpVersion, $this->body, $this->attributes); + } + + /** + * @return UriInterface + */ + public function getUri() + { + return $this->uri; + } + + /** + * @param UriInterface $uri + * @param false $preserveHost + * @return $this|PsrRequest + */ + public function withUri(UriInterface $uri, $preserveHost = false) + { + $newRequest = $this->getClonedRequest(); + $newRequest->getServer()->set('REQUEST_URI', $uri); + + return new static($newRequest, $this->httpVersion, $this->body, $this->attributes); + } + + /** + * @return HttpRequest + */ + protected function getClonedRequest() + { + return clone $this->request; + } +} diff --git a/src/Services/PSR/PSR7/PsrResponse.php b/src/Services/PSR/PSR7/PsrResponse.php new file mode 100644 index 0000000..5fa02be --- /dev/null +++ b/src/Services/PSR/PSR7/PsrResponse.php @@ -0,0 +1,213 @@ +response = $response; + $this->httpVersion = $httpVersion ?? static::DEFAULT_HTTP_VERSION; + $this->body = $body; + } + + /** + * @inheritDoc + */ + public function getProtocolVersion() + { + return $this->httpVersion; + } + + /** + * @inheritDoc + */ + public function withProtocolVersion($version) + { + return new static($this->response, $version, $this->body); + } + + /** + * @inheritDoc + */ + public function getHeaders() + { + return $this->response->getHeaders()->toArray(); + } + + /** + * @inheritDoc + */ + public function hasHeader($name) + { + return !empty($this->getHeader($name)); + } + + /** + * @inheritDoc + */ + public function getHeader($name) + { + return $this->response->getHeaders()->get($name, true); + } + + /** + * @inheritDoc + */ + public function getHeaderLine($name) + { + $value = $this->getHeader($name); + if (empty($value)) { + return ''; + } + + return implode(',', $value); + } + + /** + * @inheritDoc + */ + public function withHeader($name, $value) + { + $newResponse = clone $this->response; + $newResponse->getHeaders()->set($name, $value); + return new static($newResponse, $this->httpVersion, $this->body); + } + + /** + * @inheritDoc + */ + public function withAddedHeader($name, $value) + { + if ($this->hasHeader($name)) { + return $this; + } + + return $this->withHeader($name, $value); + } + + /** + * @inheritDoc + */ + public function withoutHeader($name) + { + if (!$this->hasHeader($name)) { + return $this; + } + + $newResponse = clone $this->response; + $newResponse->getHeaders()->delete($name); + return new static($newResponse, $this->httpVersion, $this->body); + } + + /** + * @inheritDoc + */ + public function getBody() + { + if (!$this->body) { + $this->body = Utils::streamFor($this->response->getContent()); + } + + return $this->body; + } + + /** + * @inheritDoc + * @throws ArgumentTypeException + */ + public function withBody(StreamInterface $body) + { + $newResponse = clone $this->response; + $newResponse->setContent($body); + + return new static($newResponse, $this->httpVersion, $body); + } + + /** + * @inheritDoc + */ + public function getStatusCode() + { + preg_match('/(\d+)\s+.*/', $this->response->getStatus(), $match); + return (int)($match[1] ?? 200); + } + + /** + * @inheritDoc + */ + public function withStatus($code, $reasonPhrase = '') + { + $newResponse = clone $this->response; + $newResponse->getHeaders()->set('Status', implode(' ', [$code, $reasonPhrase])); + + return new static($newResponse, $this->httpVersion, $this->body); + } + + /** + * @inheritDoc + */ + public function getReasonPhrase() + { + preg_match('/\d+\s+(.*)/', $this->response->getStatus(), $match); + return $match[1] ?? ''; + } + + /** + * @inheritDoc + */ + public function serialize() + { + return serialize([ + 'response' => $this->response, + 'http_version' => $this->httpVersion, + 'body' => (string)$this->body, + ]); + } + + /** + * @inheritDoc + */ + public function unserialize($serialized) + { + $data = unserialize($serialized); + $this->response = $data['response']; + $this->httpVersion = $data['http_version']; + $this->body = $data['body']; + } +} diff --git a/src/Services/PSR/PSR7/ServerPsrRequest.php b/src/Services/PSR/PSR7/ServerPsrRequest.php new file mode 100644 index 0000000..f11b25d --- /dev/null +++ b/src/Services/PSR/PSR7/ServerPsrRequest.php @@ -0,0 +1,187 @@ +request->getServer()->toArray(); + } + + /** + * @inheritDoc + */ + public function getCookieParams(): array + { + return $this->request->getCookieList()->toArray(); + } + + /** + * @inheritDoc + */ + public function withCookieParams(array $cookies) + { + $newRequest = $this->getClonedRequest(); + $newRequest->getCookieList()->setValues($cookies); + + return new static($newRequest, $this->httpVersion, $this->body, $this->attributes); + } + + /** + * @inheritDoc + */ + public function getQueryParams(): array + { + return $this->request->getQueryList()->toArray(); + } + + /** + * @inheritDoc + */ + public function withQueryParams(array $query): ServerPsrRequest + { + $newRequest = $this->getClonedRequest(); + $newRequest->getQueryList()->setValues($query); + + return new static($newRequest, $this->httpVersion, $this->body, $this->attributes); + } + + /** + * @inheritDoc + */ + public function getUploadedFiles() + { + return array_map(function (array $file) { + if (is_array($file['tmp_name'])) { + $result = []; + for ($i = 0; $i < count($file['tmp_name']); $i++) { + $result[$i] = new UploadedFile( + $file['tmp_name'][$i], + (int)$file['size'][$i], + (int)$file['error'][$i], + $file['name'][$i], + $file['type'][$i] + ); + } + + return $result; + } + return new UploadedFile( + $file['tmp_name'], + (int) $file['size'], + (int) $file['error'], + $file['name'], + $file['type'] + ); + }, $this->request->getFileList()->toArray()); + } + + /** + * @return array + */ + private function getFileList(): array + { + $fileList = []; + foreach ($this->request->getFileList() as $key => $file) { + foreach ($file as $k => $value) { + if (is_array($value)) { + foreach ($value as $i => $v) { + $fileList[$key][$i][$k] = $v; + } + } else { + $fileList[$key][$k] = $v; + } + } + } + + return $fileList; + } + + /** + * @inheritDoc + */ + public function withUploadedFiles(array $uploadedFiles) + { + $newRequest = $this->getClonedRequest(); + $newRequest->getFileList()->setValues($uploadedFiles); + + return new static($newRequest, $this->httpVersion, $this->body, $this->attributes); + } + + /** + * @inheritDoc + */ + public function getParsedBody() + { + return $this->request->getPostList()->toArray(); + } + + /** + * @inheritDoc + */ + public function withParsedBody($data) + { + $newRequest = $this->getClonedRequest(); + $newRequest->getPostList()->setValues($data); + + return new static($newRequest, $this->httpVersion, $this->body, $this->attributes); + } + + /** + * @inheritDoc + */ + public function getAttributes(): array + { + return $this->attributes; + } + + /** + * @inheritDoc + */ + public function getAttribute($attribute, $default = null) + { + if (false === array_key_exists($attribute, $this->attributes)) { + return $default; + } + + return $this->attributes[$attribute]; + } + + /** + * @inheritDoc + */ + public function withAttribute($attribute, $value): ServerRequestInterface + { + $new = clone $this; + $new->attributes[$attribute] = $value; + + return $new; + } + + /** + * @inheritDoc + */ + public function withoutAttribute($attribute): ServerRequestInterface + { + if (false === array_key_exists($attribute, $this->attributes)) { + return $this; + } + + $new = clone $this; + unset($new->attributes[$attribute]); + + return $new; + } +} diff --git a/tests/Cases/BitrixRequestConvertorTest.php b/tests/Cases/BitrixRequestConvertorTest.php new file mode 100644 index 0000000..c079383 --- /dev/null +++ b/tests/Cases/BitrixRequestConvertorTest.php @@ -0,0 +1,75 @@ +bitrixRequest = $this->getBitrixRequest(); + $this->obTestObject = new BitrixRequestConvertor($this->bitrixRequest); + } + + /** + * @return void + */ + public function testRequest() : void + { + $result = $this->obTestObject->request(); + + $query = $result->query->all(); + + $this->assertNotEmpty($query['clear_cache']); + $this->assertNotEmpty($query['query']); + $this->assertSame('123', $query['query']); + + $server = $result->server->all(); + + $this->assertNotEmpty($server['HTTP_HOST']); + $this->assertSame('bitrix-example2.loc', $server['HTTP_HOST']); + + $this->assertNotEmpty($server['HTTP_X_REAL_IP']); + $this->assertSame('127.0.0.1', $server['HTTP_X_REAL_IP']); + + $this->assertNotEmpty($server['HTTP_COOKIE']); + + $cookies = $result->cookies->all(); + $this->assertNotEmpty($cookies); + } + + /** + * Request из фикстуры. + * + * @return HttpRequest + */ + private function getBitrixRequest() : HttpRequest + { + $fixture = file_get_contents(__DIR__ . '/fixtures/request.json'); + + return unserialize($fixture); + } +} \ No newline at end of file diff --git a/tests/Cases/BitrixResponseConvertorTest.php b/tests/Cases/BitrixResponseConvertorTest.php new file mode 100644 index 0000000..7d3dafd --- /dev/null +++ b/tests/Cases/BitrixResponseConvertorTest.php @@ -0,0 +1,61 @@ +bitrixResponse = $this->getBitrixResponse(); + $this->obTestObject = new BitrixResponseConvertor($this->bitrixResponse); + } + + /** + * @return void + */ + public function testResponse() : void + { + $result = $this->obTestObject->response(); + + $headers = $result->headers->all(); + + $this->assertNotEmpty($headers['cache-control']); + $this->assertNotEmpty($headers['date']); + + $this->assertSame(200, $result->getStatusCode()); + } + + /** + * Response из фикстуры. + * + * @return HttpResponse + */ + private function getBitrixResponse() : HttpResponse + { + $fixture = file_get_contents(__DIR__ . '/fixtures/response.json'); + + return unserialize($fixture); + } +} \ No newline at end of file diff --git a/tests/Cases/fixtures/request.json b/tests/Cases/fixtures/request.json new file mode 100644 index 0000000..a765a69 Binary files /dev/null and b/tests/Cases/fixtures/request.json differ diff --git a/tests/Cases/fixtures/response.json b/tests/Cases/fixtures/response.json new file mode 100644 index 0000000..6e6e8d9 Binary files /dev/null and b/tests/Cases/fixtures/response.json differ