From 15a557e3ee3a18b6354648cba327408e7446be14 Mon Sep 17 00:00:00 2001 From: Jelle van Oosterbosch Date: Sun, 1 Mar 2026 21:41:00 +0100 Subject: [PATCH 1/4] Bundle configuration to automatically include ProblemDetails in the OpenAPI schema --- src/DependencyInjection/Configuration.php | 8 + .../StixxOpenApiCommandExtension.php | 50 +++- src/Model/ProblemDetails.php | 44 +++ .../ProblemDetailsInvalidRequestBody.php | 31 ++ src/Model/Violation.php | 36 +++ .../nelmio_problem_details.yaml | 90 ++++++ .../App/Command/CreateBookCommand.php | 2 + .../App/Command/DeleteBookCommand.php | 2 + .../App/Command/UpdateBookCommand.php | 3 + .../ProblemDetailsConfigurationTest.php | 117 ++++++++ .../config/disable_problem_details.php | 22 ++ .../Resources/config/override_nelmio.php | 28 ++ .../Resources/specifications/openapi.json | 273 ++++++++++++++++++ .../DependencyInjection/ConfigurationTest.php | 14 +- 14 files changed, 718 insertions(+), 2 deletions(-) create mode 100644 src/Model/ProblemDetails.php create mode 100644 src/Model/ProblemDetailsInvalidRequestBody.php create mode 100644 src/Model/Violation.php create mode 100644 src/Resources/specifications/nelmio_problem_details.yaml create mode 100644 tests/Functional/ProblemDetailsConfigurationTest.php create mode 100644 tests/Functional/Resources/config/disable_problem_details.php create mode 100644 tests/Functional/Resources/config/override_nelmio.php diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index c593186..1f768a9 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -39,6 +39,14 @@ public function getConfigTreeBuilder(): TreeBuilder ->end() ->end() ->end() + ->arrayNode('openapi') + ->addDefaultsIfNotSet() + ->children() + ->booleanNode('problem_details') + ->defaultTrue() + ->end() + ->end() + ->end() ->end(); return $treeBuilder; diff --git a/src/DependencyInjection/StixxOpenApiCommandExtension.php b/src/DependencyInjection/StixxOpenApiCommandExtension.php index 50499aa..6b408ea 100644 --- a/src/DependencyInjection/StixxOpenApiCommandExtension.php +++ b/src/DependencyInjection/StixxOpenApiCommandExtension.php @@ -13,15 +13,63 @@ namespace Stixx\OpenApiCommandBundle\DependencyInjection; +use Stixx\OpenApiCommandBundle\Model\ProblemDetails; +use Stixx\OpenApiCommandBundle\Model\ProblemDetailsInvalidRequestBody; +use Stixx\OpenApiCommandBundle\Model\Violation; use Stixx\OpenApiCommandBundle\Responder\ResponderInterface; use Stixx\OpenApiCommandBundle\Validator\ValidatorInterface; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Extension\Extension; +use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; +use Symfony\Component\Yaml\Yaml; -final class StixxOpenApiCommandExtension extends Extension +final class StixxOpenApiCommandExtension extends Extension implements PrependExtensionInterface { + public function prepend(ContainerBuilder $container): void + { + $configs = $container->getExtensionConfig($this->getAlias()); + /** @var array{openapi: array{problem_details: bool}} $config */ + $config = $this->processConfiguration(new Configuration(), $configs); + + if (!$config['openapi']['problem_details']) { + return; + } + + $problemDetailsConfigPath = __DIR__.'/../Resources/specifications/nelmio_problem_details.yaml'; + if (!file_exists($problemDetailsConfigPath)) { + return; + } + + $problemDetailsConfig = Yaml::parseFile($problemDetailsConfigPath); + + if (is_array($problemDetailsConfig) && isset($problemDetailsConfig['nelmio_api_doc']) && is_array($problemDetailsConfig['nelmio_api_doc'])) { + /** @var array $nelmioConfig */ + $nelmioConfig = $problemDetailsConfig['nelmio_api_doc']; + $container->prependExtensionConfig('nelmio_api_doc', $nelmioConfig); + } + + $container->prependExtensionConfig('nelmio_api_doc', [ + 'models' => [ + 'names' => [ + [ + 'alias' => 'ProblemDetails', + 'type' => ProblemDetails::class, + ], + [ + 'alias' => 'Violation', + 'type' => Violation::class, + ], + [ + 'alias' => 'ProblemDetailsInvalidRequestBody', + 'type' => ProblemDetailsInvalidRequestBody::class, + ], + ], + ], + ]); + } + public function load(array $configs, ContainerBuilder $container): void { $configuration = new Configuration(); diff --git a/src/Model/ProblemDetails.php b/src/Model/ProblemDetails.php new file mode 100644 index 0000000..495ae60 --- /dev/null +++ b/src/Model/ProblemDetails.php @@ -0,0 +1,44 @@ +bootKernel(); + $container = self::getContainer(); + + /** @var ApiDocGenerator $generator */ + $generator = $container->get('nelmio_api_doc.generator.default'); + /** @var array{components?: array{responses?: array, schemas?: array}} $spec */ + $spec = json_decode($generator->generate()->toJson(), true); + + // Assert + $this->assertIsArray($spec); + $this->assertArrayHasKey('components', $spec); + $components = $spec['components']; + $this->assertIsArray($components); + + $this->assertArrayHasKey('responses', $components); + $responses = $components['responses']; + $this->assertIsArray($responses); + + $this->assertArrayHasKey('InvalidRequestProblemDetailsResponse', $responses); + $this->assertArrayHasKey('DefaultProblemDetailsResponse', $responses); + + $this->assertArrayHasKey('schemas', $components); + $schemas = $components['schemas']; + $this->assertIsArray($schemas); + // These schemas come from the attributes in the Model classes + $this->assertArrayHasKey('ProblemDetails', $schemas); + $this->assertArrayHasKey('ProblemDetailsInvalidRequestBody', $schemas); + } + + #[WithoutErrorHandler] + public function testProblemDetailsCanBeDisabled(): void + { + // Arrange + $kernel = $this->createKernelWithConfig(static function (App\Kernel $kernel): void { + $kernel->addTestConfig(__DIR__.'/Resources/config/disable_problem_details.php'); + }); + $container = $kernel->getContainer(); + + /** @var ApiDocGenerator $generator */ + $generator = $container->get('nelmio_api_doc.generator.default'); + + $prevErrorHandler = set_error_handler(static fn () => true); + /** @var array{components?: array{responses?: array}} $spec */ + $spec = json_decode($generator->generate()->toJson(), true); + set_error_handler($prevErrorHandler); + + // Assert + $this->assertIsArray($spec); + if (isset($spec['components']['responses'])) { + $this->assertArrayNotHasKey('InvalidRequestProblemDetailsResponse', $spec['components']['responses']); + } + } + + #[WithoutErrorHandler] + public function testProblemDetailsCanBeOverriddenByProjectConfig(): void + { + // Arrange + $kernel = $this->createKernelWithConfig(static function (App\Kernel $kernel): void { + $kernel->addTestConfig(__DIR__.'/Resources/config/override_nelmio.php'); + }); + $container = $kernel->getContainer(); + + /** @var ApiDocGenerator $generator */ + $generator = $container->get('nelmio_api_doc.generator.default'); + + $prevErrorHandler = set_error_handler(static fn () => true); + /** @var array{components?: array{responses?: array}} $spec */ + $spec = json_decode($generator->generate()->toJson(), true); + set_error_handler($prevErrorHandler); + + // Assert + $this->assertIsArray($spec); + $this->assertArrayHasKey('components', $spec); + $components = $spec['components']; + $this->assertIsArray($components); + + $this->assertArrayHasKey('responses', $components); + $responses = $components['responses']; + $this->assertIsArray($responses); + + $this->assertArrayHasKey('InvalidRequestProblemDetailsResponse', $responses); + $response = $responses['InvalidRequestProblemDetailsResponse']; + $this->assertIsArray($response); + + $this->assertArrayHasKey('description', $response); + $this->assertEquals( + 'Overridden RFC7807 Problem Details', + $response['description'] + ); + + $this->assertArrayNotHasKey('content', $response); + } +} diff --git a/tests/Functional/Resources/config/disable_problem_details.php b/tests/Functional/Resources/config/disable_problem_details.php new file mode 100644 index 0000000..60af1d4 --- /dev/null +++ b/tests/Functional/Resources/config/disable_problem_details.php @@ -0,0 +1,22 @@ +extension('stixx_openapi_command', [ + 'openapi' => [ + 'problem_details' => false, + ], + ]); +}; diff --git a/tests/Functional/Resources/config/override_nelmio.php b/tests/Functional/Resources/config/override_nelmio.php new file mode 100644 index 0000000..1258540 --- /dev/null +++ b/tests/Functional/Resources/config/override_nelmio.php @@ -0,0 +1,28 @@ +extension('nelmio_api_doc', [ + 'documentation' => [ + 'components' => [ + 'responses' => [ + 'InvalidRequestProblemDetailsResponse' => [ + 'description' => 'Overridden RFC7807 Problem Details', + ], + ], + ], + ], + ]); +}; diff --git a/tests/Functional/Resources/specifications/openapi.json b/tests/Functional/Resources/specifications/openapi.json index 5729d83..d448ed9 100644 --- a/tests/Functional/Resources/specifications/openapi.json +++ b/tests/Functional/Resources/specifications/openapi.json @@ -29,6 +29,12 @@ } } } + }, + "400": { + "$ref": "#/components/responses/InvalidRequestProblemDetailsResponse" + }, + "500": { + "$ref": "#/components/responses/DefaultProblemDetailsResponse" } } } @@ -67,6 +73,15 @@ } } } + }, + "400": { + "$ref": "#/components/responses/InvalidRequestProblemDetailsResponse" + }, + "404": { + "$ref": "#/components/responses/ResourceNotFoundProblemDetailsResponse" + }, + "500": { + "$ref": "#/components/responses/DefaultProblemDetailsResponse" } } }, @@ -86,6 +101,12 @@ "responses": { "204": { "description": "Book deleted" + }, + "404": { + "$ref": "#/components/responses/ResourceNotFoundProblemDetailsResponse" + }, + "500": { + "$ref": "#/components/responses/DefaultProblemDetailsResponse" } } } @@ -93,6 +114,125 @@ }, "components": { "schemas": { + "ProblemDetails": { + "title": "ProblemDetails", + "required": [ + "type", + "title", + "status" + ], + "properties": { + "type": { + "description": "A URI reference [RFC3986] that identifies the problem type.", + "type": "string", + "format": "url", + "default": "about:blank" + }, + "title": { + "description": "A short, human-readable summary of the problem type.", + "type": "string", + "default": "An error occurred" + }, + "status": { + "description": "The HTTP status code generated by the origin server for this occurrence of the problem.", + "type": "integer", + "default": 400 + }, + "detail": { + "description": "A human-readable explanation specific to this occurrence of the problem.", + "type": "string", + "default": null, + "nullable": true + }, + "instance": { + "description": "A URI reference that identifies the specific occurrence of the problem.", + "type": "string", + "format": "uri", + "default": null, + "nullable": true + } + }, + "type": "object", + "externalDocs": { + "description": "Problem Details for HTTP APIs", + "url": "https://datatracker.ietf.org/doc/html/rfc7807" + } + }, + "Violation": { + "required": [ + "propertyPath", + "message", + "code", + "constraint" + ], + "properties": { + "propertyPath": { + "type": "string" + }, + "message": { + "type": "string" + }, + "code": { + "type": "string" + }, + "constraint": { + "type": "string" + }, + "error": { + "type": "string", + "nullable": true + } + }, + "type": "object" + }, + "ProblemDetailsInvalidRequestBody": { + "title": "InvalidRequestBody", + "required": [ + "violations" + ], + "properties": { + "violations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Violation" + } + }, + "type": { + "description": "A URI reference [RFC3986] that identifies the problem type.", + "type": "string", + "format": "url", + "default": "about:blank" + }, + "title": { + "description": "A short, human-readable summary of the problem type.", + "type": "string", + "default": "An error occurred" + }, + "status": { + "description": "The HTTP status code generated by the origin server for this occurrence of the problem.", + "type": "integer", + "default": 400 + }, + "detail": { + "description": "A human-readable explanation specific to this occurrence of the problem.", + "type": "string", + "default": null, + "nullable": true + }, + "instance": { + "description": "A URI reference that identifies the specific occurrence of the problem.", + "type": "string", + "format": "uri", + "default": null, + "nullable": true + } + }, + "type": "object", + "example": { + "title": "The request body contains errors.", + "type": "about:blank" + } + }, "BookRequest": { "title": "BookRequest", "description": "Request payload for a book", @@ -139,6 +279,139 @@ }, "type": "object" } + }, + "responses": { + "InvalidRequestProblemDetailsResponse": { + "description": "RFC7807 Problem Details", + "content": { + "application/problem+json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/ProblemDetailsInvalidRequestBody" + }, + { + "$ref": "#/components/schemas/ProblemDetails" + } + ] + }, + "examples": { + "invalidJsonInRequestBody": { + "summary": "Invalid JSON in Request Body", + "value": { + "type": "about:blank", + "title": "The request body contains errors", + "status": 400, + "detail": "Validation failed.", + "violations": [ + { + "propertyPath": "foo", + "message": "This value should not be blank.", + "code": "c1ac8c7d-eab5-458f-a950-fcf121d23059", + "constraint": "NotBlank", + "error": "NOT_EQUAL_ERROR" + } + ] + } + }, + "invalidRequestBody": { + "summary": "Invalid Request Body", + "value": { + "type": "about:blank", + "title": "The request body contains errors", + "status": 400, + "detail": "Body does not match schema", + "violations": [ + { + "constraint": "openapi_request_validation", + "message": "Body does not match schema for content-type \"application/json\" for Request [post /api/example]" + } + ] + } + } + } + } + } + }, + "UnauthorizedProblemDetailsResponse": { + "description": "RFC7807 Unauthorized Problem Details", + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + }, + "example": { + "type": "about:blank", + "title": "Unauthorized", + "status": 401, + "detail": "The authentication token is missing or invalid." + } + } + } + }, + "ForbiddenProblemDetailsResponse": { + "description": "RFC7807 Forbidden Problem Details", + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + }, + "example": { + "type": "about:blank", + "title": "Forbidden", + "status": 403, + "detail": "You are not allowed to perform this action." + } + } + } + }, + "ResourceNotFoundProblemDetailsResponse": { + "description": "RFC7807 Resource Not Found Problem Details", + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + }, + "example": { + "type": "about:blank", + "title": "Resource Not Found", + "status": 404, + "detail": "The requested resource was not found." + } + } + } + }, + "ConflictProblemDetailsResponse": { + "description": "RFC7807 Conflict Problem Details", + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + }, + "example": { + "type": "about:blank", + "title": "Conflict", + "status": 409, + "detail": "The resource already exists." + } + } + } + }, + "DefaultProblemDetailsResponse": { + "description": "RFC7807 Default Problem Details", + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + }, + "example": { + "type": "about:blank", + "title": "An error occurred", + "status": 500 + } + } + } + } } } } \ No newline at end of file diff --git a/tests/Unit/DependencyInjection/ConfigurationTest.php b/tests/Unit/DependencyInjection/ConfigurationTest.php index 7ab0620..556c821 100644 --- a/tests/Unit/DependencyInjection/ConfigurationTest.php +++ b/tests/Unit/DependencyInjection/ConfigurationTest.php @@ -34,6 +34,9 @@ public function testDefaultConfig(): void 'enabled' => true, 'groups' => ['Default'], ], + 'openapi' => [ + 'problem_details' => true, + ], ]; self::assertSame($expected, $config); } @@ -54,6 +57,15 @@ public function testCustomConfig(): void $config = $processor->processConfiguration($configuration, [$customConfig]); // Assert - self::assertSame($customConfig, $config); + $expected = [ + 'validation' => [ + 'enabled' => false, + 'groups' => ['Custom', 'Special'], + ], + 'openapi' => [ + 'problem_details' => true, + ], + ]; + self::assertSame($expected, $config); } } From 34c527f7ef218e7740108397166b64d70114ee91 Mon Sep 17 00:00:00 2001 From: Jelle van Oosterbosch Date: Sun, 1 Mar 2026 21:55:23 +0100 Subject: [PATCH 2/4] Applied CodeRabbit feedback --- src/Model/ProblemDetails.php | 2 +- .../nelmio_problem_details.yaml | 4 ++- .../ProblemDetailsConfigurationTest.php | 4 +++ .../Resources/specifications/openapi.json | 25 ++++++++++++++----- 4 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/Model/ProblemDetails.php b/src/Model/ProblemDetails.php index 495ae60..23c500a 100644 --- a/src/Model/ProblemDetails.php +++ b/src/Model/ProblemDetails.php @@ -28,7 +28,7 @@ class ProblemDetails { public function __construct( - #[OA\Property(description: 'A URI reference [RFC3986] that identifies the problem type.', format: 'url', default: 'about:blank')] + #[OA\Property(description: 'A URI reference [RFC3986] that identifies the problem type.', format: 'uri-reference', default: 'about:blank')] #[Assert\NotBlank] public string $type = 'about:blank', #[OA\Property(description: 'A short, human-readable summary of the problem type.', type: 'string', default: 'An error occurred')] diff --git a/src/Resources/specifications/nelmio_problem_details.yaml b/src/Resources/specifications/nelmio_problem_details.yaml index 56a9285..92bce9a 100644 --- a/src/Resources/specifications/nelmio_problem_details.yaml +++ b/src/Resources/specifications/nelmio_problem_details.yaml @@ -32,8 +32,10 @@ nelmio_api_doc: status: 400 detail: 'Body does not match schema' violations: - - constraint: 'openapi_request_validation' + - propertyPath: 'body' message: 'Body does not match schema for content-type "application/json" for Request [post /api/example]' + code: 'openapi_request_validation' + constraint: 'openapi_request_validation' UnauthorizedProblemDetailsResponse: description: RFC7807 Unauthorized Problem Details content: diff --git a/tests/Functional/ProblemDetailsConfigurationTest.php b/tests/Functional/ProblemDetailsConfigurationTest.php index 4205ec2..d379731 100644 --- a/tests/Functional/ProblemDetailsConfigurationTest.php +++ b/tests/Functional/ProblemDetailsConfigurationTest.php @@ -27,6 +27,8 @@ public function testProblemDetailsAreLoadedByDefault(): void /** @var ApiDocGenerator $generator */ $generator = $container->get('nelmio_api_doc.generator.default'); + + // Act /** @var array{components?: array{responses?: array, schemas?: array}} $spec */ $spec = json_decode($generator->generate()->toJson(), true); @@ -64,6 +66,7 @@ public function testProblemDetailsCanBeDisabled(): void $generator = $container->get('nelmio_api_doc.generator.default'); $prevErrorHandler = set_error_handler(static fn () => true); + // Act /** @var array{components?: array{responses?: array}} $spec */ $spec = json_decode($generator->generate()->toJson(), true); set_error_handler($prevErrorHandler); @@ -88,6 +91,7 @@ public function testProblemDetailsCanBeOverriddenByProjectConfig(): void $generator = $container->get('nelmio_api_doc.generator.default'); $prevErrorHandler = set_error_handler(static fn () => true); + // Act /** @var array{components?: array{responses?: array}} $spec */ $spec = json_decode($generator->generate()->toJson(), true); set_error_handler($prevErrorHandler); diff --git a/tests/Functional/Resources/specifications/openapi.json b/tests/Functional/Resources/specifications/openapi.json index d448ed9..3da2bfc 100644 --- a/tests/Functional/Resources/specifications/openapi.json +++ b/tests/Functional/Resources/specifications/openapi.json @@ -125,7 +125,7 @@ "type": { "description": "A URI reference [RFC3986] that identifies the problem type.", "type": "string", - "format": "url", + "format": "uri-reference", "default": "about:blank" }, "title": { @@ -193,6 +193,7 @@ "properties": { "violations": { "type": "array", + "maxItems": 100, "items": { "$ref": "#/components/schemas/Violation" } @@ -200,7 +201,7 @@ "type": { "description": "A URI reference [RFC3986] that identifies the problem type.", "type": "string", - "format": "url", + "format": "uri-reference", "default": "about:blank" }, "title": { @@ -229,8 +230,18 @@ }, "type": "object", "example": { + "type": "about:blank", "title": "The request body contains errors.", - "type": "about:blank" + "status": 400, + "detail": "Validation failed.", + "violations": [ + { + "propertyPath": "foo", + "message": "This value should not be blank.", + "code": "c1ac8c7d-eab5-458f-a950-fcf121d23059", + "constraint": "NotBlank" + } + ] } }, "BookRequest": { @@ -323,8 +334,10 @@ "detail": "Body does not match schema", "violations": [ { - "constraint": "openapi_request_validation", - "message": "Body does not match schema for content-type \"application/json\" for Request [post /api/example]" + "propertyPath": "body", + "message": "Body does not match schema for content-type \"application/json\" for Request [post /api/example]", + "code": "openapi_request_validation", + "constraint": "openapi_request_validation" } ] } @@ -414,4 +427,4 @@ } } } -} \ No newline at end of file +} From a785c3f80c8c5563d35cb97d6071e93d18840ed3 Mon Sep 17 00:00:00 2001 From: Jelle van Oosterbosch Date: Sun, 1 Mar 2026 22:08:13 +0100 Subject: [PATCH 3/4] Added example to ProblemDetailsInvalidRequestBody and fixed test --- .../ProblemDetailsInvalidRequestBody.php | 20 ++++++++++++++----- .../ProblemDetailsConfigurationTest.php | 8 ++++---- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/Model/ProblemDetailsInvalidRequestBody.php b/src/Model/ProblemDetailsInvalidRequestBody.php index 3456998..0410b73 100644 --- a/src/Model/ProblemDetailsInvalidRequestBody.php +++ b/src/Model/ProblemDetailsInvalidRequestBody.php @@ -19,12 +19,22 @@ #[OA\Schema( title: 'InvalidRequestBody', properties: [ - new OA\Property(property: 'violations', type: 'array', items: new OA\Items(ref: new Model(type: Violation::class))), + new OA\Property(property: 'violations', type: 'array', items: new OA\Items(ref: new Model(type: Violation::class)), maxItems: 100), ], - example: new OA\Schema( - title: 'The request body contains errors.', - type: 'about:blank', - ) + example: [ + 'type' => 'about:blank', + 'title' => 'The request body contains errors.', + 'status' => 400, + 'detail' => 'Validation failed.', + 'violations' => [ + [ + 'propertyPath' => 'foo', + 'message' => 'This value should not be blank.', + 'code' => 'c1ac8c7d-eab5-458f-a950-fcf121d23059', + 'constraint' => 'NotBlank', + ], + ], + ] )] final class ProblemDetailsInvalidRequestBody extends ProblemDetails { diff --git a/tests/Functional/ProblemDetailsConfigurationTest.php b/tests/Functional/ProblemDetailsConfigurationTest.php index d379731..baad4e2 100644 --- a/tests/Functional/ProblemDetailsConfigurationTest.php +++ b/tests/Functional/ProblemDetailsConfigurationTest.php @@ -65,11 +65,11 @@ public function testProblemDetailsCanBeDisabled(): void /** @var ApiDocGenerator $generator */ $generator = $container->get('nelmio_api_doc.generator.default'); - $prevErrorHandler = set_error_handler(static fn () => true); + set_error_handler(static fn () => true); // Act /** @var array{components?: array{responses?: array}} $spec */ $spec = json_decode($generator->generate()->toJson(), true); - set_error_handler($prevErrorHandler); + restore_error_handler(); // Assert $this->assertIsArray($spec); @@ -90,11 +90,11 @@ public function testProblemDetailsCanBeOverriddenByProjectConfig(): void /** @var ApiDocGenerator $generator */ $generator = $container->get('nelmio_api_doc.generator.default'); - $prevErrorHandler = set_error_handler(static fn () => true); + set_error_handler(static fn () => true); // Act /** @var array{components?: array{responses?: array}} $spec */ $spec = json_decode($generator->generate()->toJson(), true); - set_error_handler($prevErrorHandler); + restore_error_handler(); // Assert $this->assertIsArray($spec); From 26b664eb834ea3f6b2ba15d848863f4b146ea032 Mon Sep 17 00:00:00 2001 From: Jelle van Oosterbosch Date: Sun, 1 Mar 2026 22:12:29 +0100 Subject: [PATCH 4/4] Let CodeRabbit automatically approve pull-requests --- .coderabbit.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.coderabbit.yaml b/.coderabbit.yaml index 2762405..2b17a49 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -1,7 +1,7 @@ language: "en" reviews: profile: "chill" - request_changes_workflow: false + request_changes_workflow: true high_level_summary: true collapse_walkthrough: false auto_review: