From c8c9c94c0ae5a8149233a71fa0624eab01ac2c95 Mon Sep 17 00:00:00 2001 From: Anton Zabolotnikob Date: Wed, 23 Jul 2025 17:39:08 +0500 Subject: [PATCH 1/9] fix: use invocable controllers with DI with bind by contracts --- src/Services/SwaggerService.php | 5 +- src/Traits/GetDependenciesTrait.php | 4 +- tests/SwaggerServiceTest.php | 22 ++++ tests/TestCase.php | 5 +- ..._get_user_request_invoke_bind_closure.json | 108 ++++++++++++++++++ .../support/Mock/InvokableTestController.php | 10 ++ tests/support/Mock/TestRequestContract.php | 7 ++ 7 files changed, 152 insertions(+), 9 deletions(-) create mode 100644 tests/fixtures/SwaggerServiceTest/tmp_data_get_user_request_invoke_bind_closure.json create mode 100644 tests/support/Mock/InvokableTestController.php create mode 100644 tests/support/Mock/TestRequestContract.php diff --git a/src/Services/SwaggerService.php b/src/Services/SwaggerService.php index fe0ec1b1..8944ce57 100644 --- a/src/Services/SwaggerService.php +++ b/src/Services/SwaggerService.php @@ -655,10 +655,7 @@ public function getConcreteRequest() return null; } - $parameters = $this->resolveClassMethodDependencies( - app($class), - $method - ); + $parameters = $this->resolveClassMethodDependencies(app($class), $method); return Arr::first($parameters, function ($key) { return preg_match('/Request/', $key); diff --git a/src/Traits/GetDependenciesTrait.php b/src/Traits/GetDependenciesTrait.php index c0e8715e..cf91cf36 100644 --- a/src/Traits/GetDependenciesTrait.php +++ b/src/Traits/GetDependenciesTrait.php @@ -39,8 +39,6 @@ protected function getClassByInterface($interfaceName) return null; } - $classFields = (new ReflectionFunction($implementation))->getStaticVariables(); - - return $classFields['concrete']; + return get_class($implementation(app())); } } diff --git a/tests/SwaggerServiceTest.php b/tests/SwaggerServiceTest.php index bef5d334..dd0d3403 100644 --- a/tests/SwaggerServiceTest.php +++ b/tests/SwaggerServiceTest.php @@ -11,9 +11,11 @@ use RonasIT\AutoDoc\Exceptions\UnsupportedDocumentationViewerException; use RonasIT\AutoDoc\Exceptions\WrongSecurityConfigException; use RonasIT\AutoDoc\Services\SwaggerService; +use RonasIT\AutoDoc\Tests\Support\Mock\InvokableTestController; use RonasIT\AutoDoc\Tests\Support\Mock\TestContract; use RonasIT\AutoDoc\Tests\Support\Mock\TestNotificationSetting; use RonasIT\AutoDoc\Tests\Support\Mock\TestRequest; +use RonasIT\AutoDoc\Tests\Support\Mock\TestRequestContract; use RonasIT\AutoDoc\Tests\Support\Traits\SwaggerServiceMockTrait; use stdClass; @@ -873,4 +875,24 @@ public function testAddDataWhenInvokableClass() app(SwaggerService::class)->addData($request, $response); } + + public function testAddDataWhenInvokableClassWithBindingContract() + { + $this->mockDriverGetEmptyAndSaveProcessTmpData($this->getJsonFixture('tmp_data_get_user_request_invoke_bind_closure')); + + $this->app->bind(TestRequestContract::class, fn () => new TestRequest()); + + $request = $this->generateRequest( + type: 'get', + uri: 'users', + controllerMethod: '__invoke', + controllerClass: InvokableTestController::class, + ); + + $response = $this->generateResponse('example_success_user_response.json', 200, [ + 'Content-type' => 'application/json', + ]); + + app(SwaggerService::class)->addData($request, $response); + } } diff --git a/tests/TestCase.php b/tests/TestCase.php index fde97e94..cb7e3792 100755 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -126,12 +126,13 @@ protected function generateRequest( array $headers = [], array $routeConditions = [], string $controllerMethod = 'test', + string $controllerClass = TestController::class, ): Request { $request = $this->getBaseRequest($type, $uri, $data, $pathParams, $headers); - return $request->setRouteResolver(function () use ($uri, $request, $controllerMethod, $routeConditions) { + return $request->setRouteResolver(function () use ($uri, $request, $controllerMethod, $routeConditions, $controllerClass) { $route = Route::get($uri) - ->setAction(['controller' => TestController::class . '@' . $controllerMethod]) + ->setAction(['controller' => $controllerClass . '@' . $controllerMethod]) ->bind($request); foreach ($routeConditions as $condition) { diff --git a/tests/fixtures/SwaggerServiceTest/tmp_data_get_user_request_invoke_bind_closure.json b/tests/fixtures/SwaggerServiceTest/tmp_data_get_user_request_invoke_bind_closure.json new file mode 100644 index 00000000..2f32eb60 --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/tmp_data_get_user_request_invoke_bind_closure.json @@ -0,0 +1,108 @@ +{ + "openapi": "3.1.0", + "servers": [ + { + "url": "http:\/\/localhost" + } + ], + "paths": { + "\/users": { + "get": { + "tags": [ + "users" + ], + "consumes": [], + "produces": [ + "application\/json" + ], + "parameters": [ + { + "in": "query", + "name": "query", + "description": "string, required", + "schema": { + "type": "string" + }, + "required": true + }, + { + "in": "query", + "name": "user_id", + "description": "integer, with_to_array_rule_string_name", + "schema": { + "type": "integer" + } + }, + { + "in": "query", + "name": "is_email_enabled", + "description": "test_rule_without_to_string", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Operation successfully done", + "content": { + "application\/json": { + "schema": { + "type": "object", + "$ref": "#\/components\/schemas\/getUsers200ResponseObject" + }, + "example": { + "id": 2, + "name": "first_client", + "likes_count": 23, + "role": { + "id": 2, + "name": "client" + }, + "type": "reader" + } + } + } + } + }, + "security": [], + "description": "", + "deprecated": false, + "summary": "test" + } + } + }, + "components": { + "schemas": { + "getUsers200ResponseObject": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "likes_count": { + "type": "integer" + }, + "role": { + "type": "array" + }, + "type": { + "type": "string" + } + } + } + } + }, + "info": { + "description": "This is automatically collected documentation", + "version": "0.0.0", + "title": "Name of Your Application", + "termsOfService": "", + "contact": { + "email": "your@email.com" + } + } +} \ No newline at end of file diff --git a/tests/support/Mock/InvokableTestController.php b/tests/support/Mock/InvokableTestController.php new file mode 100644 index 00000000..9d5db588 --- /dev/null +++ b/tests/support/Mock/InvokableTestController.php @@ -0,0 +1,10 @@ + Date: Wed, 23 Jul 2025 17:51:38 +0500 Subject: [PATCH 2/9] fix: use invocable controllers with DI with bind by contracts --- src/Traits/GetDependenciesTrait.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Traits/GetDependenciesTrait.php b/src/Traits/GetDependenciesTrait.php index cf91cf36..39dac88c 100644 --- a/src/Traits/GetDependenciesTrait.php +++ b/src/Traits/GetDependenciesTrait.php @@ -18,7 +18,7 @@ public function resolveClassMethodDependencies(object $instance, string $method) }, (new ReflectionMethod($instance, $method))->getParameters()); } - protected function transformDependency(ReflectionParameter $parameter) + protected function transformDependency(ReflectionParameter $parameter): ?string { $type = $parameter->getType(); @@ -29,7 +29,7 @@ protected function transformDependency(ReflectionParameter $parameter) return interface_exists($type->getName()) ? $this->getClassByInterface($type->getName()) : $type->getName(); } - protected function getClassByInterface($interfaceName) + protected function getClassByInterface($interfaceName): ?string { $bindings = Container::getInstance()->getBindings(); @@ -39,6 +39,6 @@ protected function getClassByInterface($interfaceName) return null; } - return get_class($implementation(app())); + return get_class(call_user_func($implementation, app())); } } From ed651124bde45d22161c63ee8425bcba1c5e3aad Mon Sep 17 00:00:00 2001 From: Anton Zabolotnikob Date: Wed, 23 Jul 2025 17:56:14 +0500 Subject: [PATCH 3/9] fix: use invocable controllers with DI with bind by contracts --- src/Services/SwaggerService.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Services/SwaggerService.php b/src/Services/SwaggerService.php index 8944ce57..fe0ec1b1 100644 --- a/src/Services/SwaggerService.php +++ b/src/Services/SwaggerService.php @@ -655,7 +655,10 @@ public function getConcreteRequest() return null; } - $parameters = $this->resolveClassMethodDependencies(app($class), $method); + $parameters = $this->resolveClassMethodDependencies( + app($class), + $method + ); return Arr::first($parameters, function ($key) { return preg_match('/Request/', $key); From 3506f8fdd4d7215823385e2926bfa2a096aa3b1b Mon Sep 17 00:00:00 2001 From: Anton Zabolotnikob Date: Wed, 23 Jul 2025 17:58:11 +0500 Subject: [PATCH 4/9] fix: use invocable controllers with DI with bind by contracts --- tests/SwaggerServiceTest.php | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/tests/SwaggerServiceTest.php b/tests/SwaggerServiceTest.php index dd0d3403..16ae20e6 100644 --- a/tests/SwaggerServiceTest.php +++ b/tests/SwaggerServiceTest.php @@ -876,7 +876,7 @@ public function testAddDataWhenInvokableClass() app(SwaggerService::class)->addData($request, $response); } - public function testAddDataWhenInvokableClassWithBindingContract() + public function testAddDataWhenInvokableClassWithBindingContractToObject() { $this->mockDriverGetEmptyAndSaveProcessTmpData($this->getJsonFixture('tmp_data_get_user_request_invoke_bind_closure')); @@ -895,4 +895,24 @@ public function testAddDataWhenInvokableClassWithBindingContract() app(SwaggerService::class)->addData($request, $response); } + + public function testAddDataWhenInvokableClassWithBindingContractToClassName() + { + $this->mockDriverGetEmptyAndSaveProcessTmpData($this->getJsonFixture('tmp_data_get_user_request_invoke_bind_closure')); + + $this->app->bind(TestRequestContract::class, TestRequest::class); + + $request = $this->generateRequest( + type: 'get', + uri: 'users', + controllerMethod: '__invoke', + controllerClass: InvokableTestController::class, + ); + + $response = $this->generateResponse('example_success_user_response.json', 200, [ + 'Content-type' => 'application/json', + ]); + + app(SwaggerService::class)->addData($request, $response); + } } From f028ec879f7d34fcf8df237d8b631ae225e3ed40 Mon Sep 17 00:00:00 2001 From: Anton Zabolotnikob Date: Thu, 24 Jul 2025 10:42:26 +0500 Subject: [PATCH 5/9] fix: use invocable controllers with DI with bind by contracts --- tests/SwaggerServiceTest.php | 58 +++------- tests/TestCase.php | 5 +- ..._get_user_request_invoke_bind_closure.json | 108 ------------------ .../support/Mock/InvokableTestController.php | 10 -- tests/support/Mock/TestRequestContract.php | 7 -- 5 files changed, 18 insertions(+), 170 deletions(-) delete mode 100644 tests/fixtures/SwaggerServiceTest/tmp_data_get_user_request_invoke_bind_closure.json delete mode 100644 tests/support/Mock/InvokableTestController.php delete mode 100644 tests/support/Mock/TestRequestContract.php diff --git a/tests/SwaggerServiceTest.php b/tests/SwaggerServiceTest.php index 16ae20e6..c9781196 100644 --- a/tests/SwaggerServiceTest.php +++ b/tests/SwaggerServiceTest.php @@ -684,9 +684,23 @@ public function testAddDataWithNotExistsMethodOnController() $service->addData($request, $response); } - public function testAddDataWithBindingInterface() + public static function addDataWithBindingInterface(): array { - $this->app->bind(TestContract::class, TestRequest::class); + return [ + [ + 'concrete' => TestRequest::class, + ], + [ + 'concrete' => fn ($app) => new TestRequest(), + ], + ]; + } + + #[DataProvider('addDataWithBindingInterface')] + public function testAddDataWithBindingInterface($concrete) + { + $this->app->bind(TestContract::class, $concrete); + $this->mockDriverGetEmptyAndSaveProcessTmpData($this->getJsonFixture('tmp_data_get_user_request')); $service = app(SwaggerService::class); @@ -875,44 +889,4 @@ public function testAddDataWhenInvokableClass() app(SwaggerService::class)->addData($request, $response); } - - public function testAddDataWhenInvokableClassWithBindingContractToObject() - { - $this->mockDriverGetEmptyAndSaveProcessTmpData($this->getJsonFixture('tmp_data_get_user_request_invoke_bind_closure')); - - $this->app->bind(TestRequestContract::class, fn () => new TestRequest()); - - $request = $this->generateRequest( - type: 'get', - uri: 'users', - controllerMethod: '__invoke', - controllerClass: InvokableTestController::class, - ); - - $response = $this->generateResponse('example_success_user_response.json', 200, [ - 'Content-type' => 'application/json', - ]); - - app(SwaggerService::class)->addData($request, $response); - } - - public function testAddDataWhenInvokableClassWithBindingContractToClassName() - { - $this->mockDriverGetEmptyAndSaveProcessTmpData($this->getJsonFixture('tmp_data_get_user_request_invoke_bind_closure')); - - $this->app->bind(TestRequestContract::class, TestRequest::class); - - $request = $this->generateRequest( - type: 'get', - uri: 'users', - controllerMethod: '__invoke', - controllerClass: InvokableTestController::class, - ); - - $response = $this->generateResponse('example_success_user_response.json', 200, [ - 'Content-type' => 'application/json', - ]); - - app(SwaggerService::class)->addData($request, $response); - } } diff --git a/tests/TestCase.php b/tests/TestCase.php index cb7e3792..fde97e94 100755 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -126,13 +126,12 @@ protected function generateRequest( array $headers = [], array $routeConditions = [], string $controllerMethod = 'test', - string $controllerClass = TestController::class, ): Request { $request = $this->getBaseRequest($type, $uri, $data, $pathParams, $headers); - return $request->setRouteResolver(function () use ($uri, $request, $controllerMethod, $routeConditions, $controllerClass) { + return $request->setRouteResolver(function () use ($uri, $request, $controllerMethod, $routeConditions) { $route = Route::get($uri) - ->setAction(['controller' => $controllerClass . '@' . $controllerMethod]) + ->setAction(['controller' => TestController::class . '@' . $controllerMethod]) ->bind($request); foreach ($routeConditions as $condition) { diff --git a/tests/fixtures/SwaggerServiceTest/tmp_data_get_user_request_invoke_bind_closure.json b/tests/fixtures/SwaggerServiceTest/tmp_data_get_user_request_invoke_bind_closure.json deleted file mode 100644 index 2f32eb60..00000000 --- a/tests/fixtures/SwaggerServiceTest/tmp_data_get_user_request_invoke_bind_closure.json +++ /dev/null @@ -1,108 +0,0 @@ -{ - "openapi": "3.1.0", - "servers": [ - { - "url": "http:\/\/localhost" - } - ], - "paths": { - "\/users": { - "get": { - "tags": [ - "users" - ], - "consumes": [], - "produces": [ - "application\/json" - ], - "parameters": [ - { - "in": "query", - "name": "query", - "description": "string, required", - "schema": { - "type": "string" - }, - "required": true - }, - { - "in": "query", - "name": "user_id", - "description": "integer, with_to_array_rule_string_name", - "schema": { - "type": "integer" - } - }, - { - "in": "query", - "name": "is_email_enabled", - "description": "test_rule_without_to_string", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Operation successfully done", - "content": { - "application\/json": { - "schema": { - "type": "object", - "$ref": "#\/components\/schemas\/getUsers200ResponseObject" - }, - "example": { - "id": 2, - "name": "first_client", - "likes_count": 23, - "role": { - "id": 2, - "name": "client" - }, - "type": "reader" - } - } - } - } - }, - "security": [], - "description": "", - "deprecated": false, - "summary": "test" - } - } - }, - "components": { - "schemas": { - "getUsers200ResponseObject": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "name": { - "type": "string" - }, - "likes_count": { - "type": "integer" - }, - "role": { - "type": "array" - }, - "type": { - "type": "string" - } - } - } - } - }, - "info": { - "description": "This is automatically collected documentation", - "version": "0.0.0", - "title": "Name of Your Application", - "termsOfService": "", - "contact": { - "email": "your@email.com" - } - } -} \ No newline at end of file diff --git a/tests/support/Mock/InvokableTestController.php b/tests/support/Mock/InvokableTestController.php deleted file mode 100644 index 9d5db588..00000000 --- a/tests/support/Mock/InvokableTestController.php +++ /dev/null @@ -1,10 +0,0 @@ - Date: Fri, 25 Jul 2025 11:58:00 +0500 Subject: [PATCH 6/9] fix: use invocable controllers with DI with bind by contracts --- .github/workflows/run-tests-with-coverage.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run-tests-with-coverage.yml b/.github/workflows/run-tests-with-coverage.yml index 5991c0b9..1c95d01b 100644 --- a/.github/workflows/run-tests-with-coverage.yml +++ b/.github/workflows/run-tests-with-coverage.yml @@ -20,10 +20,13 @@ jobs: - name: Install Dependencies run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist - name: Execute unit tests via PHPUnit with coverage - run: vendor/bin/phpunit --coverage-clover build/logs/clover.xml + run: vendor/bin/phpunit --coverage-clover build/logs/clover-${{ matrix.php-version }}.xml - name: Upload coverage results to Coveralls env: COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | composer global require php-coveralls/php-coveralls - php-coveralls --coverage_clover=build/logs/clover.xml -v + php-coveralls --coverage_clover=build/logs/clover-${{ matrix.php-version }}.xml -v + + + From 5b3b1ebdee525c5a516bb60bc142dd444392dc2d Mon Sep 17 00:00:00 2001 From: Anton Zabolotnikob Date: Fri, 25 Jul 2025 11:59:21 +0500 Subject: [PATCH 7/9] fix: use invocable controllers with DI with bind by contracts --- .github/workflows/run-tests-with-coverage.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/run-tests-with-coverage.yml b/.github/workflows/run-tests-with-coverage.yml index 1c95d01b..88b9a526 100644 --- a/.github/workflows/run-tests-with-coverage.yml +++ b/.github/workflows/run-tests-with-coverage.yml @@ -26,7 +26,4 @@ jobs: COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | composer global require php-coveralls/php-coveralls - php-coveralls --coverage_clover=build/logs/clover-${{ matrix.php-version }}.xml -v - - - + php-coveralls --coverage_clover=build/logs/clover-${{ matrix.php-version }}.xml -v \ No newline at end of file From 28008c9b0f2ea85245f8ff16203aa59392a110c7 Mon Sep 17 00:00:00 2001 From: Anton Zabolotnikob Date: Fri, 25 Jul 2025 11:59:42 +0500 Subject: [PATCH 8/9] fix: use invocable controllers with DI with bind by contracts --- .github/workflows/run-tests-with-coverage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests-with-coverage.yml b/.github/workflows/run-tests-with-coverage.yml index 88b9a526..af5aeaa5 100644 --- a/.github/workflows/run-tests-with-coverage.yml +++ b/.github/workflows/run-tests-with-coverage.yml @@ -26,4 +26,4 @@ jobs: COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | composer global require php-coveralls/php-coveralls - php-coveralls --coverage_clover=build/logs/clover-${{ matrix.php-version }}.xml -v \ No newline at end of file + php-coveralls --coverage_clover=build/logs/clover-${{ matrix.php-version }}.xml -v From 990ee072a39e8086726c327c54ef81f539df7234 Mon Sep 17 00:00:00 2001 From: DenTray Date: Thu, 14 Aug 2025 14:49:43 +0600 Subject: [PATCH 9/9] Apply suggestions from code review --- src/Traits/GetDependenciesTrait.php | 6 ++++-- tests/SwaggerServiceTest.php | 14 ++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Traits/GetDependenciesTrait.php b/src/Traits/GetDependenciesTrait.php index 39dac88c..b982346a 100644 --- a/src/Traits/GetDependenciesTrait.php +++ b/src/Traits/GetDependenciesTrait.php @@ -31,7 +31,9 @@ protected function transformDependency(ReflectionParameter $parameter): ?string protected function getClassByInterface($interfaceName): ?string { - $bindings = Container::getInstance()->getBindings(); + $app = Container::getInstance(); + + $bindings = $app->getBindings(); $implementation = Arr::get($bindings, "{$interfaceName}.concrete"); @@ -39,6 +41,6 @@ protected function getClassByInterface($interfaceName): ?string return null; } - return get_class(call_user_func($implementation, app())); + return get_class(call_user_func($implementation, $app)); } } diff --git a/tests/SwaggerServiceTest.php b/tests/SwaggerServiceTest.php index c9781196..0516d5ca 100644 --- a/tests/SwaggerServiceTest.php +++ b/tests/SwaggerServiceTest.php @@ -11,11 +11,9 @@ use RonasIT\AutoDoc\Exceptions\UnsupportedDocumentationViewerException; use RonasIT\AutoDoc\Exceptions\WrongSecurityConfigException; use RonasIT\AutoDoc\Services\SwaggerService; -use RonasIT\AutoDoc\Tests\Support\Mock\InvokableTestController; use RonasIT\AutoDoc\Tests\Support\Mock\TestContract; use RonasIT\AutoDoc\Tests\Support\Mock\TestNotificationSetting; use RonasIT\AutoDoc\Tests\Support\Mock\TestRequest; -use RonasIT\AutoDoc\Tests\Support\Mock\TestRequestContract; use RonasIT\AutoDoc\Tests\Support\Traits\SwaggerServiceMockTrait; use stdClass; @@ -684,22 +682,22 @@ public function testAddDataWithNotExistsMethodOnController() $service->addData($request, $response); } - public static function addDataWithBindingInterface(): array + public static function getImplementations(): array { return [ [ - 'concrete' => TestRequest::class, + 'implementation' => TestRequest::class, ], [ - 'concrete' => fn ($app) => new TestRequest(), + 'implementation' => fn ($app) => new TestRequest(), ], ]; } - #[DataProvider('addDataWithBindingInterface')] - public function testAddDataWithBindingInterface($concrete) + #[DataProvider('getImplementations')] + public function testAddDataWithBindingInterface($implementation) { - $this->app->bind(TestContract::class, $concrete); + $this->app->bind(TestContract::class, $implementation); $this->mockDriverGetEmptyAndSaveProcessTmpData($this->getJsonFixture('tmp_data_get_user_request'));