diff --git a/.github/workflows/cs-tests.yml b/.github/workflows/cs-tests.yml
new file mode 100644
index 0000000..3da9965
--- /dev/null
+++ b/.github/workflows/cs-tests.yml
@@ -0,0 +1,46 @@
+on:
+ - push
+
+name: Run phpcs checks
+
+jobs:
+ mutation:
+ name: PHP ${{ matrix.php }}-${{ matrix.os }}
+
+ runs-on: ${{ matrix.os }}
+
+ strategy:
+ matrix:
+ os:
+ - ubuntu-latest
+
+ php:
+ - "8.1"
+ - "8.2"
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+
+ - name: Install PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: "${{ matrix.php }}"
+ tools: composer:v2, cs2pr
+ coverage: none
+
+ - name: Determine composer cache directory
+ run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV
+
+ - name: Cache dependencies installed with composer
+ uses: actions/cache@v3
+ with:
+ path: ${{ env.COMPOSER_CACHE_DIR }}
+ key: php${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }}
+ restore-keys: |
+ php${{ matrix.php }}-composer-
+ - name: Install dependencies with composer
+ run: composer update --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi
+
+ - name: Run phpcs checks
+ run: vendor/bin/phpcs
diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml
new file mode 100644
index 0000000..74550fc
--- /dev/null
+++ b/.github/workflows/static-analysis.yml
@@ -0,0 +1,46 @@
+on:
+ - push
+
+name: Run static analysis
+
+jobs:
+ mutation:
+ name: PHP ${{ matrix.php }}-${{ matrix.os }}
+
+ runs-on: ${{ matrix.os }}
+
+ strategy:
+ matrix:
+ os:
+ - ubuntu-latest
+
+ php:
+ - "8.1"
+ - "8.2"
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+
+ - name: Install PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: "${{ matrix.php }}"
+ tools: composer:v2, cs2pr
+ coverage: none
+
+ - name: Determine composer cache directory
+ run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV
+
+ - name: Cache dependencies installed with composer
+ uses: actions/cache@v3
+ with:
+ path: ${{ env.COMPOSER_CACHE_DIR }}
+ key: php${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }}
+ restore-keys: |
+ php${{ matrix.php }}-composer-
+ - name: Install dependencies with composer
+ run: composer update --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi
+
+ - name: Run static analysis
+ run: vendor/bin/psalm --no-cache --output-format=github --show-info=false --threads=4
diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml
new file mode 100644
index 0000000..d2ab8e7
--- /dev/null
+++ b/.github/workflows/unit-tests.yml
@@ -0,0 +1,47 @@
+on:
+ - push
+
+name: Run PHPUnit tests
+
+jobs:
+ mutation:
+ name: PHP ${{ matrix.php }}-${{ matrix.os }}
+
+ runs-on: ${{ matrix.os }}
+
+ strategy:
+ matrix:
+ os:
+ - ubuntu-latest
+
+ php:
+ - "8.1"
+ - "8.2"
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+
+ - name: Install PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: "${{ matrix.php }}"
+ tools: composer:v2, cs2pr
+ coverage: none
+
+ - name: Determine composer cache directory
+ run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV
+
+ - name: Cache dependencies installed with composer
+ uses: actions/cache@v3
+ with:
+ path: ${{ env.COMPOSER_CACHE_DIR }}
+ key: php${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }}
+ restore-keys: |
+ php${{ matrix.php }}-composer-
+
+ - name: Install dependencies with composer
+ run: composer install --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi
+
+ - name: Run PHPUnit tests
+ run: vendor/bin/phpunit --colors=always
diff --git a/.gitignore b/.gitignore
index 485dee6..948af93 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,6 @@
+composer.phar
+/vendor/
+.phpcs-cache
.idea
+composer.lock
+.phpunit.result.cache
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3486233..d3870ad 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -31,4 +31,4 @@
* Nothing
### Fixed
-* Nothing
\ No newline at end of file
+* Nothing
diff --git a/README.md b/README.md
index 366be66..9adf98a 100644
--- a/README.md
+++ b/README.md
@@ -1,18 +1,23 @@
# dot-response-header

-
+
[](https://github.com/dotkernel/dot-response-header/issues)
[](https://github.com/dotkernel/dot-response-header/network)
[](https://github.com/dotkernel/dot-response-header/stargazers)
-[](https://github.com/dotkernel/response-header/LICENSE.md)
+[](https://github.com/dotkernel/dot-response-header/blob/3.0/LICENSE)
+
+[](https://github.com/dotkernel/dot-response-header/actions/workflows/static-analysis.yml)
+
+[](https://insight.symfony.com/projects/dce88959-bd29-40ef-b1e7-d12815145438)
+
Middleware for setting and overwriting custom response headers.
### Requirements
-- PHP >= 7.4
+- PHP >= 8.1
### Installation
diff --git a/composer.json b/composer.json
index 644afff..95b630a 100644
--- a/composer.json
+++ b/composer.json
@@ -19,15 +19,42 @@
"mezzio"
],
"require": {
- "php": "~7.4.0 || ~8.0.0 || ~8.1.0"
+ "php": "~8.1.0 || ~8.2.0",
+ "mezzio/mezzio-router": "^3.16",
+ "psr/http-client": "^1.0",
+ "psr/http-message": "^2.0"
},
"require-dev": {
- "phpunit/phpunit": "^9.5.20",
- "squizlabs/php_codesniffer": "^3.6.2"
+ "laminas/laminas-coding-standard": "^2.5",
+ "laminas/laminas-diactoros": "^3.0",
+ "phpunit/phpunit": "^10.2",
+ "vimeo/psalm": "^5.13"
},
"autoload": {
"psr-4": {
- "Dot\\ResponseHeader\\": "src"
+ "Dot\\ResponseHeader\\": "src/"
}
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "DotTest\\ResponseHeader\\": "test/"
+ }
+ },
+ "scripts": {
+ "check": [
+ "@cs-check",
+ "@test"
+ ],
+ "cs-check": "phpcs",
+ "cs-fix": "phpcbf",
+ "test": "phpunit --colors=always",
+ "test-coverage": "phpunit --colors=always --coverage-clover clover.xml",
+ "static-analysis": "psalm --shepherd --stats"
+ },
+ "config": {
+ "allow-plugins": {
+ "dealerdirect/phpcodesniffer-composer-installer": true
+ },
+ "sort-packages": true
}
-}
\ No newline at end of file
+}
diff --git a/config/response-header.global.php.dist b/config/response-header.global.php.dist
index 79562ab..4c7a0e1 100644
--- a/config/response-header.global.php.dist
+++ b/config/response-header.global.php.dist
@@ -18,4 +18,4 @@ return [
]
]
]
-];
\ No newline at end of file
+];
diff --git a/phpcs.xml b/phpcs.xml
new file mode 100644
index 0000000..0d4c1dc
--- /dev/null
+++ b/phpcs.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ config
+ src
+ test
+
+
+
+
diff --git a/phpunit.xml b/phpunit.xml
new file mode 100644
index 0000000..cf5ba32
--- /dev/null
+++ b/phpunit.xml
@@ -0,0 +1,14 @@
+
+
+
+
+ ./test
+
+
+
+
+
+ ./src
+
+
+
diff --git a/psalm.xml b/psalm.xml
new file mode 100644
index 0000000..30f61eb
--- /dev/null
+++ b/psalm.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/ConfigProvider.php b/src/ConfigProvider.php
index a0a9e96..aa45e00 100644
--- a/src/ConfigProvider.php
+++ b/src/ConfigProvider.php
@@ -1,20 +1,14 @@
has('config')) {
+ throw new Exception(self::MESSAGE_MISSING_CONFIG);
+ }
+ $config = $container->get('config');
+
+ if (
+ ! array_key_exists('dot_response_headers', $config)
+ || ! is_array($config['dot_response_headers'])
+ || empty($config['dot_response_headers'])
+ ) {
+ throw new Exception(self::MESSAGE_MISSING_PACKAGE_CONFIG);
+ }
+
return new ResponseHeaderMiddleware($container->get('config')['dot_response_headers'] ?? []);
}
-}
\ No newline at end of file
+}
diff --git a/src/Middleware/ResponseHeaderMiddleware.php b/src/Middleware/ResponseHeaderMiddleware.php
index 975b1d7..3f3d6ca 100644
--- a/src/Middleware/ResponseHeaderMiddleware.php
+++ b/src/Middleware/ResponseHeaderMiddleware.php
@@ -1,36 +1,28 @@
config = $config;
}
- /**
- * @param ServerRequestInterface $request
- * @param RequestHandlerInterface $handler
- * @return ResponseInterface
- */
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$response = $handler->handle($request);
@@ -38,7 +30,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
return $response;
}
- $response = $this->addHeaders($response, self::ALL_ROUTES);
+ $response = $this->addHeaders($response, self::ALL_ROUTES);
$routeResult = $request->getAttribute(RouteResult::class);
if ($routeResult instanceof RouteResult && $routeResult->isSuccess()) {
$response = $this->addHeaders($response, $routeResult->getMatchedRouteName());
@@ -46,24 +38,22 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
return $response;
}
- /**
- * @param ResponseInterface $response
- * @param string $route
- * @return ResponseInterface
- */
- private function addHeaders(ResponseInterface $response, string $route): ResponseInterface
+ public function addHeaders(ResponseInterface $response, string $route): ResponseInterface
{
if (array_key_exists($route, $this->config)) {
foreach ($this->config[$route] as $header => $data) {
if (! array_key_exists('value', $data)) {
continue;
}
- $overwrite = (isset($data['overwrite']) && $data['overwrite'] === true) ? true : false;
- if ($overwrite) {
+ $addHeader = true;
+ if ($response->hasHeader($header)) {
+ $addHeader = isset($data['overwrite']) && $data['overwrite'] === true;
+ }
+ if ($addHeader === true) {
$response = $response->withHeader($header, $data['value']);
}
}
}
return $response;
}
-}
\ No newline at end of file
+}
diff --git a/test/ConfigProviderTest.php b/test/ConfigProviderTest.php
new file mode 100644
index 0000000..23a3255
--- /dev/null
+++ b/test/ConfigProviderTest.php
@@ -0,0 +1,32 @@
+configProvider = new ConfigProvider();
+ }
+
+ public function testInvoke()
+ {
+ $data = $this->configProvider->__invoke();
+
+ $this->assertIsArray($data);
+ }
+
+ public function testGetDependencies()
+ {
+ $data = $this->configProvider->getDependencies();
+
+ $this->assertIsArray($data);
+ }
+}
diff --git a/test/Factory/ResponseHeaderMiddlewareFactoryTest.php b/test/Factory/ResponseHeaderMiddlewareFactoryTest.php
new file mode 100644
index 0000000..7995468
--- /dev/null
+++ b/test/Factory/ResponseHeaderMiddlewareFactoryTest.php
@@ -0,0 +1,60 @@
+createMock(ContainerInterface::class);
+
+ $container->expects($this->once())
+ ->method('has')
+ ->with('config')
+ ->willReturn(false);
+
+ $this->expectException(\Exception::class);
+ $this->expectExceptionMessage(ResponseHeaderMiddlewareFactory::MESSAGE_MISSING_CONFIG);
+ (new ResponseHeaderMiddlewareFactory())($container);
+ }
+
+ /**
+ * @throws Exception
+ * @throws ContainerExceptionInterface
+ * @throws NotFoundExceptionInterface
+ */
+ public function testWillNotCreateApplicationWithoutPackageConfig(): void
+ {
+ $container = $this->createMock(ContainerInterface::class);
+
+ $container->expects($this->once())
+ ->method('has')
+ ->with('config')
+ ->willReturn(true);
+
+ $container->expects($this->once())
+ ->method('get')
+ ->with('config')
+ ->willReturn([
+ 'test',
+ ]);
+
+ $this->expectException(\Exception::class);
+ $this->expectExceptionMessage(ResponseHeaderMiddlewareFactory::MESSAGE_MISSING_PACKAGE_CONFIG);
+ (new ResponseHeaderMiddlewareFactory())($container);
+ }
+}
diff --git a/test/Middleware/ResponseHeaderMiddlewareTest.php b/test/Middleware/ResponseHeaderMiddlewareTest.php
new file mode 100644
index 0000000..5e929bb
--- /dev/null
+++ b/test/Middleware/ResponseHeaderMiddlewareTest.php
@@ -0,0 +1,177 @@
+responseHeader = new ResponseHeaderMiddleware([]);
+ $this->serverRequest = $this->createMock(ServerRequestInterface::class);
+ $this->requestHandler = $this->createMock(RequestHandlerInterface::class);
+ }
+
+ public function testProcess()
+ {
+ $data = $this->responseHeader->process($this->serverRequest, $this->requestHandler);
+
+ $this->assertInstanceOf(ResponseInterface::class, $data);
+ $this->assertInstanceOf(StreamInterface::class, $data->getBody());
+ $this->assertNotEmpty($data->getBody());
+ $this->assertIsArray($data->getHeaders());
+ $this->assertIsInt($data->getStatusCode());
+ $this->assertIsString($data->getProtocolVersion());
+ $this->assertIsString($data->getReasonPhrase());
+ }
+
+ public function testWillNotAddHeadersWithoutCommonWithoutRouteSpecificHeadersConfigured(): void
+ {
+ $responseHeader = new ResponseHeaderMiddleware([]);
+
+ $response = new Response();
+ $response = $responseHeader->addHeaders($response, 'test');
+
+ $this->assertEmpty($response->getHeaders());
+ }
+
+ public function testWillAddHeadersWithCommonWithoutRouteSpecificHeadersConfigured(): void
+ {
+ $config = [
+ '*' => [
+ 'CustomHeader1' => [
+ 'value' => 'CustomHeader1-Value',
+ 'overwrite' => true,
+ ],
+ 'CustomHeader2' => [
+ 'value' => 'CustomHeader2-Value',
+ 'overwrite' => false,
+ ],
+ ],
+ ];
+ $responseHeader = new ResponseHeaderMiddleware($config);
+
+ $response = new Response();
+
+ $response = $responseHeader->addHeaders($response, 'test');
+ $this->assertEmpty($response->getHeaders());
+ $this->assertFalse($response->hasHeader('CustomHeader1'));
+ $this->assertFalse($response->hasHeader('CustomHeader2'));
+
+ $response = $responseHeader->addHeaders($response, '*');
+ $this->assertCount(2, $response->getHeaders());
+ $this->assertTrue($response->hasHeader('CustomHeader1'));
+ $this->assertTrue($response->hasHeader('CustomHeader2'));
+ $this->assertSame($config['*']['CustomHeader1']['value'], $response->getHeaderLine('CustomHeader1'));
+ $this->assertSame($config['*']['CustomHeader2']['value'], $response->getHeaderLine('CustomHeader2'));
+ }
+
+ public function testWillAddHeadersWithCommonWithRouteSpecificHeadersConfiguredWhenNoRouteMatched(): void
+ {
+ $config = [
+ '*' => [
+ 'CustomHeader1' => [
+ 'value' => 'CustomHeader1-Value',
+ 'overwrite' => true,
+ ],
+ 'CustomHeader2' => [
+ 'value' => 'CustomHeader2-Value',
+ 'overwrite' => false,
+ ],
+ ],
+ 'home' => [
+ 'CustomHeader' => [
+ 'value' => 'header3',
+ ],
+ ],
+ ];
+ $responseHeader = new ResponseHeaderMiddleware($config);
+
+ $response = new Response();
+
+ $response = $responseHeader->addHeaders($response, 'test');
+ $this->assertFalse($response->hasHeader('CustomHeader'));
+ $this->assertEmpty($response->getHeaders());
+
+ $response = $responseHeader->addHeaders($response, '*');
+ $this->assertCount(2, $response->getHeaders());
+ $this->assertTrue($response->hasHeader('CustomHeader1'));
+ $this->assertTrue($response->hasHeader('CustomHeader2'));
+ $this->assertSame($config['*']['CustomHeader1']['value'], $response->getHeaderLine('CustomHeader1'));
+ $this->assertSame($config['*']['CustomHeader2']['value'], $response->getHeaderLine('CustomHeader2'));
+ }
+
+ public function testWillAddHeadersWithCommonWithRouteSpecificHeadersConfiguredWhenRouteMatched(): void
+ {
+ $config = [
+ '*' => [
+ 'CustomHeader1' => [
+ 'value' => 'CustomHeader1-Value',
+ 'overwrite' => true,
+ ],
+ 'CustomHeader2' => [
+ 'value' => 'CustomHeader2-Value',
+ 'overwrite' => true,
+ ],
+ ],
+ 'home' => [
+ 'CustomHeader' => [
+ 'value' => 'header3',
+ ],
+ 'CustomHeader1' => [
+ 'value' => 'CustomHeader1-Overwritten-Value',
+ 'overwrite' => true,
+ ],
+ 'CustomHeader2' => [
+ 'value' => 'CustomHeader2-Overwritten-Value',
+ 'overwrite' => false,
+ ],
+ ],
+ ];
+ $responseHeader = new ResponseHeaderMiddleware($config);
+
+ $response = new Response();
+
+ $this->assertFalse($response->hasHeader('CustomHeader1'));
+ $this->assertFalse($response->hasHeader('CustomHeader2'));
+ $this->assertFalse($response->hasHeader('CustomHeader'));
+ $this->assertEmpty($response->getHeaders());
+
+ $response = $responseHeader->addHeaders($response, '*');
+ $this->assertCount(2, $response->getHeaders());
+ $this->assertTrue($response->hasHeader('CustomHeader1'));
+ $this->assertTrue($response->hasHeader('CustomHeader2'));
+ $this->assertSame($config['*']['CustomHeader1']['value'], $response->getHeaderLine('CustomHeader1'));
+ $this->assertSame($config['*']['CustomHeader2']['value'], $response->getHeaderLine('CustomHeader2'));
+
+ $response = $responseHeader->addHeaders($response, 'home');
+ $this->assertCount(3, $response->getHeaders());
+ $this->assertTrue($response->hasHeader('CustomHeader'));
+ $this->assertTrue($response->hasHeader('CustomHeader1'));
+ $this->assertTrue($response->hasHeader('CustomHeader2'));
+ $this->assertSame($config['home']['CustomHeader']['value'], $response->getHeaderLine('CustomHeader'));
+ $this->assertSame($config['home']['CustomHeader1']['value'], $response->getHeaderLine('CustomHeader1'));
+ $this->assertSame($config['*']['CustomHeader2']['value'], $response->getHeaderLine('CustomHeader2'));
+ $this->assertNotSame($config['home']['CustomHeader2']['value'], $response->getHeaderLine('CustomHeader2'));
+ }
+}