From c22d1a55f2327a7830fdfa73b8fe162f80ea3aba Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Tue, 1 Apr 2025 16:50:55 +0200 Subject: [PATCH 1/4] adding_custom_media_type: mv to Resolver Move to ValueObjectVisitorDispatcher to ValueObjectVisitorResolver --- .../api/rest_api/config/services.yaml | 21 +++++--- .../Output/ValueObjectVisitorDispatcher.php | 49 ------------------- .../Output/ValueObjectVisitorResolver.php | 32 ++++++++++++ .../adding_custom_media_type.md | 22 ++++----- 4 files changed, 56 insertions(+), 68 deletions(-) delete mode 100644 code_samples/api/rest_api/src/Rest/Output/ValueObjectVisitorDispatcher.php create mode 100644 code_samples/api/rest_api/src/Rest/Output/ValueObjectVisitorResolver.php diff --git a/code_samples/api/rest_api/config/services.yaml b/code_samples/api/rest_api/config/services.yaml index e8c592606f..8de7c36281 100644 --- a/code_samples/api/rest_api/config/services.yaml +++ b/code_samples/api/rest_api/config/services.yaml @@ -7,24 +7,29 @@ services: app.rest.output.visitor.xml: class: Ibexa\Contracts\Rest\Output\Visitor arguments: - - '@Ibexa\Rest\Output\Generator\Xml' - - '@App\Rest\Output\ValueObjectVisitorDispatcher' + $generator: '@Ibexa\Rest\Output\Generator\Xml' + $normalizer: '@ibexa.rest.serializer' + $encoder: '@ibexa.rest.serializer.encoder.xml' + $valueObjectVisitorResolver: '@App\Rest\Output\ValueObjectVisitorResolver' + $format: 'xml' tags: - { name: ibexa.rest.output.visitor, regexps: app.rest.output.visitor.xml.regexps, priority: 20 } app.rest.output.visitor.json: class: Ibexa\Contracts\Rest\Output\Visitor arguments: - - '@Ibexa\Rest\Output\Generator\Json' - - '@App\Rest\Output\ValueObjectVisitorDispatcher' + $generator: '@Ibexa\Rest\Output\Generator\Json' + $normalizer: '@ibexa.rest.serializer' + $encoder: '@ibexa.rest.serializer.encoder.json' + $valueObjectVisitorResolver: '@App\Rest\Output\ValueObjectVisitorResolver' + $format: 'json' tags: - { name: ibexa.rest.output.visitor, regexps: app.rest.output.visitor.json.regexps, priority: 20 } - App\Rest\Output\ValueObjectVisitorDispatcher: - class: App\Rest\Output\ValueObjectVisitorDispatcher + App\Rest\Output\ValueObjectVisitorResolver: arguments: - - !tagged_iterator { tag: 'app.rest.output.value_object.visitor', index_by: 'type' } - - '@Ibexa\Contracts\Rest\Output\ValueObjectVisitorDispatcher' + $visitors: !tagged_iterator { tag: 'app.rest.output.value_object.visitor', index_by: 'type' } + $resolver: '@Ibexa\Contracts\Rest\Output\ValueObjectVisitorResolver' App\Rest\ValueObjectVisitor\RestLocation: class: App\Rest\ValueObjectVisitor\RestLocation diff --git a/code_samples/api/rest_api/src/Rest/Output/ValueObjectVisitorDispatcher.php b/code_samples/api/rest_api/src/Rest/Output/ValueObjectVisitorDispatcher.php deleted file mode 100644 index 39782164fc..0000000000 --- a/code_samples/api/rest_api/src/Rest/Output/ValueObjectVisitorDispatcher.php +++ /dev/null @@ -1,49 +0,0 @@ -visitors = []; - foreach ($visitors as $type => $visitor) { - $this->visitors[$type] = $visitor; - } - $this->valueObjectVisitorDispatcher = $valueObjectVisitorDispatcher; - } - - public function setOutputVisitor(Visitor $outputVisitor): void - { - $this->outputVisitor = $outputVisitor; - $this->valueObjectVisitorDispatcher->setOutputVisitor($outputVisitor); - } - - public function setOutputGenerator(Generator $outputGenerator): void - { - $this->outputGenerator = $outputGenerator; - $this->valueObjectVisitorDispatcher->setOutputGenerator($outputGenerator); - } - - public function visit($data) - { - $className = get_class($data); - if (isset($this->visitors[$className])) { - return $this->visitors[$className]->visit($this->outputVisitor, $this->outputGenerator, $data); - } - - return $this->valueObjectVisitorDispatcher->visit($data); - } -} diff --git a/code_samples/api/rest_api/src/Rest/Output/ValueObjectVisitorResolver.php b/code_samples/api/rest_api/src/Rest/Output/ValueObjectVisitorResolver.php new file mode 100644 index 0000000000..6e95b91adb --- /dev/null +++ b/code_samples/api/rest_api/src/Rest/Output/ValueObjectVisitorResolver.php @@ -0,0 +1,32 @@ +visitors = []; + foreach ($visitors as $type => $visitor) { + $this->visitors[$type] = $visitor; + } + $this->valueObjectVisitorResolver = $resolver; + } + + public function resolveValueObjectVisitor(object $object): ?ValueObjectVisitor + { + $className = get_class($object); + if (isset($this->visitors[$className])) { + return $this->visitors[$className]; + } + + return $this->valueObjectVisitorResolver->resolveValueObjectVisitor($object); + } +} diff --git a/docs/api/rest_api/extending_rest_api/adding_custom_media_type.md b/docs/api/rest_api/extending_rest_api/adding_custom_media_type.md index 0715112198..89a26b1d41 100644 --- a/docs/api/rest_api/extending_rest_api/adding_custom_media_type.md +++ b/docs/api/rest_api/extending_rest_api/adding_custom_media_type.md @@ -12,13 +12,13 @@ The following example adds the handling of a new media type `application/app.api You need the following elements: - `ValueObjectVisitor` - to create the new response corresponding to the new media type -- `ValueObjectVisitorDispatcher` - to have this `ValueObjectVisitor` used to visit the default controller result -- `Output\Visitor` - service associating this new `ValueObjectVisitorDispatcher` with the new media type +- `ValueObjectVisitorResolver` - to have this `ValueObjectVisitor` used to visit the default controller result +- `Output\Visitor` - service associating this new `ValueObjectVisitorResolver` with the new media type !!! note You can change the vendor name (from default `vnd.ibexa.api` to new `app.api` like in this example), or you can create a new media type in the default vendor (like `vnd.ibexa.api.Greeting` in the [Creating a new REST resource](creating_new_rest_resource.md) example). - To do so, tag your new ValueObjectVisitor with `ibexa.rest.output.value_object.visitor` to add it to the existing `ValueObjectVisitorDispatcher`, and a new one isn't needed. + To do so, tag your new `ValueObjectVisitor` with `ibexa.rest.output.value_object.visitor` to add it to the existing `ValueObjectVisitorResolver`, and a new one isn't needed. This way, the `media-type` attribute is also easier to create, because the default `Output\Generator` uses this default vendor. This example presents creating a new vendor as a good practice, to highlight that this is custom extensions that isn't available in a regular [[= product_name =]] installation. @@ -41,27 +41,27 @@ This tag has a `type` property to associate the new `ValueObjectVisitor` with th ``` yaml services: #… -[[= include_file('code_samples/api/rest_api/config/services.yaml', 28, 35) =]] +[[= include_file('code_samples/api/rest_api/config/services.yaml', 33, 41) =]] ``` -## New `ValueObjectVisitorDispatcher` +## New `ValueObjectVisitorResolver` -The new `ValueObjectVisitorDispatcher` receives the `ValueObjectVisitor`s tagged `app.rest.output.value_object.visitor`. -As not all value FQCNs are handled, the new `ValueObjectVisitorDispatcher` also receives the default one as a fallback. +The new `ValueObjectVisitorResolver` receives the `ValueObjectVisitor`s tagged `app.rest.output.value_object.visitor`. +As not all value FQCNs are handled, the new `ValueObjectVisitorResolver` also receives the default one as a fallback. ``` yaml services: #… -[[= include_file('code_samples/api/rest_api/config/services.yaml', 22, 27) =]] +[[= include_file('code_samples/api/rest_api/config/services.yaml', 28, 32) =]] ``` ``` php -[[= include_file('code_samples/api/rest_api/src/Rest/Output/ValueObjectVisitorDispatcher.php') =]] +[[= include_file('code_samples/api/rest_api/src/Rest/Output/ValueObjectVisitorResolver.php') =]] ``` ## New `Output\Visitor` service -The following new pair of `Ouput\Visitor` entries associates `Accept` headers starting with `application/app.api.` to the new `ValueObjectVisitorDispatcher` for both XML and JSON. +The following new pair of `Ouput\Visitor` entries associates `Accept` headers starting with `application/app.api.` to the new `ValueObjectVisitorResolver` for both XML and JSON. A priority is set higher than other `ibexa.rest.output.visitor` tagged built-in services. ``` yaml @@ -70,7 +70,7 @@ parameters: [[= include_file('code_samples/api/rest_api/config/services.yaml', 1, 3) =]] services: #… -[[= include_file('code_samples/api/rest_api/config/services.yaml', 6, 21) =]] +[[= include_file('code_samples/api/rest_api/config/services.yaml', 6, 27) =]] ``` ## Testing the new media-type From 59003d431554c67707a11c572629514b97cde222 Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Tue, 1 Apr 2025 17:04:03 +0200 Subject: [PATCH 2/4] phpstan-baseline.neon: Remove ValueObjectVisitorDispatcher errors --- phpstan-baseline.neon | 54 ------------------------------------------- 1 file changed, 54 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index ab3610cd9e..68fc7ea1e3 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -150,60 +150,6 @@ parameters: count: 1 path: code_samples/api/rest_api/create_image.xml.php - - - message: '#^Call to method setOutputGenerator\(\) on an unknown class Ibexa\\Contracts\\Rest\\Output\\ValueObjectVisitorDispatcher\.$#' - identifier: class.notFound - count: 1 - path: code_samples/api/rest_api/src/Rest/Output/ValueObjectVisitorDispatcher.php - - - - message: '#^Call to method setOutputVisitor\(\) on an unknown class Ibexa\\Contracts\\Rest\\Output\\ValueObjectVisitorDispatcher\.$#' - identifier: class.notFound - count: 1 - path: code_samples/api/rest_api/src/Rest/Output/ValueObjectVisitorDispatcher.php - - - - message: '#^Call to method visit\(\) on an unknown class Ibexa\\Contracts\\Rest\\Output\\ValueObjectVisitorDispatcher\.$#' - identifier: class.notFound - count: 1 - path: code_samples/api/rest_api/src/Rest/Output/ValueObjectVisitorDispatcher.php - - - - message: '#^Method App\\Rest\\Output\\ValueObjectVisitorDispatcher\:\:__construct\(\) has parameter \$visitors with no value type specified in iterable type iterable\.$#' - identifier: missingType.iterableValue - count: 1 - path: code_samples/api/rest_api/src/Rest/Output/ValueObjectVisitorDispatcher.php - - - - message: '#^Method App\\Rest\\Output\\ValueObjectVisitorDispatcher\:\:visit\(\) has no return type specified\.$#' - identifier: missingType.return - count: 1 - path: code_samples/api/rest_api/src/Rest/Output/ValueObjectVisitorDispatcher.php - - - - message: '#^Method App\\Rest\\Output\\ValueObjectVisitorDispatcher\:\:visit\(\) has parameter \$data with no type specified\.$#' - identifier: missingType.parameter - count: 1 - path: code_samples/api/rest_api/src/Rest/Output/ValueObjectVisitorDispatcher.php - - - - message: '#^Parameter \$valueObjectVisitorDispatcher of method App\\Rest\\Output\\ValueObjectVisitorDispatcher\:\:__construct\(\) has invalid type Ibexa\\Contracts\\Rest\\Output\\ValueObjectVisitorDispatcher\.$#' - identifier: class.notFound - count: 1 - path: code_samples/api/rest_api/src/Rest/Output/ValueObjectVisitorDispatcher.php - - - - message: '#^Property App\\Rest\\Output\\ValueObjectVisitorDispatcher\:\:\$valueObjectVisitorDispatcher has unknown class Ibexa\\Contracts\\Rest\\Output\\ValueObjectVisitorDispatcher as its type\.$#' - identifier: class.notFound - count: 1 - path: code_samples/api/rest_api/src/Rest/Output/ValueObjectVisitorDispatcher.php - - - - message: '#^Property App\\Rest\\Output\\ValueObjectVisitorDispatcher\:\:\$visitors type has no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: code_samples/api/rest_api/src/Rest/Output/ValueObjectVisitorDispatcher.php - - message: '#^Method App\\Rest\\ValueObjectVisitor\\Greeting\:\:visit\(\) has no return type specified\.$#' identifier: missingType.return From d1a0290aba6d63d38c09e6a8665a96a0b2eb826d Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Tue, 1 Apr 2025 17:10:47 +0200 Subject: [PATCH 3/4] ValueObjectVisitorResolver.php: Type hinting Property App\Rest\Output\ValueObjectVisitorResolver::$visitors type has no value type specified in iterable type array. Method App\Rest\Output\ValueObjectVisitorResolver::__construct() has parameter $visitors with no value type specified in iterable type iterable. --- .../api/rest_api/src/Rest/Output/ValueObjectVisitorResolver.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/code_samples/api/rest_api/src/Rest/Output/ValueObjectVisitorResolver.php b/code_samples/api/rest_api/src/Rest/Output/ValueObjectVisitorResolver.php index 6e95b91adb..b5e9e6bd78 100644 --- a/code_samples/api/rest_api/src/Rest/Output/ValueObjectVisitorResolver.php +++ b/code_samples/api/rest_api/src/Rest/Output/ValueObjectVisitorResolver.php @@ -7,10 +7,12 @@ class ValueObjectVisitorResolver implements ValueObjectVisitorResolverInterface { + /** @var array */ private array $visitors; private ValueObjectVisitorResolverInterface $valueObjectVisitorResolver; + /** @param iterable $visitors */ public function __construct(iterable $visitors, ValueObjectVisitorResolverInterface $resolver) { $this->visitors = []; From edbbb0320249fbaff6a6a4232bcef6fa39026b74 Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Tue, 1 Apr 2025 17:18:35 +0200 Subject: [PATCH 4/4] creating_new_rest_resource.md: Update/Offset includes --- .../extending_rest_api/creating_new_rest_resource.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md b/docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md index d057cb05ad..8307c0eabe 100644 --- a/docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md +++ b/docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md @@ -48,7 +48,7 @@ You can use the following configuration to have all controllers from the `App\Re ``` yaml services: #… -[[= include_file('code_samples/api/rest_api/config/services.yaml', 36, 42) =]] +[[= include_file('code_samples/api/rest_api/config/services.yaml', 41, 47) =]] ``` Having the REST controllers set as services enables using features such as the `InputDispatcher` service in the [Controller action](#controller-action). @@ -98,7 +98,7 @@ The `Values/Greeting` class is linked to its `ValueObjectVisitor` through the se ``` yaml services: #… -[[= include_file('code_samples/api/rest_api/config/services.yaml', 43, 48) =]] +[[= include_file('code_samples/api/rest_api/config/services.yaml', 48, 53) =]] ``` Here, the media type is `application/vnd.ibexa.api.Greeting` plus a format. @@ -120,7 +120,7 @@ In other cases, it could return whatever object is needed to represent the input ``` yaml services: #… -[[= include_file('code_samples/api/rest_api/config/services.yaml', 48, 53) =]] +[[= include_file('code_samples/api/rest_api/config/services.yaml', 53, 58) =]] ``` ## Testing the new resource