Skip to content

IBX-8190: Update REST custom media type #2688

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 deletions code_samples/api/rest_api/config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php declare(strict_types=1);

namespace App\Rest\Output;

use Ibexa\Contracts\Rest\Output\ValueObjectVisitor;
use Ibexa\Contracts\Rest\Output\ValueObjectVisitorResolverInterface;

class ValueObjectVisitorResolver implements ValueObjectVisitorResolverInterface
{
/** @var array<string, ValueObjectVisitor> */
private array $visitors;

private ValueObjectVisitorResolverInterface $valueObjectVisitorResolver;

/** @param iterable<string, ValueObjectVisitor> $visitors */
public function __construct(iterable $visitors, ValueObjectVisitorResolverInterface $resolver)
{
$this->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);
}
}
22 changes: 11 additions & 11 deletions docs/api/rest_api/extending_rest_api/adding_custom_media_type.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand Down
54 changes: 0 additions & 54 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading