As you can read in the General Design Considerations, just make an ordinary PHP class (POPO). Give it an ApiResource annontation like this:
* @ApiResource(
* collectionOperations={
* "post"
* },
* itemOperations={}
* )
Make sure the folder your class is in is in the paths list in api/config/packages/api_platform.yaml. There usually is the following configuration:
api_platform:
mapping:
paths: ['%kernel.project_dir%/src/Entity']
You should add your path if your class is not in the Entity folder.
Api Platform will expect json to be posted and try to unserialize it into an instance of your class. Make a custom DataPersister to process the instance, for example if your class is AppApiCommandDoit:
namespace AppDataPersister;
use ApiPlatformCoreDataPersisterContextAwareDataPersisterInterface;
use AppApiCommandDoit;
use AppApiResultDoitResult;
final class DoitDataPersister implements ContextAwareDataPersisterInterface
{
public function supports($data, array $context = []): bool
{
return $data instanceof Doit;
}
public function persist($data, array $context = [])
{
// code to process $data
$result = new DoitResult();
$result->description = 'Hello world';
return $result;
}
public function remove($data, array $context = [])
{
// will not be called if you have no delete operation
}
}
If you need Doctrine, add:
public function __construct(ManagerRegistry $managerRegistry)
{
$this->managerRegistry = $managerRegistry;
}
See Injecting Extensions for how to use it.
Notice that the result returned by ::persist is not an instance of Doit. If you return a Doit api platform will try to serialize that as the result of your operation. But we have marked Doit as an ApiResource so (?) api platform looks for an item operation that can retrieve it, resulting in an error "No item route associated with the type AppApiCommandDoit". To avoid this you can return any object that Symfonies serializer can serialize that is not an ApiResource. In the example an instance of DoitResult. Alternatively you can return an instance of SymfonyComponentHttpFoundationResponse but then you have to take care of the serialization yourself.
The post operation should already work, but the swagger docs are made from metadata. To tell api platform that it should expect a DoitResult to be returned, change the @ApiResource annotation:
* collectionOperations={
* "post"={
* "output"=DoitResult::class
* }
* },
This will the add a new type for DoitResult to the swagger docs, but the descriptions are still wrong. You can correct them using a SwaggerDecorator. Here is one for a 201 post response:
namespace AppSwagger;
use SymfonyComponentSerializerNormalizerNormalizerInterface;
final class SwaggerDecorator implements NormalizerInterface
{
private $decorated;
public function __construct(NormalizerInterface $decorated)
{
$this->decorated = $decorated;
}
public function normalize($object, string $format = null, array $context = [])
{
$summary = 'short explanation about DoitResult';
$docs = $this->decorated->normalize($object, $format, $context);
$docs['paths']['/doit']['post']['responses']['201']['description'] = 'Additional explanation about DoitResult';
$responseContent = $docs['paths']['/doit']['post']['responses']['201']['content'];
$this->setByRef($docs, $responseContent['application/ld+json']['schema']['properties']['hydra:member']['items']['$ref'],
'description', $summary);
$this->setByRef($docs, $responseContent['application/json']['schema']['items']['$ref'],
'description', $summary);
return $docs;
}
public function supportsNormalization($data, string $format = null)
{
return $this->decorated->supportsNormalization($data, $format);
}
private function setByRef(&$docs, $ref, $key, $value)
{
$pieces = explode('/', substr($ref, 2));
$sub =& $docs;
foreach ($pieces as $piece) {
$sub =& $sub[$piece];
}
$sub[$key] = $value;
}
}
To configure the service add the following to api/config/services.yaml:
'AppSwaggerSwaggerDecorator':
decorates: 'api_platform.swagger.normalizer.api_gateway'
arguments: [ '@AppSwaggerSwaggerDecorator.inner' ]
autoconfigure: false
If your post operation is not actually creating something you may not like the 201 response. You can change that by specifying the response code in the @ApiResource annotation, for example:
* collectionOperations={
* "post"={
* "output"=DoitResult::class,
* "status"=200
* }
* },
You may want to adapt the SwaggerDecorator accordingly.
Creating a "get" collection operation is similar, but you need to make a DataProvider instead of a DataPersister. The chapter9-api branch of my tutorial contains an example of a SwaggerDecorator for a collection response.