diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md index c34dc79..7bf2bd8 100644 --- a/.github/CODE_OF_CONDUCT.md +++ b/.github/CODE_OF_CONDUCT.md @@ -2,4 +2,4 @@ Be free to fork this project. All inspired code or re-used code have to be licensed to GNU GPLv3. No contribution is accepted on this READ ONLY repository. Please contribute on https://github.com/abenevaut/opensource. -Roadmap: https://github.com/users/abenevaut/projects/15 +Roadmap: https://github.com/users/abenevaut/projects/14 diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 422e7f5..4c002f8 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,3 +1,3 @@ No contribution is accepted on this READ ONLY repository. Please contribute on https://github.com/abenevaut/opensource. -Roadmap: https://github.com/users/abenevaut/projects/15 +Roadmap: https://github.com/users/abenevaut/projects/14 diff --git a/.gitignore b/.gitignore index f6970af..b961d97 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ composer.lock coverage vendor +xdebug-errors.log diff --git a/composer.json b/composer.json index cbe2ee4..16f1d61 100644 --- a/composer.json +++ b/composer.json @@ -21,8 +21,10 @@ "phpunit/phpunit": "^10.5", "mockery/mockery": "^1.6", "squizlabs/php_codesniffer": "^3.9", - "icanhazstring/composer-unused": "^0.8.5", - "laravel/framework": "^9.45 || ^10 || ^11" + "laravel/framework": "^9.45 || ^10 || ^11", + "phpstan/phpstan": "^1.10 || ^2.0", + "nikic/php-parser": "^5.0", + "fakerphp/faker": "^1.24" }, "autoload": { "psr-4": { diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..588fcf7 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,9 @@ +services: + + app: + image: ghcr.io/abenevaut/vapor-ci:php83 + restart: unless-stopped + environment: + - php_xdebug_log=/var/task/xdebug-errors.log + volumes: + - .:/var/task diff --git a/docs/Contribute.md b/docs/Contribute.md new file mode 100644 index 0000000..110b776 --- /dev/null +++ b/docs/Contribute.md @@ -0,0 +1,9 @@ +# How to contribute + +## Build + +Maintain composer packages with php 8.1 + +```bash +/opt/homebrew/opt/php@8.1/bin/php /opt/homebrew/bin//composer update +``` diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..f6ea6ff --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,8 @@ +parameters: + level: 1 + editorUrl: 'phpstorm://open?file=%%file%%&line=%%line%%' + paths: + - src +rules: + - Tests\Rules\CodeDoesNotContainDumpRule + - Tests\Rules\ControllersExtendBaseControllerRule diff --git a/src/App/Events/EventInterface.php b/src/App/Events/EventInterface.php new file mode 100644 index 0000000..292adfb --- /dev/null +++ b/src/App/Events/EventInterface.php @@ -0,0 +1,8 @@ + + */ private array $queue = []; private int $queueLength = 0; + /** + * @var array + */ private array $computingQueue = []; private int $computingQueueLength = 0; @@ -24,7 +30,7 @@ public function getQueueLength(): int return $this->queueLength; } - public function handle(): bool + public function handle(): int { return $this ->boot() diff --git a/src/Http/Middleware/IdentifyClientRequestMiddleware.php b/src/Http/Middleware/IdentifyClientRequestMiddleware.php new file mode 100644 index 0000000..a3112ad --- /dev/null +++ b/src/Http/Middleware/IdentifyClientRequestMiddleware.php @@ -0,0 +1,24 @@ + + */ + protected function additionalContext(Request $request): array + { + $clientId = ''; + if (Auth::guard('api')->check()) { + $clientId = Auth::guard('api')->client()->id; + } + + return [ + 'client-id' => $clientId, + ]; + } +} diff --git a/src/Http/Middleware/IdentifyUserRequestMiddleware.php b/src/Http/Middleware/IdentifyUserRequestMiddleware.php new file mode 100644 index 0000000..7e1cc0b --- /dev/null +++ b/src/Http/Middleware/IdentifyUserRequestMiddleware.php @@ -0,0 +1,24 @@ + + */ + protected function additionalContext(Request $request): array + { + $userId = ''; + if (Auth::check()) { + $userId = Auth::user()->getAuthIdentifier(); + } + + return [ + 'user-id' => $userId, + ]; + } +} diff --git a/src/Http/Middleware/ShareLogsContextMiddlewareAbstract.php b/src/Http/Middleware/ShareLogsContextMiddlewareAbstract.php new file mode 100644 index 0000000..24b923e --- /dev/null +++ b/src/Http/Middleware/ShareLogsContextMiddlewareAbstract.php @@ -0,0 +1,43 @@ + + */ + abstract protected function additionalContext(Request $request): array; + + public function handle(Request $request, Closure $next): Response + { + $sharedContext = array_merge( + $this->requestHitId(), + $this->additionalContext($request) + ); + + Log::shareContext($sharedContext); + + /** @var Response $response */ + $response = $next($request); + $response->header('REQUEST-HIT-ID', $sharedContext['request-hit-id']); + + return $response; + } + + /** + * @return array + */ + protected function requestHitId(): array + { + return [ + 'request-hit-id' => (string) Str::ulid() + ]; + } +} diff --git a/src/Http/Requests/ApiFormRequestAbstract.php b/src/Http/Requests/ApiFormRequestAbstract.php new file mode 100644 index 0000000..45bbb03 --- /dev/null +++ b/src/Http/Requests/ApiFormRequestAbstract.php @@ -0,0 +1,18 @@ + $validator->errors()], 400); + + throw new HttpResponseException($response); + } +} diff --git a/tests/Rules/CodeDoesNotContainDumpRule.php b/tests/Rules/CodeDoesNotContainDumpRule.php new file mode 100644 index 0000000..cc12d07 --- /dev/null +++ b/tests/Rules/CodeDoesNotContainDumpRule.php @@ -0,0 +1,40 @@ +name instanceof NodeName) { + return []; + } + + $functionName = $node->name->toString(); + + if (in_array($functionName, ['dd', 'var_dump', 'dump'], true)) { + return [ + RuleErrorBuilder::message( + sprintf('Method %s is prohibited', $functionName) + )->build(), + ]; + } + + return []; + } +} diff --git a/tests/Rules/ControllersExtendBaseControllerRule.php b/tests/Rules/ControllersExtendBaseControllerRule.php new file mode 100644 index 0000000..afdedd6 --- /dev/null +++ b/tests/Rules/ControllersExtendBaseControllerRule.php @@ -0,0 +1,40 @@ +getNamespace()) + || !str_starts_with($scope->getNamespace(), 'abenevaut\Infrastructure\Http\Controllers') + ) { + return []; + } + + $reflectionClass = $node->getClassReflection(); + + if ($reflectionClass->getName() === 'abenevaut\Infrastructure\Http\Controllers\ControllerAbstract') { + return []; + } + + if (!$reflectionClass->isSubclassOf('Illuminate\Routing\Controller')) { + return [ + "Controllers should extend 'Illuminate\Routing\Controller' (see rule #49)" + ]; + } + + return []; + } +} diff --git a/tests/Unit/ShareLogsContextMiddlewareAbstractTest.php b/tests/Unit/ShareLogsContextMiddlewareAbstractTest.php new file mode 100644 index 0000000..982db8d --- /dev/null +++ b/tests/Unit/ShareLogsContextMiddlewareAbstractTest.php @@ -0,0 +1,55 @@ +makeFaker()->uuid; + + $middlewareStub = $this + ->createPartialMock( + ShareLogsContextMiddlewareAbstract::class, + [ + 'requestHitId', + 'additionalContext' + ] + ); + + $middlewareStub + ->expects($this->once()) + ->method('requestHitId') + ->willReturn(['request-hit-id' => $requestHitId]); + + $middlewareStub + ->expects($this->once()) + ->method('additionalContext') + ->willReturn([]); + + /** @var Response $request */ + $response = $middlewareStub + ->handle( + $this->createMock(Request::class), + function ($request) { + return new Response(); + } + ); + + Log::shouldHaveReceived('shareContext')->once()->withAnyArgs(); + + $this->assertTrue($response->headers->has('REQUEST-HIT-ID')); + $this->assertSame($response->headers->get('REQUEST-HIT-ID'), $requestHitId); + } +}