Skip to content

Commit 991543f

Browse files
author
Jérôme Deuchnord
committed
Add doc about exposing models without any routes.
1 parent 83acd6d commit 991543f

File tree

1 file changed

+189
-0
lines changed

1 file changed

+189
-0
lines changed

core/operations.md

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -953,3 +953,192 @@ book_post_publication:
953953
_api_resource_class: App\Entity\Book
954954
_api_item_operation_name: post_publication
955955
```
956+
957+
## Expose a model without any routes
958+
959+
Sometimes, you may want to expose a model, but want it to be used through subrequests only, and never through item or collection operations.
960+
Because the OpenAPI standard requires at least one route to be exposed to make your models consumable, let's see how you can manage this kind
961+
of issue.
962+
963+
Let's say you have the following entities in your project:
964+
965+
```php
966+
<?php
967+
// src/Entity/Place.php
968+
969+
namespace App\Entity;
970+
971+
use Doctrine\ORM\Mapping as ORM;
972+
973+
/**
974+
* @ORM\Entity
975+
*/
976+
class Place
977+
{
978+
/**
979+
* @var int
980+
*
981+
* @ORM\Id
982+
* @ORM\GeneratedValue()
983+
* @ORM\Column(type="integer")
984+
*/
985+
private $id;
986+
987+
/**
988+
* @var string
989+
*
990+
* Column(type="string")
991+
*/
992+
private $name;
993+
994+
/**
995+
* @var float
996+
*
997+
* Column(type="float")
998+
*/
999+
private $latitude;
1000+
1001+
/**
1002+
* @var float
1003+
*
1004+
* Column(type="float")
1005+
*/
1006+
private $longitude;
1007+
1008+
// ...
1009+
}
1010+
```
1011+
1012+
```php
1013+
<?php
1014+
// src/Entity/Weather.php
1015+
1016+
namespace App\Entity;
1017+
1018+
class Weather
1019+
{
1020+
/**
1021+
* @var float
1022+
*/
1023+
private $temperature;
1024+
1025+
/**
1026+
* @var float
1027+
*/
1028+
private $pressure;
1029+
1030+
// ...
1031+
}
1032+
```
1033+
1034+
We don't save the `Weather` entity in the database, since we want to return the weather in real time when it is queried.
1035+
Because we want to get the weather for a known place, it is more reasonable to query it through a subresource of the `Place` entity, so let's do this:
1036+
1037+
1038+
```php
1039+
<?php
1040+
// src/Entity/Place.php
1041+
1042+
namespace App\Entity;
1043+
1044+
use App\Controller\GetWeather;
1045+
use Doctrine\ORM\Mapping as ORM;
1046+
1047+
/**
1048+
* @ORM\Entity
1049+
*
1050+
* @ApiResource(
1051+
* itemOperations={
1052+
* "get",
1053+
* "put",
1054+
* "delete",
1055+
* "get_weather": {
1056+
* "method": "GET",
1057+
* "path": "/places/{id}/weather",
1058+
* "controller": GetWeather::class
1059+
* }
1060+
* }, collectionOperations={"get", "post"})
1061+
*/
1062+
class Place
1063+
{
1064+
// ...
1065+
```
1066+
1067+
The `GetWeather` controller fetches the weather for the given city and returns an instance of the `Weather` entity.
1068+
This implies that API Platform has to know about this entity, so we will need to make it an API resource too:
1069+
1070+
1071+
```php
1072+
<?php
1073+
// src/Entity/Weather.php
1074+
1075+
namespace App\Entity;
1076+
1077+
/**
1078+
* @ApiResource
1079+
*/
1080+
class Weather
1081+
{
1082+
// ...
1083+
```
1084+
1085+
This will expose the `Weather` model, but also all the default CRUD routes: `GET`, `PUT`, `DELETE` and `POST`, which is a non-sense in our context.
1086+
Since we are required to expose at least one route, let's expose just one and disable its output:
1087+
1088+
1089+
```php
1090+
<?php
1091+
// src/Entity/Weather.php
1092+
1093+
namespace App\Entity;
1094+
1095+
/**
1096+
* @ApiResource(itemOperations={
1097+
* "get": {
1098+
* "method": "GET",
1099+
* "controller": SomeRandomController::class
1100+
* }
1101+
* })
1102+
*/
1103+
class Weather
1104+
{
1105+
// ...
1106+
```
1107+
1108+
This way, we expose a route that will do… nothing. Note that the controller does not even need to exist.
1109+
1110+
It's almost done, we have just one final issue: our fake item operation is visible in the API docs.
1111+
To remove it, we will need to [decorate the Swagger documentation](/docs/core/swagger/#overriding-the-openapi-specification).
1112+
Then, remove the route from the decorator:
1113+
1114+
```php
1115+
<?php
1116+
// src/Swagger/SwaggerDecorator.php
1117+
1118+
namespace App\Swagger;
1119+
1120+
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
1121+
1122+
final class SwaggerDecorator implements NormalizerInterface
1123+
{
1124+
private $decorated;
1125+
1126+
public function __construct(NormalizerInterface $decorated)
1127+
{
1128+
$this->decorated = $decorated;
1129+
}
1130+
1131+
public function normalize($object, $format = null, array $context = [])
1132+
{
1133+
$docs = $this->decorated->normalize($object, $format, $context);
1134+
1135+
// If a prefix is configured on API Platform's routes, it must appear here.
1136+
unset($docs['paths']['/weathers/{id}']);
1137+
1138+
return $docs;
1139+
}
1140+
1141+
// ...
1142+
```
1143+
1144+
That's it: your route is gone!

0 commit comments

Comments
 (0)