Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new class RequestHeaders #14

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from
7 changes: 5 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
# Yii Request Provider Change Log

## 1.1.1 under development

## 1.1.2 under development
- no changes in this release.

## 1.1.1 December 13, 2024

- New #12: Add `RequestHeaders` clas that provides convenient access to request headers (@uzdevid)

## 1.1.0 October 28, 2024

- New #11: Add `RequestCookies` class that provides convenient access to request cookies (@hacan359)
Expand Down
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,28 @@ class MyClass
}
```

### Request headers collection

You can work with headers as follows:

```php
class MyClass
{
public function __construct(
private \Yiisoft\RequestProvider\RequestHeaders $headers
) {}

public function go(): void
{
$this->headers->hasHeader('X-Foo');
$this->headers->getHeader('X-Foo');
$this->headers->getHeaderLine('X-Foo');
$this->headers->getHeaders();
$this->headers->getFirstHeaders('X-Foo');
}
}
```

## Documentation

- [Internals](docs/internals.md)
Expand Down
72 changes: 72 additions & 0 deletions src/RequestHeaders.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

vjik marked this conversation as resolved.
Show resolved Hide resolved
declare(strict_types=1);

namespace Yiisoft\RequestProvider;

/**
* The RequestHeaders class provides utility methods for retrieving and managing HTTP headers
* from a request. It uses a RequestProviderInterface implementation to access the current request.
*/
final class RequestHeaders {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
final class RequestHeaders {
final class RequestHeaderProvider {

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested name is not consistent with RequestCookies.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RequestCookies is inconsistent with the rest Yii3 packages as well

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. RequestHeaderProvider is better name.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a RequestHeaderCollection?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also good, but it's not a classic collection. Very custom implementation that depends on another class. It's more proxy than collection.
ProxyCollection? 😁

DikoIbragimov marked this conversation as resolved.
Show resolved Hide resolved
/**
* Initializes the RequestHeaders instance with a request provider.
*
* @param RequestProviderInterface $requestProvider The request provider to access the request.
*/
public function __construct(
private readonly RequestProviderInterface $requestProvider
) {
}

/**
* Retrieves the value of a specific header as a string. If the header does not exist, returns null.
*
* @param string $name The name of the header to retrieve.
* @return string|null The header value as a string, or null if the header is not present.
*/
public function getHeaderLine(string $name, string|null $default = null): string|null {
$request = $this->requestProvider->get();
return $request->hasHeader($name) ? $request->getHeaderLine($name) : $default;
}

/**
* Retrieves the value(s) of a specific header as an array.
*
* @param string $name The name of the header to retrieve.
* @return string[] An array of header values, or an empty array if the header is not present.
*/
public function getHeader(string $name): array {
return $this->requestProvider->get()->getHeader($name);
}

/**
* Retrieves all headers as an associative array where the key is the header name
* and the value is an array of its values.
*
* @return string[][] An associative array of all headers.
*/
public function getHeaders(): array {
return $this->requestProvider->get()->getHeaders();
}

/**
* Retrieves the first value of each header as an associative array where the key is the header name
* and the value is the first header value.
*
* @return string[] An associative array of the first values of all headers.
*/
public function getFirstHeaders(): array {
return array_map(static fn(array $lines) => $lines[0], $this->getHeaders());
}

/**
* Checks if a specific header is present in the request.
*
* @param string $name The name of the header to check.
* @return bool True if the header is present, false otherwise.
*/
public function hasHeader(string $name): bool {
return $this->requestProvider->get()->hasHeader($name);
}
}
98 changes: 98 additions & 0 deletions tests/RequestHeadersTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?php

declare(strict_types=1);

namespace Yiisoft\RequestProvider\Tests;

use PHPUnit\Framework\MockObject\Exception;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ServerRequestInterface;
use Yiisoft\RequestProvider\RequestHeaders;
use Yiisoft\RequestProvider\RequestProviderInterface;

final class RequestHeadersTest extends TestCase {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
final class RequestHeadersTest extends TestCase {
final class RequestHeaderProviderTest extends TestCase {

private const HEADER_NAME = 'test';
private const HEADER_VALUE = 'value';

/**
* @return void
* @throws Exception
*/
public function testGetHeaderLine(): void {
$requestHeaders = $this->createRequestHeaders([self::HEADER_NAME => [self::HEADER_VALUE]]);

$this->assertSame(self::HEADER_VALUE, $requestHeaders->getHeaderLine(self::HEADER_NAME));
}

/**
* @return void
* @throws Exception
*/
public function testGetHeader(): void {
$requestHeaders = $this->createRequestHeaders([self::HEADER_NAME => [self::HEADER_VALUE]]);

$this->assertSame([self::HEADER_VALUE], $requestHeaders->getHeader(self::HEADER_NAME));
}

/**
* @return void
* @throws Exception
*/
public function testGetHeaders(): void {
$requestHeaders = $this->createRequestHeaders([self::HEADER_NAME => [self::HEADER_VALUE]]);

$this->assertSame([self::HEADER_NAME => [self::HEADER_VALUE]], $requestHeaders->getHeaders());
}

/**
* @return void
* @throws Exception
*/
public function testGetFirstHeader(): void {
$requestHeaders = $this->createRequestHeaders([self::HEADER_NAME => [self::HEADER_VALUE]]);

$this->assertSame([self::HEADER_NAME => self::HEADER_VALUE], $requestHeaders->getFirstHeaders());
}

/**
* @return void
* @throws Exception
*/
public function testHasHeader(): void {
$requestHeaders = $this->createRequestHeaders([self::HEADER_NAME => [self::HEADER_VALUE]]);

$this->assertTrue($requestHeaders->hasHeader(self::HEADER_NAME));
$this->assertFalse($requestHeaders->hasHeader('non-exist'));
}

/**
* @param array $headers
* @return RequestHeaders
* @throws Exception
*/
private function createRequestHeaders(array $headers = []): RequestHeaders {
/** @var ServerRequestInterface $serverRequestMock */
$serverRequestMock = $this->createMock(ServerRequestInterface::class);
$serverRequestMock
->method('getHeaderLine')
->willReturnCallback(fn(string $name): string => $headers[$name][0] ?? null);

$serverRequestMock
->method('getHeader')
->willReturnCallback(fn(string $name): array => $headers[$name] ?? []);

$serverRequestMock
->method('getHeaders')
->willReturnCallback(fn(): array => $headers);

$serverRequestMock
->method('hasHeader')
->willReturnCallback(fn(string $name) => array_key_exists($name, $headers));

/** @var RequestProviderInterface $requestProvider */
$requestProvider = $this->createMock(RequestProviderInterface::class);
$requestProvider->method('get')->willReturn($serverRequestMock);

return new RequestHeaders($requestProvider);
}
}
Loading