Skip to content

Commit 0439ec1

Browse files
authored
Merge pull request #864 from Deuchnord/routeless-model
Exposing models without any routes.
2 parents ab18579 + 958b658 commit 0439ec1

File tree

1 file changed

+194
-0
lines changed

1 file changed

+194
-0
lines changed

core/operations.md

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

0 commit comments

Comments
 (0)