documentation-developer icon indicating copy to clipboard operation
documentation-developer copied to clipboard

IBX-8190: Update REST new resource

Open adriendupuis opened this issue 8 months ago • 3 comments

Question Answer
JIRA Ticket IBX-8190
Versions
Edition

  • Use serialiser/normalizer/denormalizer instead of ValueObjectVisitor/InputParser.
  • Add GET /greet and POST /greet to schema/doc (/api/ibexa/v2/doc#/App/api_greet_get).

Preview: Creating new REST resource

Checklist

  • [ ] Text renders correctly
  • [ ] Text has been checked with vale
  • [ ] Description metadata is up to date
  • [ ] Redirects cover removed/moved pages
  • [ ] Code samples are working
  • [ ] PHP code samples have been fixed with PHP CS fixer
  • [ ] Added link to this PR in relevant JIRA ticket or code PR

adriendupuis avatar Mar 27 '25 08:03 adriendupuis

Preview of modified files

Preview of modified Markdown:

github-actions[bot] avatar Jul 22 '25 10:07 github-actions[bot]

code_samples/ change report

Before (on target branch)After (in current PR)

code_samples/api/rest_api/config/routes_rest.yaml

docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@29:``` yaml
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@30:[[= include_file('code_samples/api/rest_api/config/routes_rest.yaml', 0, 3) =]] methods: [GET]
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@31:```

001⫶app.rest.greeting:
002⫶ path: '/greet'

code_samples/api/rest_api/config/routes_rest.yaml

docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@29:``` yaml
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@30:[[= include_file('code_samples/api/rest_api/config/routes_rest.yaml', 0, 3) =]] methods: [GET]
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@31:```

001⫶app.rest.greeting:
002⫶ path: '/greet'
003⫶    controller: App\Rest\Controller\DefaultController::helloWorld
003⫶    controller: App\Rest\Controller\DefaultController::greet
004⫶    methods: [GET]

docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@38:``` yaml
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@39:[[= include_file('code_samples/api/rest_api/config/routes_rest.yaml') =]]
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@40:```

001⫶app.rest.greeting:
002⫶ path: '/greet'
004⫶    methods: [GET]

docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@38:``` yaml
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@39:[[= include_file('code_samples/api/rest_api/config/routes_rest.yaml') =]]
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@40:```

001⫶app.rest.greeting:
002⫶ path: '/greet'
003⫶    controller: App\Rest\Controller\DefaultController::helloWorld
003⫶    controller: App\Rest\Controller\DefaultController::greet
004⫶    methods: [GET,POST]
005⫶ defaults:
006⫶ csrf_protection: false


code_samples/api/rest_api/config/services.yaml

docs/api/rest_api/extending_rest_api/adding_custom_media_type.md@41:``` yaml
docs/api/rest_api/extending_rest_api/adding_custom_media_type.md@42:services:
docs/api/rest_api/extending_rest_api/adding_custom_media_type.md@43: #…
docs/api/rest_api/extending_rest_api/adding_custom_media_type.md@44:[[= include_file('code_samples/api/rest_api/config/services.yaml', 28, 35) =]]
docs/api/rest_api/extending_rest_api/adding_custom_media_type.md@45:```

001⫶services:
002⫶ #…
003⫶ App\Rest\ValueObjectVisitor\RestLocation:
004⫶ class: App\Rest\ValueObjectVisitor\RestLocation
005⫶ parent: Ibexa\Contracts\Rest\Output\ValueObjectVisitor
006⫶ arguments:
007⫶ $urlAliasService: '@ibexa.api.service.url_alias'
008⫶ tags:
009⫶ - { name: app.rest.output.value_object.visitor, type: Ibexa\Rest\Server\Values\RestLocation }

docs/api/rest_api/extending_rest_api/adding_custom_media_type.md@52:``` yaml
docs/api/rest_api/extending_rest_api/adding_custom_media_type.md@53:services:
docs/api/rest_api/extending_rest_api/adding_custom_media_type.md@54: #…
docs/api/rest_api/extending_rest_api/adding_custom_media_type.md@55:[[= include_file('code_samples/api/rest_api/config/services.yaml', 22, 27) =]]
docs/api/rest_api/extending_rest_api/adding_custom_media_type.md@56:```

001⫶services:
002⫶ #…
003⫶ App\Rest\Output\ValueObjectVisitorDispatcher:
004⫶ class: App\Rest\Output\ValueObjectVisitorDispatcher
005⫶ arguments:
006⫶ - !tagged_iterator { tag: 'app.rest.output.value_object.visitor', index_by: 'type' }
007⫶ - '@Ibexa\Contracts\Rest\Output\ValueObjectVisitorDispatcher'

docs/api/rest_api/extending_rest_api/adding_custom_media_type.md@67:``` yaml
docs/api/rest_api/extending_rest_api/adding_custom_media_type.md@68:parameters:
docs/api/rest_api/extending_rest_api/adding_custom_media_type.md@69: #…
docs/api/rest_api/extending_rest_api/adding_custom_media_type.md@70:[[= include_file('code_samples/api/rest_api/config/services.yaml', 1, 3) =]]
docs/api/rest_api/extending_rest_api/adding_custom_media_type.md@71:services:
docs/api/rest_api/extending_rest_api/adding_custom_media_type.md@72: #…
docs/api/rest_api/extending_rest_api/adding_custom_media_type.md@73:[[= include_file('code_samples/api/rest_api/config/services.yaml', 6, 21) =]]
docs/api/rest_api/extending_rest_api/adding_custom_media_type.md@74:```

001⫶parameters:
002⫶ #…
003⫶ app.rest.output.visitor.xml.regexps: ['(^application/app\.api\.[A-Za-z]+\+xml$)']
004⫶ app.rest.output.visitor.json.regexps: ['(^application/app\.api\.[A-Za-z]+\+json$)']
005⫶
006⫶services:
007⫶ #…
008⫶ app.rest.output.visitor.xml:
009⫶ class: Ibexa\Contracts\Rest\Output\Visitor
010⫶ arguments:
011⫶ - '@Ibexa\Rest\Output\Generator\Xml'
012⫶ - '@App\Rest\Output\ValueObjectVisitorDispatcher'
013⫶ tags:
014⫶ - { name: ibexa.rest.output.visitor, regexps: app.rest.output.visitor.xml.regexps, priority: 20 }
015⫶
016⫶ app.rest.output.visitor.json:
017⫶ class: Ibexa\Contracts\Rest\Output\Visitor
018⫶ arguments:
019⫶ - '@Ibexa\Rest\Output\Generator\Json'
020⫶ - '@App\Rest\Output\ValueObjectVisitorDispatcher'
021⫶ tags:
022⫶ - { name: ibexa.rest.output.visitor, regexps: app.rest.output.visitor.json.regexps, priority: 20 }

docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@48:``` yaml
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@49:services:
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@50: #…
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@51:[[= include_file('code_samples/api/rest_api/config/services.yaml', 36, 42) =]]
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@52:```

001⫶services:
002⫶ #…
003⫶ App\Rest\Controller\:
004⫶ resource: '../src/Rest/Controller/'
005⫶ parent: Ibexa\Rest\Server\Controller
006⫶ autowire: true
007⫶ autoconfigure: true
004⫶    methods: [GET,POST]
005⫶ defaults:
006⫶ csrf_protection: false


code_samples/api/rest_api/config/services.yaml

docs/api/rest_api/extending_rest_api/adding_custom_media_type.md@41:``` yaml
docs/api/rest_api/extending_rest_api/adding_custom_media_type.md@42:services:
docs/api/rest_api/extending_rest_api/adding_custom_media_type.md@43: #…
docs/api/rest_api/extending_rest_api/adding_custom_media_type.md@44:[[= include_file('code_samples/api/rest_api/config/services.yaml', 28, 35) =]]
docs/api/rest_api/extending_rest_api/adding_custom_media_type.md@45:```

001⫶services:
002⫶ #…
003⫶ App\Rest\ValueObjectVisitor\RestLocation:
004⫶ class: App\Rest\ValueObjectVisitor\RestLocation
005⫶ parent: Ibexa\Contracts\Rest\Output\ValueObjectVisitor
006⫶ arguments:
007⫶ $urlAliasService: '@ibexa.api.service.url_alias'
008⫶ tags:
009⫶ - { name: app.rest.output.value_object.visitor, type: Ibexa\Rest\Server\Values\RestLocation }

docs/api/rest_api/extending_rest_api/adding_custom_media_type.md@52:``` yaml
docs/api/rest_api/extending_rest_api/adding_custom_media_type.md@53:services:
docs/api/rest_api/extending_rest_api/adding_custom_media_type.md@54: #…
docs/api/rest_api/extending_rest_api/adding_custom_media_type.md@55:[[= include_file('code_samples/api/rest_api/config/services.yaml', 22, 27) =]]
docs/api/rest_api/extending_rest_api/adding_custom_media_type.md@56:```

001⫶services:
002⫶ #…
003⫶ App\Rest\Output\ValueObjectVisitorDispatcher:
004⫶ class: App\Rest\Output\ValueObjectVisitorDispatcher
005⫶ arguments:
006⫶ - !tagged_iterator { tag: 'app.rest.output.value_object.visitor', index_by: 'type' }
007⫶ - '@Ibexa\Contracts\Rest\Output\ValueObjectVisitorDispatcher'

docs/api/rest_api/extending_rest_api/adding_custom_media_type.md@67:``` yaml
docs/api/rest_api/extending_rest_api/adding_custom_media_type.md@68:parameters:
docs/api/rest_api/extending_rest_api/adding_custom_media_type.md@69: #…
docs/api/rest_api/extending_rest_api/adding_custom_media_type.md@70:[[= include_file('code_samples/api/rest_api/config/services.yaml', 1, 3) =]]
docs/api/rest_api/extending_rest_api/adding_custom_media_type.md@71:services:
docs/api/rest_api/extending_rest_api/adding_custom_media_type.md@72: #…
docs/api/rest_api/extending_rest_api/adding_custom_media_type.md@73:[[= include_file('code_samples/api/rest_api/config/services.yaml', 6, 21) =]]
docs/api/rest_api/extending_rest_api/adding_custom_media_type.md@74:```

001⫶parameters:
002⫶ #…
003⫶ app.rest.output.visitor.xml.regexps: ['(^application/app\.api\.[A-Za-z]+\+xml$)']
004⫶ app.rest.output.visitor.json.regexps: ['(^application/app\.api\.[A-Za-z]+\+json$)']
005⫶
006⫶services:
007⫶ #…
008⫶ app.rest.output.visitor.xml:
009⫶ class: Ibexa\Contracts\Rest\Output\Visitor
010⫶ arguments:
011⫶ - '@Ibexa\Rest\Output\Generator\Xml'
012⫶ - '@App\Rest\Output\ValueObjectVisitorDispatcher'
013⫶ tags:
014⫶ - { name: ibexa.rest.output.visitor, regexps: app.rest.output.visitor.xml.regexps, priority: 20 }
015⫶
016⫶ app.rest.output.visitor.json:
017⫶ class: Ibexa\Contracts\Rest\Output\Visitor
018⫶ arguments:
019⫶ - '@Ibexa\Rest\Output\Generator\Json'
020⫶ - '@App\Rest\Output\ValueObjectVisitorDispatcher'
021⫶ tags:
022⫶ - { name: ibexa.rest.output.visitor, regexps: app.rest.output.visitor.json.regexps, priority: 20 }

docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@48:``` yaml
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@49:services:
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@50: #…
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@51:[[= include_file('code_samples/api/rest_api/config/services.yaml', 36, 42) =]]
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@52:```

001⫶services:
002⫶ #…
003⫶ App\Rest\Controller\:
004⫶ resource: '../src/Rest/Controller/'
005⫶ parent: Ibexa\Rest\Server\Controller
006⫶ autowire: true
007⫶ autoconfigure: true
008⫶        tags: [ 'controller.service_arguments' ]
008⫶        tags: ['controller.service_arguments', 'ibexa.api_platform.resource']


docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@98:``` yaml
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@99:services:
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@100: #…
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@101:[[= include_file('code_samples/api/rest_api/config/services.yaml', 43, 48) =]]
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@102:```
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@95:``` yaml
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@96:services:
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@97: #…
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@98:[[= include_file('code_samples/api/rest_api/config/services.yaml', 43, 48) =]]
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@99:```

001⫶services:
002⫶ #…

001⫶services:
002⫶ #…
003⫶    App\Rest\ValueObjectVisitor\Greeting:
004⫶ parent: Ibexa\Contracts\Rest\Output\ValueObjectVisitor
005⫶ tags:
006⫶ - { name: ibexa.rest.output.value_object.visitor, type: App\Rest\Values\Greeting }

docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@120:``` yaml
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@121:services:
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@122: #…
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@123:[[= include_file('code_samples/api/rest_api/config/services.yaml', 48, 53) =]]
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@124:```

001⫶services:
002⫶ #…
003⫶ App\Rest\InputParser\GreetingInput:
004⫶ parent: Ibexa\Rest\Server\Common\Parser
005⫶ tags:
006⫶ - { name: ibexa.rest.input.parser, mediaType: application/vnd.ibexa.api.GreetingInput }
003⫶
004⫶ App\Rest\Serializer\:
005⫶ resource: '../src/Rest/Serializer/'
006⫶ tags: ['ibexa.rest.serializer.normalizer']


code_samples/api/rest_api/src/Rest/Controller/DefaultController.php



code_samples/api/rest_api/src/Rest/Controller/DefaultController.php

docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@63:``` php
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@64:[[= include_file('code_samples/api/rest_api/src/Rest/Controller/DefaultController.php') =]]
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@65:```
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@68:``` php
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@69:[[= include_file('code_samples/api/rest_api/src/Rest/Controller/DefaultController.php', 0, 14) =]]
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@70:[[= include_file('code_samples/api/rest_api/src/Rest/Controller/DefaultController.php', 214) =]]
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@71:```

001⫶<?php declare(strict_types=1);
002⫶
003⫶namespace App\Rest\Controller;
004⫶

001⫶<?php declare(strict_types=1);
002⫶
003⫶namespace App\Rest\Controller;
004⫶
005⫶use App\Rest\Values\Greeting;
006⫶use Ibexa\Rest\Message;
007⫶use Ibexa\Rest\Server\Controller;
008⫶use Symfony\Component\HttpFoundation\Request;
009⫶
010⫶class DefaultController extends Controller
011⫶{
012⫶ public function greet(Request $request): Greeting
013⫶ {
014⫶ if ('POST' === $request->getMethod()) {
015⫶ return $this->inputDispatcher->parse(
016⫶ new Message(
017⫶ ['Content-Type' => $request->headers->get('Content-Type')],
018⫶ $request->getContent()
019⫶ )
020⫶ );
021⫶ }
022⫶
023⫶ return new Greeting();
024⫶ }
025⫶}
005⫶use ApiPlatform\Metadata\Get;
006⫶use ApiPlatform\Metadata\Post;
007⫶use ApiPlatform\OpenApi\Factory\OpenApiFactory;
008⫶use ApiPlatform\OpenApi\Model;
009⫶use App\Rest\Values\Greeting;
010⫶use Ibexa\Rest\Server\Controller;
011⫶use Symfony\Component\HttpFoundation\Request;
012⫶use Symfony\Component\HttpFoundation\Response;
013⫶use Symfony\Component\Serializer\Encoder\XmlEncoder;
014⫶use Symfony\Component\Serializer\SerializerInterface;

docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@171:``` php hl_lines="5 6 16 89"
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@172:[[= include_file('code_samples/api/rest_api/src/Rest/Controller/DefaultController.php', 0, 215) =]]//…
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@173:```

001⫶<?php declare(strict_types=1);
002⫶
003⫶namespace App\Rest\Controller;
004⫶
005❇️use ApiPlatform\Metadata\Get;
006❇️use ApiPlatform\Metadata\Post;
007⫶use ApiPlatform\OpenApi\Factory\OpenApiFactory;
008⫶use ApiPlatform\OpenApi\Model;
009⫶use App\Rest\Values\Greeting;
010⫶use Ibexa\Rest\Server\Controller;
011⫶use Symfony\Component\HttpFoundation\Request;
012⫶use Symfony\Component\HttpFoundation\Response;
013⫶use Symfony\Component\Serializer\Encoder\XmlEncoder;
014⫶use Symfony\Component\Serializer\SerializerInterface;
015⫶
016❇️#[Get(
017⫶ uriTemplate: '/greet',
018⫶ extraProperties: [OpenApiFactory::OVERRIDE_OPENAPI_RESPONSES => false],
019⫶ openapi: new Model\Operation(
020⫶ summary: 'Greet',
021⫶ description: 'Greets a recipient with a salutation',
022⫶ tags: [
023⫶ 'App',
024⫶ ],
025⫶ parameters: [],
026⫶ responses: [
027⫶ Response::HTTP_OK => [
028⫶ 'description' => 'OK - Return a greeting',
029⫶ 'content' => [
030⫶ 'application/vnd.ibexa.api.Greeting+xml' => [
031⫶ 'schema' => [
032⫶ 'xml' => [
033⫶ 'name' => 'Greeting',
034⫶ 'wrapped' => false,
035⫶ ],
036⫶ 'properties' => [
037⫶ 'salutation' => [
038⫶ 'type' => 'string',
039⫶ ],
040⫶ 'recipient' => [
041⫶ 'type' => 'string',
042⫶ ],
043⫶ 'sentence' => [
044⫶ 'type' => 'string',
045⫶ 'description' => 'Composed sentence using salutation and recipient.',
046⫶ ],
047⫶ ],
048⫶ ],
049⫶ 'example' => [
050⫶ 'salutation' => 'Hello',
051⫶ 'recipient' => 'World',
052⫶ 'sentence' => 'Hello World',
053⫶ ],
054⫶ ],
055⫶ 'application/vnd.ibexa.api.Greeting+json' => [
056⫶ 'schema' => [
057⫶ 'type' => 'object',
058⫶ 'properties' => [
059⫶ 'Greeting' => [
060⫶ 'type' => 'object',
061⫶ 'properties' => [
062⫶ 'salutation' => [
063⫶ 'type' => 'string',
064⫶ ],
065⫶ 'recipient' => [
066⫶ 'type' => 'string',
067⫶ ],
068⫶ 'sentence' => [
069⫶ 'type' => 'string',
070⫶ 'description' => 'Composed sentence using salutation and recipient.',
071⫶ ],
072⫶ ],
073⫶ ],
074⫶ ],
075⫶ ],
076⫶ 'example' => [
077⫶ 'Greeting' => [
078⫶ 'salutation' => 'Hello',
079⫶ 'recipient' => 'World',
080⫶ 'sentence' => 'Hello World',
081⫶ ],
082⫶ ],
083⫶ ],
084⫶ ],
085⫶ ],
086⫶ ],
087⫶ ),
088⫶)]
089❇️#[Post(
090⫶ uriTemplate: '/greet',
091⫶ extraProperties: [OpenApiFactory::OVERRIDE_OPENAPI_RESPONSES => false],
092⫶ openapi: new Model\Operation(
093⫶ summary: 'Greet',
094⫶ description: 'Greets a recipient with a salutation',
095⫶ tags: [
096⫶ 'App',
097⫶ ],
098⫶ parameters: [],
099⫶ requestBody: new Model\RequestBody(
100⫶ description: 'Set salutation or recipient.',
101⫶ required: false,
102⫶ content: new \ArrayObject([
103⫶ 'application/vnd.ibexa.api.GreetingInput+xml' => [
104⫶ 'schema' => [
105⫶ 'type' => 'object',
106⫶ 'xml' => [
107⫶ 'name' => 'GreetingInput',
108⫶ 'wrapped' => false,
109⫶ ],
110⫶ 'required' => [],
111⫶ 'properties' => [
112⫶ 'salutation' => [
113⫶ 'type' => 'string',
114⫶ ],
115⫶ 'recipient' => [
116⫶ 'type' => 'string',
117⫶ ],
118⫶ ],
119⫶ ],
120⫶ 'example' => [
121⫶ 'salutation' => 'Good morning',
122⫶ 'recipient' => 'Planet',
123⫶ ],
124⫶ ],
125⫶ 'application/vnd.ibexa.api.GreetingInput+json' => [
126⫶ 'schema' => [
127⫶ 'type' => 'object',
128⫶ 'properties' => [
129⫶ 'GreetingInput' => [
130⫶ 'type' => 'object',
131⫶ 'required' => [],
132⫶ 'properties' => [
133⫶ 'salutation' => [
134⫶ 'type' => 'string',
135⫶ ],
136⫶ 'recipient' => [
137⫶ 'type' => 'string',
138⫶ ],
139⫶ ],
140⫶ ],
141⫶ ],
142⫶ ],
143⫶ 'example' => [
144⫶ 'GreetingInput' => [
145⫶ 'salutation' => 'Good day',
146⫶ 'recipient' => 'Earth',
147⫶ ],
148⫶ ],
149⫶ ],
150⫶ ]),
151⫶ ),
152⫶ responses: [
153⫶ Response::HTTP_OK => [
154⫶ 'description' => 'OK - Return a greeting',
155⫶ 'content' => [
156⫶ 'application/vnd.ibexa.api.Greeting+xml' => [
157⫶ 'schema' => [
158⫶ 'xml' => [
159⫶ 'name' => 'Greeting',
160⫶ 'wrapped' => false,
161⫶ ],
162⫶ 'properties' => [
163⫶ 'salutation' => [
164⫶ 'type' => 'string',
165⫶ ],
166⫶ 'recipient' => [
167⫶ 'type' => 'string',
168⫶ ],
169⫶ 'sentence' => [
170⫶ 'type' => 'string',
171⫶ 'description' => 'Composed sentence using salutation and recipient.',
172⫶ ],
173⫶ ],
174⫶ ],
175⫶ 'example' => [
176⫶ 'salutation' => 'Good morning',
177⫶ 'recipient' => 'World',
178⫶ 'sentence' => 'Good Morning World',
179⫶ ],
180⫶ ],
181⫶ 'application/vnd.ibexa.api.Greeting+json' => [
182⫶ 'schema' => [
183⫶ 'type' => 'object',
184⫶ 'properties' => [
185⫶ 'Greeting' => [
186⫶ 'type' => 'object',
187⫶ 'properties' => [
188⫶ 'salutation' => [
189⫶ 'type' => 'string',
190⫶ ],
191⫶ 'recipient' => [
192⫶ 'type' => 'string',
193⫶ ],
194⫶ 'sentence' => [
195⫶ 'type' => 'string',
196⫶ 'description' => 'Composed sentence using salutation and recipient.',
197⫶ ],
198⫶ ],
199⫶ ],
200⫶ ],
201⫶ ],
202⫶ 'example' => [
203⫶ 'Greeting' => [
204⫶ 'salutation' => 'Good day',
205⫶ 'recipient' => 'Earth',
206⫶ 'sentence' => 'Good day Earth',
207⫶ ],
208⫶ ],
209⫶ ],
210⫶ ],
211⫶ ],
212⫶ ],
213⫶ ),
214⫶)]
215⫶class DefaultController extends Controller
216⫶//…


code_samples/api/rest_api/src/Rest/InputParser/GreetingInput.php



code_samples/api/rest_api/src/Rest/InputParser/GreetingInput.php


code_samples/api/rest_api/src/Rest/Serializer/GreetingInputDenormalizer.php

docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@113:``` php
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@113:``` php
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@114:[[= include_file('code_samples/api/rest_api/src/Rest/InputParser/GreetingInput.php') =]]
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@114:[[= include_file('code_samples/api/rest_api/src/Rest/Serializer/GreetingInputDenormalizer.php') =]]
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@115:```

001⫶<?php declare(strict_types=1);
002⫶
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@115:```

001⫶<?php declare(strict_types=1);
002⫶
003⫶namespace App\Rest\InputParser;
003⫶namespace App\Rest\Serializer;
004⫶
005⫶use App\Rest\Values\Greeting;
004⫶
005⫶use App\Rest\Values\Greeting;
006⫶use Ibexa\Contracts\Rest\Exceptions;
007⫶use Ibexa\Contracts\Rest\Input\ParsingDispatcher;
008⫶use Ibexa\Rest\Input\BaseParser;
006⫶use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
007⫶use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait;
008⫶use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
009⫶
009⫶
010⫶class GreetingInput extends BaseParser
010⫶class GreetingInputDenormalizer implements DenormalizerInterface, DenormalizerAwareInterface
011⫶{
011⫶{
012⫶    public function parse(array $data, ParsingDispatcher $parsingDispatcher): Greeting
013⫶ {
014⫶ if (!isset($data['Salutation'])) {
015⫶ throw new Exceptions\Parser("Missing or invalid 'Salutation' element for Greeting.");
016⫶ }
017⫶
018⫶ return new Greeting($data['Salutation'], $data['Recipient'] ?? 'World');
019⫶ }
020⫶}


code_samples/api/rest_api/src/Rest/Serializer/GreetingInputDenormalizer.php
012⫶    use DenormalizerAwareTrait;
013⫶
014⫶ public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed
015⫶ {
016⫶ if ('json' === $format) {
017⫶ $data = $data[array_key_first($data)];
018⫶ }
019⫶ $data = array_change_key_case($data);
020⫶
021⫶ $salutation = $data['salutation'] ?? 'Hello';
022⫶ $recipient = $data['recipient'] ?? 'World';
023⫶
024⫶ return new Greeting($salutation, $recipient);
025⫶ }
026⫶
027⫶ public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool
028⫶ {
029⫶ if (!is_array($data)) {
030⫶ return false;
031⫶ }
032⫶
033⫶ if ('json' === $format) {
034⫶ $data = $data[array_key_first($data)];
035⫶ }
036⫶ $data = array_change_key_case($data);
037⫶
038⫶ return in_array($type, $this->getSupportedTypes($format), true) &&
039⫶ (array_key_exists('salutation', $data) || array_key_exists('recipient', $data));
040⫶ }
041⫶
042⫶ public function getSupportedTypes(?string $format): array
043⫶ {
044⫶ return [
045⫶ Greeting::class => true,
046⫶ ];
047⫶ }
048⫶}


code_samples/api/rest_api/src/Rest/Serializer/GreetingNormalizer.php



code_samples/api/rest_api/src/Rest/Serializer/GreetingNormalizer.php


code_samples/api/rest_api/src/Rest/ValueObjectVisitor/Greeting.php

docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@92:``` php
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@93:[[= include_file('code_samples/api/rest_api/src/Rest/ValueObjectVisitor/Greeting.php') =]]
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@94:```
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@103:``` php
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@104:[[= include_file('code_samples/api/rest_api/src/Rest/Serializer/GreetingNormalizer.php') =]]
docs/api/rest_api/extending_rest_api/creating_new_rest_resource.md@105:```

001⫶<?php declare(strict_types=1);
002⫶

001⫶<?php declare(strict_types=1);
002⫶
003⫶namespace App\Rest\ValueObjectVisitor;
003⫶namespace App\Rest\Serializer;
004⫶
004⫶
005⫶use Ibexa\Contracts\Rest\Output\Generator;
006⫶use Ibexa\Contracts\Rest\Output\ValueObjectVisitor;
007⫶use Ibexa\Contracts\Rest\Output\Visitor;
008⫶
009⫶class Greeting extends ValueObjectVisitor
010⫶{
011⫶ public function visit(Visitor $visitor, Generator $generator, $data)
012⫶ {
013⫶ $visitor->setHeader('Content-Type', $generator->getMediaType('Greeting'));
014⫶ $generator->startObjectElement('Greeting');
015⫶ $generator->attribute('href', $this->router->generate('app.rest.greeting'));
016⫶ $generator->valueElement('Salutation', $data->salutation);
017⫶ $generator->valueElement('Recipient', $data->recipient);
018⫶ $generator->valueElement('Sentence', "{$data->salutation} {$data->recipient}");
019⫶ $generator->endObjectElement('Greeting');
020⫶ }
021⫶}
005⫶use App\Rest\Values\Greeting;
006⫶use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
007⫶use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
008⫶use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
009⫶
010⫶class GreetingNormalizer implements NormalizerInterface, NormalizerAwareInterface
011⫶{
012⫶ use NormalizerAwareTrait;
013⫶
014⫶ public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool
015⫶ {
016⫶ return $data instanceof Greeting;
017⫶ }
018⫶
019⫶ /** @param \App\Rest\Values\Greeting $object */
020⫶ public function normalize(mixed $object, ?string $format = null, array $context = []): array|\ArrayObject|bool|float|int|null|string
021⫶ {
022⫶ $data = [
023⫶ 'Salutation' => $object->salutation,
024⫶ 'Recipient' => $object->recipient,
025⫶ 'Sentence' => "{$object->salutation} {$object->recipient}",
026⫶ ];
027⫶ if ('json' === $format) {
028⫶ $data = ['Greeting' => $data];
029⫶ }
030⫶
031⫶ return $this->normalizer->normalize($data, $format, $context);
032⫶ }
033⫶
034⫶ public function getSupportedTypes(?string $format): array
035⫶ {
036⫶ return [
037⫶ Greeting::class => true,
038⫶ ];
039⫶ }
040⫶}


code_samples/api/rest_api/src/Rest/ValueObjectVisitor/Greeting.php


Download colorized diff

github-actions[bot] avatar Aug 08 '25 12:08 github-actions[bot]