diff --git a/.gitignore b/.gitignore index d2e6e56..6830e4e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /vendor/ /tools/ /bin/ +/tmp/ /.phpunit.cache/ composer.lock diff --git a/.phive/phars.xml b/.phive/phars.xml index f3f8d24..e54880b 100644 --- a/.phive/phars.xml +++ b/.phive/phars.xml @@ -1,4 +1,7 @@ - + + + + diff --git a/README.md b/README.md index 5e0f262..0602f46 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,29 @@ This is a Symfony bridge for the framework agnostic [Correlation ID library](htt * [The value of a correlation ID](https://blog.rapid7.com/2016/12/23/the-value-of-correlation-ids/) * [Identity Correlation on Wikipedia](https://en.wikipedia.org/wiki/Identity_correlation) +## Installation + +```sh +composer require phauthentic/correlation-id-symfony-bundle +``` + +## Configuration + +You can configure three different settings to control the behavior: + +* `response_header_name` - The name of the response header for the ID. +* `request_header_name` - The name of the request header for the ID. +* `pass_through` - If the ID from the request should be passed to the response enable this. This is useful if you are dealing with a microservice that is not exposed to the public but gets further actions delegated from the entry point and must retain the original ID. + +`config/correlation_id.yaml`: + +```yaml +correlation_id: + response_header_name: 'X-Correlation-ID' + request_header_name: 'X-Correlation-ID' + pass_through: false +``` + ## Copyright & License Licensed under the [MIT license](LICENSE). diff --git a/composer.lock b/composer.lock index b1ce428..9b97a2e 100644 --- a/composer.lock +++ b/composer.lock @@ -12,12 +12,12 @@ "source": { "type": "git", "url": "https://github.com/Phauthentic/correlation-id.git", - "reference": "a7ee97ae0c166407df5e0ca3ca8326c3702ae7f4" + "reference": "9e0e298bd34665e05ff5ac9505379d67e6d45ef9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Phauthentic/correlation-id/zipball/a7ee97ae0c166407df5e0ca3ca8326c3702ae7f4", - "reference": "a7ee97ae0c166407df5e0ca3ca8326c3702ae7f4", + "url": "https://api.github.com/repos/Phauthentic/correlation-id/zipball/9e0e298bd34665e05ff5ac9505379d67e6d45ef9", + "reference": "9e0e298bd34665e05ff5ac9505379d67e6d45ef9", "shasum": "" }, "require": { @@ -54,7 +54,7 @@ "issues": "https://github.com/Phauthentic/correlation-id/issues", "source": "https://github.com/Phauthentic/correlation-id/tree/2.0.0" }, - "time": "2024-03-27T23:29:11+00:00" + "time": "2024-03-27T23:51:34+00:00" }, { "name": "psr/event-dispatcher", @@ -1380,16 +1380,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.65", + "version": "1.10.66", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "3c657d057a0b7ecae19cb12db446bbc99d8839c6" + "reference": "94779c987e4ebd620025d9e5fdd23323903950bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/3c657d057a0b7ecae19cb12db446bbc99d8839c6", - "reference": "3c657d057a0b7ecae19cb12db446bbc99d8839c6", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/94779c987e4ebd620025d9e5fdd23323903950bd", + "reference": "94779c987e4ebd620025d9e5fdd23323903950bd", "shasum": "" }, "require": { @@ -1438,7 +1438,7 @@ "type": "tidelift" } ], - "time": "2024-03-23T10:30:26+00:00" + "time": "2024-03-28T16:17:31+00:00" }, { "name": "phpunit/php-code-coverage", @@ -1763,16 +1763,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.15", + "version": "10.5.16", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "86376e05e8745ed81d88232ff92fee868247b07b" + "reference": "18f8d4a5f52b61fdd9370aaae3167daa0eeb69cd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/86376e05e8745ed81d88232ff92fee868247b07b", - "reference": "86376e05e8745ed81d88232ff92fee868247b07b", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/18f8d4a5f52b61fdd9370aaae3167daa0eeb69cd", + "reference": "18f8d4a5f52b61fdd9370aaae3167daa0eeb69cd", "shasum": "" }, "require": { @@ -1844,7 +1844,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.15" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.16" }, "funding": [ { @@ -1860,7 +1860,7 @@ "type": "tidelift" } ], - "time": "2024-03-22T04:17:47+00:00" + "time": "2024-03-28T10:08:10+00:00" }, { "name": "psr/container", diff --git a/config/correlation_id.yaml b/config/correlation_id.yaml new file mode 100644 index 0000000..0212eec --- /dev/null +++ b/config/correlation_id.yaml @@ -0,0 +1,3 @@ +correlation_id: + response_header_name: 'X-Correlation-ID' + request_header_name: 'X-Correlation-ID' diff --git a/config/services.yaml b/config/services.yaml index 7bf8b83..a04773a 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -1,4 +1,9 @@ services: Phauthentic\CorrelationIdBundle\EventSubscriber\CorrelationIdSubscriber: + arguments: + $config: + response_header_name: '%correlation_id.response_header_name%' + request_header_name: '%correlation_id.request_header_name%' + pass_through: '%correlation_id.pass_through%' tags: - { name: 'kernel.event_subscriber' } diff --git a/grumphp.yml b/grumphp.yml new file mode 100644 index 0000000..751b291 --- /dev/null +++ b/grumphp.yml @@ -0,0 +1,45 @@ +grumphp: + ascii: + failed: resources/grumphp-grumpy.txt + succeeded: resources/grumphp-happy.txt + fixer: + enabled: true + fix_by_default: true + tasks: + phpstan: + autoload_file: ~ + configuration: phpstan.neon + level: null + force_patterns: [] + ignore_patterns: [] + triggered_by: ['php'] + memory_limit: "-1" + use_grumphp_paths: true + phpmd: + whitelist_patterns: [] + exclude: [] + report_format: text + ruleset: ['cleancode', 'codesize', 'naming', 'design', 'unusedcode'] + triggered_by: ['php'] + phpcs: + standard: [] + severity: ~ + error_severity: ~ + warning_severity: ~ + tab_width: ~ + report: full + report_width: ~ + whitelist_patterns: [] + encoding: ~ + ignore_patterns: [] + sniffs: [] + triggered_by: [php] + exclude: [] + show_sniffs_error_path: true + phpunit: + config_file: phpunit.xml.dist + testsuite: ~ + group: [] + exclude_group: [] + always_execute: false + order: null diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 16739ac..95fce90 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,22 +1,22 @@ - - - - - - - - - - - tests - - - - - src - - + + + + + + + + + + + tests + + + + + src + + diff --git a/resources/grumphp-grumpy.txt b/resources/grumphp-grumpy.txt new file mode 100644 index 0000000..e69de29 diff --git a/resources/grumphp-happy.txt b/resources/grumphp-happy.txt new file mode 100644 index 0000000..b2a6350 --- /dev/null +++ b/resources/grumphp-happy.txt @@ -0,0 +1,2 @@ + +All good! diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php new file mode 100644 index 0000000..5045b70 --- /dev/null +++ b/src/DependencyInjection/Configuration.php @@ -0,0 +1,32 @@ +getRootNode() + ->children() + ->booleanNode('pass_through') + ->defaultValue(false) + ->end() + ->scalarNode('response_header_name') + ->defaultValue('X-Correlation-ID') + ->end() + ->scalarNode('request_header_name') + ->defaultValue('X-Correlation-ID') + ->end() + ->end(); + + return $treeBuilder; + } +} diff --git a/src/DependencyInjection/CorrelationIdExtension.php b/src/DependencyInjection/CorrelationIdExtension.php index 0a39ed2..0b40421 100644 --- a/src/DependencyInjection/CorrelationIdExtension.php +++ b/src/DependencyInjection/CorrelationIdExtension.php @@ -18,7 +18,18 @@ class CorrelationIdExtension extends Extension { public function load(array $configs, ContainerBuilder $container): void { - $yamlLoader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../../config')); + $configuration = new Configuration(); + $config = $this->processConfiguration($configuration, $configs); + + $container->setParameter('correlation_id.request_header_name', $config['response_header_name']); + $container->setParameter('correlation_id.response_header_name', $config['response_header_name']); + $container->setParameter('correlation_id.pass_through', $config['pass_through']); + + $yamlLoader = new Loader\YamlFileLoader( + $container, + new FileLocator(__DIR__ . '/../../config') + ); + $yamlLoader->load('services.yaml'); } } diff --git a/src/EventSubscriber/CorrelationIdSubscriber.php b/src/EventSubscriber/CorrelationIdSubscriber.php index ac1f96e..1e33fa7 100644 --- a/src/EventSubscriber/CorrelationIdSubscriber.php +++ b/src/EventSubscriber/CorrelationIdSubscriber.php @@ -4,6 +4,8 @@ namespace Phauthentic\CorrelationIdBundle\EventSubscriber; +use InvalidArgumentException; +use Exception; use Phauthentic\Infrastructure\Utils\CorrelationID; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\RequestEvent; @@ -12,25 +14,63 @@ class CorrelationIdSubscriber implements EventSubscriberInterface { - private string $header = 'X-Correlation-ID'; + private string $requestHeaderName = 'X-Correlation-ID'; + private string $responseHeaderName = 'X-Correlation-ID'; + private bool $passthrough = false; + /** + * @param array $config + * @return void + */ + public function __construct(array $config = []) + { + if (isset($config['request_header_name'])) { + $this->requestHeaderName = (string)$config['request_header_name']; + } + + if (isset($config['response_header_name'])) { + $this->responseHeaderName = (string)$config['response_header_name']; + } + + if (isset($config['pass_through'])) { + $this->passthrough = (bool)$config['pass_through']; + } + } + + /** + * @SuppressWarnings(PHPMD.StaticAccess) + * + * @param RequestEvent $event + * @return void + */ public function onKernelRequest(RequestEvent $event): void { - $event->getRequest()->attributes->set($this->header, CorrelationID::toString()); + $event->getRequest()->attributes->set($this->requestHeaderName, CorrelationID::toString()); } + /** + * @SuppressWarnings(PHPMD.StaticAccess) + * + * @param ResponseEvent $event + * @return void + * @throws InvalidArgumentException + * @throws Exception + */ public function onKernelResponse(ResponseEvent $event): void { - if ($event->getRequest()->headers->has($this->header)) { + if ( + $this->passthrough + && $event->getRequest()->headers->has($this->requestHeaderName) + ) { $event->getResponse()->headers->set( - $this->header, - $event->getRequest()->headers->get($this->header) + $this->responseHeaderName, + $event->getRequest()->headers->get($this->requestHeaderName) ); return; } - $event->getResponse()->headers->set($this->header, CorrelationID::toString()); + $event->getResponse()->headers->set($this->responseHeaderName, CorrelationID::toString()); } public static function getSubscribedEvents(): array diff --git a/src/Resources/config/correlation_id.yaml b/src/Resources/config/correlation_id.yaml deleted file mode 100644 index 7bf8b83..0000000 --- a/src/Resources/config/correlation_id.yaml +++ /dev/null @@ -1,4 +0,0 @@ -services: - Phauthentic\CorrelationIdBundle\EventSubscriber\CorrelationIdSubscriber: - tags: - - { name: 'kernel.event_subscriber' } diff --git a/tests/EventSubscriber/CorrelationIdSubscriberTest.php b/tests/EventSubscriber/CorrelationIdSubscriberTest.php index b15b344..7d51d4f 100644 --- a/tests/EventSubscriber/CorrelationIdSubscriberTest.php +++ b/tests/EventSubscriber/CorrelationIdSubscriberTest.php @@ -18,6 +18,9 @@ class CorrelationIdSubscriberTest extends TestCase { private const CORRELATION_ID = 'd8d089ec-72c8-44c1-a0bf-1906e5fc3524'; + /** + * @SuppressWarnings(PHPMD.StaticAccess) + */ public function setUp(): void { $reflection = new ReflectionClass(CorrelationID::class); @@ -26,6 +29,26 @@ public function setUp(): void parent::setUp(); } + public function testOnKernelRequestConfigWithOtherHeader(): void + { + $request = new Request(); + $request->headers->set('X-Correlation-ID', self::CORRELATION_ID); + + $requestEvent = new RequestEvent( + $this->createMock(\Symfony\Component\HttpKernel\HttpKernelInterface::class), + $request, + null + ); + + $subscriber = new CorrelationIdSubscriber([ + 'request_header_name' => 'CID-IN', + ]); + $subscriber->onKernelRequest($requestEvent); + + $this->assertNotEmpty($request->attributes->get('CID-IN')); + $this->assertSame(self::CORRELATION_ID, $request->attributes->get('CID-IN')); + } + public function testOnKernelRequest(): void { $request = new Request(); @@ -61,6 +84,28 @@ public function testOnKernelResponse(): void $this->assertNotEmpty($response->headers->get('X-Correlation-ID')); } + public function testOnKernelResponseConfigWithOtherHeader(): void + { + $response = new Response(); + + $responseEvent = new ResponseEvent( + $this->createMock(\Symfony\Component\HttpKernel\HttpKernelInterface::class), + new Request(), + 1, + $response + ); + + $subscriber = new CorrelationIdSubscriber([ + 'response_header_name' => 'CID-OUT', + ]); + $subscriber->onKernelResponse($responseEvent); + + $this->assertNotEmpty($response->headers->get('CID-OUT')); + } + + /** + * @SuppressWarnings(PHPMD.StaticAccess) + */ public function testGetSubscribedEvents(): void { $subscribedEvents = CorrelationIdSubscriber::getSubscribedEvents();