├── .github ├── dependabot.yml ├── stale.yml └── workflows │ └── ci.yml ├── .github_changelog_generator ├── .gitignore ├── .php-cs-fixer.php ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── composer.json ├── phpstan-baseline.neon ├── phpstan.neon ├── phpunit.xml.dist ├── src └── Qandidate │ └── Stack │ ├── RequestId.php │ ├── RequestId │ └── MonologProcessor.php │ ├── RequestIdGenerator.php │ └── UuidRequestIdGenerator.php └── test └── Qandidate └── Stack ├── RequestId └── MonologProcessorTest.php ├── RequestIdTest.php └── UuidRequestIdGeneratorTest.php /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: composer 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | - package-ecosystem: github-actions 9 | directory: "/" 10 | schedule: 11 | interval: weekly 12 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # see https://probot.github.io/apps/stale/ 2 | 3 | # inherit settings from https://github.com/qandidate-labs/.github/blob/main/.github/stale.yml 4 | _extends: .github 5 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: "CI" 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - "master" 7 | push: 8 | branches: 9 | - "master" 10 | schedule: 11 | - cron: "37 13 * * 1" 12 | 13 | jobs: 14 | tests: 15 | name: "Run tests" 16 | runs-on: "ubuntu-20.04" 17 | strategy: 18 | matrix: 19 | php-version: 20 | - "7.4" 21 | - "8.0" 22 | - "8.1" 23 | - "8.2" 24 | steps: 25 | - name: "Checkout" 26 | uses: "actions/checkout@v4" 27 | - name: "Install PHP" 28 | uses: "shivammathur/setup-php@v2" 29 | with: 30 | php-version: "${{ matrix.php-version }}" 31 | coverage: "none" 32 | env: 33 | fail-fast: true 34 | - name: "Validate composer.json and composer.lock" 35 | run: "composer validate --strict --no-interaction --ansi" 36 | - name: "Install dependencies with Composer" 37 | uses: "ramsey/composer-install@v3" 38 | - name: "Run tests" 39 | run: "make test" 40 | 41 | coding-standards: 42 | name: "Coding standards" 43 | runs-on: "ubuntu-20.04" 44 | steps: 45 | - name: "Checkout" 46 | uses: "actions/checkout@v4" 47 | - name: "Install PHP" 48 | uses: "shivammathur/setup-php@v2" 49 | with: 50 | php-version: "8.0" 51 | coverage: "none" 52 | - name: "Install dependencies with Composer" 53 | uses: "ramsey/composer-install@v3" 54 | - name: "Check coding standards" 55 | run: "make php-cs-fixer-ci" 56 | 57 | static-analysis: 58 | name: "Static analysis" 59 | runs-on: "ubuntu-20.04" 60 | steps: 61 | - name: "Checkout" 62 | uses: "actions/checkout@v4" 63 | - name: "Install PHP" 64 | uses: "shivammathur/setup-php@v2" 65 | with: 66 | php-version: "8.0" 67 | coverage: "none" 68 | - name: "Install dependencies with Composer" 69 | uses: "ramsey/composer-install@v3" 70 | - name: "Run PHPStan" 71 | run: "make phpstan" 72 | -------------------------------------------------------------------------------- /.github_changelog_generator: -------------------------------------------------------------------------------- 1 | future-release=2.0.2 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | composer.lock 3 | .php-cs-fixer.cache 4 | .phpunit.result.cache 5 | -------------------------------------------------------------------------------- /.php-cs-fixer.php: -------------------------------------------------------------------------------- 1 | setFinder( 6 | \PhpCsFixer\Finder::create() 7 | ->in([ 8 | __DIR__ . '/src', 9 | __DIR__ . '/test', 10 | ]) 11 | ); 12 | 13 | return $config; 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [2.0.2](https://github.com/qandidate-labs/stack-request-id/tree/2.0.2) (2020-05-07) 4 | 5 | [Full Changelog](https://github.com/qandidate-labs/stack-request-id/compare/2.0.1...2.0.2) 6 | 7 | **Merged pull requests:** 8 | 9 | - Drop support for Symfony 3.4 [\#30](https://github.com/qandidate-labs/stack-request-id/pull/30) ([rgeraads](https://github.com/rgeraads)) 10 | 11 | ## [2.0.1](https://github.com/qandidate-labs/stack-request-id/tree/2.0.1) (2020-04-24) 12 | 13 | [Full Changelog](https://github.com/qandidate-labs/stack-request-id/compare/2.0.0...2.0.1) 14 | 15 | **Closed issues:** 16 | 17 | - Support Symfony 5? [\#17](https://github.com/qandidate-labs/stack-request-id/issues/17) 18 | 19 | **Merged pull requests:** 20 | 21 | - Allow ramsey/uuid 4.0 [\#29](https://github.com/qandidate-labs/stack-request-id/pull/29) ([rgeraads](https://github.com/rgeraads)) 22 | - Symfony \>=5.0.7 [\#28](https://github.com/qandidate-labs/stack-request-id/pull/28) ([othillo](https://github.com/othillo)) 23 | - Symfony \>=5.0.7 [\#27](https://github.com/qandidate-labs/stack-request-id/pull/27) ([othillo](https://github.com/othillo)) 24 | 25 | ## [2.0.0](https://github.com/qandidate-labs/stack-request-id/tree/2.0.0) (2020-02-11) 26 | 27 | [Full Changelog](https://github.com/qandidate-labs/stack-request-id/compare/1.1.0...2.0.0) 28 | 29 | **Closed issues:** 30 | 31 | - support Symfony 4 [\#14](https://github.com/qandidate-labs/stack-request-id/issues/14) 32 | 33 | **Merged pull requests:** 34 | 35 | - only test actively supported PHP versions [\#21](https://github.com/qandidate-labs/stack-request-id/pull/21) ([othillo](https://github.com/othillo)) 36 | 37 | ## [1.1.0](https://github.com/qandidate-labs/stack-request-id/tree/1.1.0) (2017-12-11) 38 | 39 | [Full Changelog](https://github.com/qandidate-labs/stack-request-id/compare/1.0.0...1.1.0) 40 | 41 | **Closed issues:** 42 | 43 | - What's the current state of this library? [\#11](https://github.com/qandidate-labs/stack-request-id/issues/11) 44 | 45 | **Merged pull requests:** 46 | 47 | - support Symfony 4 [\#16](https://github.com/qandidate-labs/stack-request-id/pull/16) ([othillo](https://github.com/othillo)) 48 | 49 | ## [1.0.0](https://github.com/qandidate-labs/stack-request-id/tree/1.0.0) (2017-07-17) 50 | 51 | [Full Changelog](https://github.com/qandidate-labs/stack-request-id/compare/0.4.1...1.0.0) 52 | 53 | **Merged pull requests:** 54 | 55 | - Update version in README [\#13](https://github.com/qandidate-labs/stack-request-id/pull/13) ([wjzijderveld](https://github.com/wjzijderveld)) 56 | - test with newer PHP versions [\#12](https://github.com/qandidate-labs/stack-request-id/pull/12) ([othillo](https://github.com/othillo)) 57 | 58 | ## [0.4.1](https://github.com/qandidate-labs/stack-request-id/tree/0.4.1) (2016-12-06) 59 | 60 | [Full Changelog](https://github.com/qandidate-labs/stack-request-id/compare/0.4.0...0.4.1) 61 | 62 | **Closed issues:** 63 | 64 | - Release a new version [\#9](https://github.com/qandidate-labs/stack-request-id/issues/9) 65 | 66 | **Merged pull requests:** 67 | 68 | - Added support for both ramsey/uuid 2.0 and 3.0 [\#10](https://github.com/qandidate-labs/stack-request-id/pull/10) ([robinvdvleuten](https://github.com/robinvdvleuten)) 69 | 70 | ## [0.4.0](https://github.com/qandidate-labs/stack-request-id/tree/0.4.0) (2016-03-26) 71 | 72 | [Full Changelog](https://github.com/qandidate-labs/stack-request-id/compare/0.3.0...0.4.0) 73 | 74 | ## [0.3.0](https://github.com/qandidate-labs/stack-request-id/tree/0.3.0) (2015-10-19) 75 | 76 | [Full Changelog](https://github.com/qandidate-labs/stack-request-id/compare/0.2.0...0.3.0) 77 | 78 | **Closed issues:** 79 | 80 | - switch to ramsey/uuid? [\#2](https://github.com/qandidate-labs/stack-request-id/issues/2) 81 | 82 | **Merged pull requests:** 83 | 84 | - Add compatibility with symfony 3 [\#7](https://github.com/qandidate-labs/stack-request-id/pull/7) ([olivier34000](https://github.com/olivier34000)) 85 | - Enable support for StackPHP's middleware stack builder. [\#5](https://github.com/qandidate-labs/stack-request-id/pull/5) ([zanbaldwin](https://github.com/zanbaldwin)) 86 | - Update dependency to maintained uuid project [\#4](https://github.com/qandidate-labs/stack-request-id/pull/4) ([merk](https://github.com/merk)) 87 | 88 | ## [0.2.0](https://github.com/qandidate-labs/stack-request-id/tree/0.2.0) (2015-01-30) 89 | 90 | [Full Changelog](https://github.com/qandidate-labs/stack-request-id/compare/0.1.0...0.2.0) 91 | 92 | **Merged pull requests:** 93 | 94 | - Add support for sending the request id with a response. [\#1](https://github.com/qandidate-labs/stack-request-id/pull/1) ([jakzal](https://github.com/jakzal)) 95 | 96 | ## [0.1.0](https://github.com/qandidate-labs/stack-request-id/tree/0.1.0) (2014-10-28) 97 | 98 | [Full Changelog](https://github.com/qandidate-labs/stack-request-id/compare/3dd14983adb5f4592dd464a7933f13e7d5815ec3...0.1.0) 99 | 100 | 101 | 102 | \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* 103 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Qandidate.com - http://qandidate.com/ 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL:=help 2 | 3 | .PHONY: dependencies 4 | dependencies: 5 | composer install --no-interaction --no-suggest --no-scripts --ansi 6 | 7 | .PHONY: test 8 | test: 9 | vendor/bin/phpunit --testdox --exclude-group=none --colors=always 10 | 11 | .PHONY: qa 12 | qa: php-cs-fixer-ci phpstan 13 | 14 | .PHONY: php-cs-fixer 15 | php-cs-fixer: 16 | vendor/bin/php-cs-fixer fix --no-interaction --allow-risky=yes --diff --verbose 17 | 18 | .PHONY: php-cs-fixer-ci 19 | php-cs-fixer-ci: 20 | vendor/bin/php-cs-fixer fix --dry-run --no-interaction --allow-risky=yes --diff --verbose 21 | 22 | .PHONY: phpstan 23 | phpstan: 24 | vendor/bin/phpstan analyse --level=max src/ 25 | 26 | .PHONY: changelog 27 | changelog: 28 | git log $$(git describe --abbrev=0 --tags)...HEAD --no-merges --pretty=format:"* [%h](http://github.com/${TRAVIS_REPO_SLUG}/commit/%H) %s (%cN)" 29 | 30 | .PHONY: license 31 | license: 32 | vendor/bin/docheader check --no-interaction --ansi -vvv {src,test} 33 | 34 | # Based on https://suva.sh/posts/well-documented-makefiles/ 35 | help: ## Display this help 36 | @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n\nTargets:\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-20s\033[0m %s\n", $$1, $$2 }' $(MAKEFILE_LIST) 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | stack-request-id 2 | ===== 3 | Middleware for adding a request id to your Symfony Requests 4 | 5 | ![build status](https://github.com/qandidate-labs/stack-request-id/actions/workflows/ci.yml/badge.svg) 6 | 7 | ## Installation 8 | First, add this project to your project's composer.json 9 | 10 | ``` 11 | $ composer require qandidate/stack-request-id ^1.0 12 | ``` 13 | 14 | ## Setting up 15 | Update your `app.php` to include the middleware: 16 | 17 | Before: 18 | ```php5 19 | use Symfony\Component\HttpFoundation\Request; 20 | 21 | $kernel = new AppKernel($env, $debug); 22 | $kernel->loadClassCache(); 23 | 24 | $request = Request::createFromGlobals(); 25 | $response = $kernel->handle($request); 26 | $response->send(); 27 | $kernel->terminate($request, $response); 28 | ``` 29 | 30 | After: 31 | ```php5 32 | use Qandidate\Stack\RequestId; 33 | use Qandidate\Stack\UuidRequestIdGenerator; 34 | use Symfony\Component\HttpFoundation\Request; 35 | 36 | $kernel = new AppKernel($env, $debug); 37 | 38 | // Stack it! 39 | $generator = new UuidRequestIdGenerator(1337); 40 | $stack = new RequestId($kernel, $generator); 41 | 42 | $kernel->loadClassCache(); 43 | 44 | $request = Request::createFromGlobals(); 45 | $response = $stack->handle($request); 46 | $response->send(); 47 | $kernel->terminate($request, $response); 48 | ``` 49 | 50 | ## Adding the request id to your monolog logs 51 | If you use Symfony's [MonologBundle] you can add the request id to your monolog logs by adding the following service definition to your services.xml file: 52 | 53 | ```XML 54 | 55 | 56 | 57 | 58 | ``` 59 | 60 | [MonologBundle]: https://github.com/symfony/MonologBundle 61 | 62 | ## Adding the request id to responses 63 | If you need to send the request id back with the response you can enable the response header: 64 | 65 | ```php5 66 | $generator = new UuidRequestIdGenerator(1337); 67 | $stack = new RequestId($kernel, $generator); 68 | $stack->enableResponseHeader(); 69 | ``` 70 | 71 | It is also possible to change response header's name: 72 | 73 | ```php5 74 | $stack->enableResponseHeader('My-Custom-Request-Id'); 75 | ``` 76 | 77 | If you don't have access to the `RequestId` object instance (StackPHP, for example) the response header can be set via 78 | the fourth argument of the `RequestId` constructor method. 79 | 80 | ```php5 81 | $generator = new UuidRequestIdGenerator(1337); 82 | $stack = new RequestId($kernel, $generator, 'X-Request-Id', 'My-Custom-Request-Id'); 83 | ``` 84 | 85 | The third argument, for reference, is the name of the header: 86 | - That will be checked for a value before falling back to generating a new request ID, 87 | - Used to store the resulting request ID inside Symfony's request object. 88 | 89 | ## StackPHP's Middleware Builder 90 | If you are already using [StackPHP](http://stackphp.com), just push the `RequestId` class into the builder. 91 | 92 | ```php5 93 | $kernel = new AppKernel('dev', true); 94 | 95 | $generator = new UuidRequestIdGenerator(1337); 96 | $stack = (new Stack\Builder) 97 | ->push('Qandidate\Stack\RequestId', $generator, 'X-Request-Id', 'X-Request-Id') 98 | ->resolve($kernel); 99 | 100 | $kernel->loadClassCache(); 101 | 102 | $request = Request::createFromGlobals(); 103 | $response = $stack->handle($request); 104 | $response->send(); 105 | $kernel->terminate($request, $response); 106 | ``` 107 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "qandidate/stack-request-id", 3 | "description": "Middleware for adding request id to Symfony Request.", 4 | "license": "MIT", 5 | "require": { 6 | "symfony/http-kernel": "^5.1.5", 7 | "symfony/http-foundation": "^5.0.7", 8 | "ramsey/uuid": "^4.0" 9 | }, 10 | "authors": [ 11 | { 12 | "name": "Alexander", 13 | "email": "iam.asm89@gmail.com" 14 | }, 15 | { 16 | "name": "Fritsjan", 17 | "email": "fritsjan@qandidate.com" 18 | }, 19 | { 20 | "name": "Qandidate.com", 21 | "homepage": "http://labs.qandidate.com/" 22 | } 23 | ], 24 | "suggest": { 25 | "symfony/monolog-bundle": "For registering the MonologProcessor" 26 | }, 27 | "autoload": { 28 | "psr-0": { 29 | "Qandidate\\Stack": "src/" 30 | } 31 | }, 32 | "autoload-dev": { 33 | "psr-0": { 34 | "Qandidate\\Stack": "test/" 35 | } 36 | }, 37 | "require-dev": { 38 | "phpunit/phpunit": "^9.5", 39 | "broadway/coding-standard": "^1.2", 40 | "phpstan/phpstan": "^1.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /phpstan-baseline.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | ignoreErrors: 3 | - 4 | message: "#^Method Qandidate\\\\Stack\\\\RequestId\\\\MonologProcessor\\:\\:__invoke\\(\\) has parameter \\$record with no value type specified in iterable type array\\.$#" 5 | count: 1 6 | path: src/Qandidate/Stack/RequestId/MonologProcessor.php 7 | 8 | - 9 | message: "#^Method Qandidate\\\\Stack\\\\RequestId\\\\MonologProcessor\\:\\:__invoke\\(\\) return type has no value type specified in iterable type array\\.$#" 10 | count: 1 11 | path: src/Qandidate/Stack/RequestId/MonologProcessor.php 12 | 13 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | includes: 2 | - phpstan-baseline.neon 3 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | 14 | 15 | ./test/ 16 | 17 | 18 | 19 | 20 | ./src/ 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Qandidate/Stack/RequestId.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Qandidate\Stack; 15 | 16 | use Symfony\Component\HttpFoundation\Request; 17 | use Symfony\Component\HttpKernel\HttpKernelInterface; 18 | 19 | /** 20 | * Middleware adding a unique request id to the request if it is not present. 21 | */ 22 | class RequestId implements HttpKernelInterface 23 | { 24 | /** @var HttpKernelInterface */ 25 | private $app; 26 | 27 | /** @var RequestIdGenerator */ 28 | private $generator; 29 | 30 | /** @var string */ 31 | private $header; 32 | 33 | /** @var string|null */ 34 | private $responseHeader; 35 | 36 | public function __construct( 37 | HttpKernelInterface $app, 38 | RequestIdGenerator $generator, 39 | string $header = 'X-Request-Id', 40 | ?string $responseHeader = null 41 | ) { 42 | $this->app = $app; 43 | $this->generator = $generator; 44 | $this->header = $header; 45 | $this->responseHeader = $responseHeader; 46 | } 47 | 48 | /** 49 | * {@inheritDoc} 50 | */ 51 | public function handle(Request $request, int $type = HttpKernelInterface::MASTER_REQUEST, bool $catch = true) 52 | { 53 | if (!$request->headers->has($this->header)) { 54 | $request->headers->set($this->header, $this->generator->generate()); 55 | } 56 | 57 | $response = $this->app->handle($request, $type, $catch); 58 | 59 | if (null !== $this->responseHeader) { 60 | $response->headers->set($this->responseHeader, (string) $request->headers->get($this->header, '')); 61 | } 62 | 63 | return $response; 64 | } 65 | 66 | public function enableResponseHeader(string $header = 'X-Request-Id'): void 67 | { 68 | $this->responseHeader = $header; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Qandidate/Stack/RequestId/MonologProcessor.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Qandidate\Stack\RequestId; 15 | 16 | use Symfony\Component\HttpKernel\Event\RequestEvent; 17 | 18 | /** 19 | * Processor to add the request id to monolog records. 20 | */ 21 | class MonologProcessor 22 | { 23 | /** @var string */ 24 | private $header; 25 | 26 | /** @var string|null */ 27 | private $requestId; 28 | 29 | public function __construct(string $header = 'X-Request-Id') 30 | { 31 | $this->header = $header; 32 | } 33 | 34 | public function onKernelRequest(RequestEvent $event): void 35 | { 36 | $request = $event->getRequest(); 37 | $this->requestId = (string) $request->headers->get($this->header, ''); 38 | } 39 | 40 | public function __invoke(array $record): array 41 | { 42 | if ($this->requestId) { 43 | $record['extra']['request_id'] = $this->requestId; 44 | } 45 | 46 | return $record; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Qandidate/Stack/RequestIdGenerator.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Qandidate\Stack; 15 | 16 | /** 17 | * Generates request ids. 18 | */ 19 | interface RequestIdGenerator 20 | { 21 | public function generate(): string; 22 | } 23 | -------------------------------------------------------------------------------- /src/Qandidate/Stack/UuidRequestIdGenerator.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Qandidate\Stack; 15 | 16 | use Ramsey\Uuid\Type\Hexadecimal; 17 | use Ramsey\Uuid\Uuid; 18 | 19 | /** 20 | * Generates a uuid for the request id. 21 | */ 22 | class UuidRequestIdGenerator implements RequestIdGenerator 23 | { 24 | /** @var Hexadecimal|int|string|null */ 25 | private $nodeId; 26 | 27 | /** 28 | * @param Hexadecimal|int|string|null $nodeId 29 | */ 30 | public function __construct($nodeId = null) 31 | { 32 | $this->nodeId = $nodeId; 33 | } 34 | 35 | /** 36 | * {@inheritDoc} 37 | */ 38 | public function generate(): string 39 | { 40 | return Uuid::uuid1($this->nodeId)->toString(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/Qandidate/Stack/RequestId/MonologProcessorTest.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Qandidate\Stack\RequestId; 15 | 16 | use PHPUnit\Framework\TestCase; 17 | use Symfony\Component\HttpFoundation\Request; 18 | use Symfony\Component\HttpKernel\Event\RequestEvent; 19 | 20 | class MonologProcessorTest extends TestCase 21 | { 22 | private $processor; 23 | private $header = 'Foo-Id'; 24 | 25 | public function setUp(): void 26 | { 27 | $this->processor = new MonologProcessor($this->header); 28 | } 29 | 30 | /** 31 | * @test 32 | */ 33 | public function it_adds_the_request_id_if_it_was_available_in_the_request() 34 | { 35 | $record = ['message' => 'w00t w00t']; 36 | $requestId = 'ea1379-42'; 37 | $getResponseEvent = $this->createGetResponseEvent($requestId); 38 | 39 | $this->processor->onKernelRequest($getResponseEvent); 40 | 41 | $expectedRecord = $record; 42 | $expectedRecord['extra']['request_id'] = $requestId; 43 | 44 | $this->assertEquals($expectedRecord, $this->invokeProcessor($record)); 45 | } 46 | 47 | /** 48 | * @test 49 | */ 50 | public function it_leaves_the_record_untouched_if_no_request_id_was_available_in_the_request() 51 | { 52 | $record = ['message' => 'w00t w00t']; 53 | $getResponseEvent = $this->createGetResponseEvent(); 54 | 55 | $this->processor->onKernelRequest($getResponseEvent); 56 | 57 | $expectedRecord = $record; 58 | 59 | $this->assertEquals($expectedRecord, $this->invokeProcessor($record)); 60 | } 61 | 62 | /** 63 | * @test 64 | */ 65 | public function it_leaves_the_record_untouched_if_no_request_was_handled() 66 | { 67 | $record = ['message' => 'w00t w00t']; 68 | 69 | $expectedRecord = $record; 70 | 71 | $this->assertEquals($expectedRecord, $this->invokeProcessor($record)); 72 | } 73 | 74 | private function createGetResponseEvent($requestId = false) 75 | { 76 | $getResponseEventMock = $this->createMock(RequestEvent::class); 77 | 78 | $request = new Request(); 79 | 80 | if (false !== $requestId) { 81 | $request->headers->set($this->header, $requestId); 82 | } 83 | 84 | $getResponseEventMock 85 | ->expects($this->any()) 86 | ->method('getRequest') 87 | ->will($this->returnValue($request)); 88 | 89 | return $getResponseEventMock; 90 | } 91 | 92 | private function invokeProcessor(array $record) 93 | { 94 | return call_user_func_array($this->processor, [$record]); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /test/Qandidate/Stack/RequestIdTest.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Qandidate\Stack; 15 | 16 | use PHPUnit\Framework\TestCase; 17 | use Symfony\Component\HttpFoundation\Request; 18 | use Symfony\Component\HttpFoundation\Response; 19 | use Symfony\Component\HttpKernel\HttpKernelInterface; 20 | 21 | class RequestIdTest extends TestCase 22 | { 23 | private $app; 24 | private $requestIdGenerator; 25 | private $stackedApp; 26 | private $header = 'X-Request-Id'; 27 | 28 | public function setUp(): void 29 | { 30 | $this->requestIdGenerator = $this->createMock('Qandidate\Stack\RequestIdGenerator'); 31 | $this->app = new MockApp($this->header); 32 | $this->stackedApp = new RequestId($this->app, $this->requestIdGenerator, $this->header); 33 | } 34 | 35 | /** 36 | * @test 37 | */ 38 | public function it_calls_the_generator_when_no_request_id_is_present() 39 | { 40 | $this->requestIdGenerator->expects($this->once()) 41 | ->method('generate'); 42 | 43 | $this->stackedApp->handle($this->createRequest()); 44 | } 45 | 46 | /** 47 | * @test 48 | */ 49 | public function it_sets_the_request_id_in_the_header() 50 | { 51 | $this->requestIdGenerator->expects($this->once()) 52 | ->method('generate') 53 | ->will($this->returnValue('yolo')); 54 | 55 | $this->stackedApp->handle($this->createRequest()); 56 | 57 | $this->assertEquals('yolo', $this->app->getLastHeaderValue()); 58 | } 59 | 60 | /** 61 | * @test 62 | */ 63 | public function it_does_not_set_a_new_request_id_if_it_was_already_present() 64 | { 65 | $this->requestIdGenerator->expects($this->never()) 66 | ->method('generate'); 67 | 68 | $this->stackedApp->handle($this->createRequest('foo')); 69 | 70 | $this->assertEquals('foo', $this->app->getLastHeaderValue()); 71 | } 72 | 73 | /** 74 | * @test 75 | */ 76 | public function it_sets_the_request_id_in_the_response_header_if_enabled() 77 | { 78 | $this->stackedApp->enableResponseHeader(); 79 | 80 | $this->requestIdGenerator->expects($this->any()) 81 | ->method('generate') 82 | ->will($this->returnValue('yolo')); 83 | 84 | $response = $this->stackedApp->handle($this->createRequest()); 85 | 86 | $this->assertSame('yolo', $response->headers->get($this->header)); 87 | } 88 | 89 | /** 90 | * @test 91 | */ 92 | public function it_sets_the_request_id_in_a_custom_response_header_if_given() 93 | { 94 | $this->stackedApp->enableResponseHeader('Request-Id'); 95 | 96 | $this->requestIdGenerator->expects($this->any()) 97 | ->method('generate') 98 | ->will($this->returnValue('yolo')); 99 | 100 | $response = $this->stackedApp->handle($this->createRequest()); 101 | 102 | $this->assertSame('yolo', $response->headers->get('Request-Id')); 103 | } 104 | 105 | /** 106 | * @test 107 | */ 108 | public function it_can_set_the_response_header_from_the_constructor_argument() 109 | { 110 | $this->requestIdGenerator->expects($this->any()) 111 | ->method('generate') 112 | ->will($this->returnValue('yolo')); 113 | 114 | $responseHeader = 'Request-Id'; 115 | 116 | $this->stackedApp->enableResponseHeader($responseHeader); 117 | $normalResponse = $this->stackedApp->handle($this->createRequest()); 118 | 119 | $alternateStackedApp = new RequestId($this->app, $this->requestIdGenerator, $this->header, $responseHeader); 120 | $alternateResponse = $alternateStackedApp->handle($this->createRequest()); 121 | 122 | $this->assertSame('yolo', $alternateResponse->headers->get($responseHeader)); 123 | $this->assertSame( 124 | $normalResponse->headers->get($responseHeader), 125 | $alternateResponse->headers->get($responseHeader) 126 | ); 127 | } 128 | 129 | /** 130 | * @test 131 | */ 132 | public function it_can_override_the_response_header_argument_with_the_response_header_method() 133 | { 134 | $this->requestIdGenerator->expects($this->any()) 135 | ->method('generate') 136 | ->will($this->returnValue('yolo')); 137 | 138 | $alternateStackedApp = new RequestId($this->app, $this->requestIdGenerator, $this->header, 'Bad-Request-Id'); 139 | $alternateStackedApp->enableResponseHeader('Good-Request-Id'); 140 | 141 | $response = $alternateStackedApp->handle($this->CreateRequest()); 142 | 143 | $this->assertFalse($response->headers->has('Bad-Request-Id')); 144 | $this->assertTrue($response->headers->has('Good-Request-Id')); 145 | } 146 | 147 | /** 148 | * @test 149 | */ 150 | public function it_does_not_set_the_request_id_in_the_response_header_by_default() 151 | { 152 | $this->requestIdGenerator->expects($this->any()) 153 | ->method('generate') 154 | ->will($this->returnValue('yolo')); 155 | 156 | $response = $this->stackedApp->handle($this->createRequest()); 157 | 158 | $this->assertFalse($response->headers->has($this->header), 'The request id is not added to the response by default'); 159 | } 160 | 161 | private function createRequest($requestId = null) 162 | { 163 | $request = new Request(); 164 | 165 | if ($requestId) { 166 | $request->headers->set($this->header, $requestId); 167 | } 168 | 169 | return $request; 170 | } 171 | } 172 | 173 | class MockApp implements HttpKernelInterface 174 | { 175 | private $headerValue; 176 | private $recordHeader; 177 | 178 | public function __construct($recordHeader) 179 | { 180 | $this->recordHeader = $recordHeader; 181 | } 182 | 183 | public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) 184 | { 185 | $this->headerValue = $request->headers->get($this->recordHeader); 186 | 187 | return new Response(); 188 | } 189 | 190 | public function getLastHeaderValue() 191 | { 192 | return $this->headerValue; 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /test/Qandidate/Stack/UuidRequestIdGeneratorTest.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Qandidate\Stack; 15 | 16 | use PHPUnit\Framework\TestCase; 17 | 18 | class UuidRequestIdGeneratorTest extends TestCase 19 | { 20 | /** 21 | * @test 22 | */ 23 | public function it_generates_a_string() 24 | { 25 | $generator = new UuidRequestIdGenerator(); 26 | 27 | $this->assertIsString($generator->generate()); 28 | } 29 | } 30 | --------------------------------------------------------------------------------