Let me start saying i'm new to Api Platform and atm i find it really hard to find all the documentation needed to make something work.
That said, my example problem is quite simple, i want a public endpoint where i can fetch the current mobile app version number (which is simply a string saved somewhere).
What i did until now (based on what i've found online/documentation/demo/symfonycast) is that i created a new PHP object inside my /Entity folder and named it "PublicApi".
use Ramsey\Uuid\Uuid;
#[ApiResource(
collectionOperations: [],
itemOperations: [
'version' => [
'method' => 'GET',
'read' => false,
'write' => false,
'path' => '/version',
'controller' => ApiPublicVersionAction::class,
"openapi_context" => [
"summary" => "Returns current app version.",
"description" => "Public endpoint, no need to authenticate",
'parameters' => [],
'requestBody' => [],
],
],
],
routePrefix: '/public'
)]
class PublicApi {
#[ApiProperty( identifier: true )]
public $code;
#[ApiProperty]
private string $exposedData;
public function __construct() {
$this->code = Uuid::uuid4();
}
doing that i can see the route being recognized by api platform, it shows up in the doc and i can test it. The controller is quite simple, it just has an __invoke method
<?php
namespace App\Controller\Api;
use App\Entity\PublicApi;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class ApiPublicVersionAction extends AbstractController {
public function __invoke() {
return ( new PublicApi() )->setExposedData( '1.0.0' );
}
}
While this actually "works" i find it really confusing and i miss some pieces of information. I'd like to know the following:
- Is there another way of creating a custom public endpoint or i am forced to create a new controller class with a single method (__invoke) for EVERY endpoint?
- Can't i just have a single controller for all the endpoint and make api-platform call the corrisponding method? that would be so much more clear and easy to maintain
- What are the actual options that i can pass to: collectionOperations, itemOperations or openapi_context? I can't really find a good list with some good documentation of what i can do
- Why am i forced to have an identifier even in this case where i don't really want to fetch anything and i just want to return some non-structured data? The code i've made requires me to pass a random string (which is the "code" property value that api platform uses as identifier) when i don't really need it
- I've read about DataProvider but in this case i don't really need one, in fact i tested creating a very simple one that simply supports PublicApi::class and it gives me no real advantage. Only one more class/file to maintain
CodePudding user response:
- Is there another way of creating a custom public endpoint or i am forced to create a new controller class with a single method (__invoke) for EVERY endpoint?
The problem with your operation is, that it doesn’t really work as an item operation as api-platform understands it, because an item needs to be identified and you don’t have an identifier. This messes with some of the internals of api-platform, which are hidden away in request listeners, e.g. for fetching an item from the database with the identifier. You could work around this by using a collection-get endpoint instead. I would recommend having a regular controller outside of api-platform, but then you will need to manually update the api-docs for this endpoint to show up in your docs, which is a bit annoying.
- Can't i just have a single controller for all the endpoint and make api-platform call the corrisponding method? that would be so much more clear and easy to maintain
Yes, you can. This is how api-platform works by default. There is a generic PlaceholderAction which doesn’t really do anything. All the logic of api-platform is tucked away in request listeners. Ideally, you don’t have to write controllers at all and instead use one of the many extension points that api-platform provides such as DataProviders, DataPersisters and so on. If you have a specific need, feel free to open a new question. Listing them all otherwise is out of scope.
- What are the actual options that i can pass to: collectionOperations, itemOperations or openapi_context? I can't really find a good list with some good documentation of what i can do
There is a docs page with more details on the operations: https://api-platform.com/docs/core/operations/#operations
Figuring out what context you can pass can be a bit annoying. There are both some defaults from Symfony, e.g. Serializer context options and validation groups. Additionally, api-platform adds its own context options. I don’t know of a good overview, so I recommend looking at the listeners you are interested in and see in the code what options they support, even if its not very satisfying. Maybe you can suggest a doc update for this similar to the full configuration reference that already exists.
- Why am i forced to have an identifier even in this case where i don't really want to fetch anything and i just want to return some non-structured data? The code i've made requires me to pass a random string (which is the "code" property value that api platform uses as identifier) when i don't really need it
An item needs an identifier, but collections don’t. You need it because api-platform adheres very strictly to the standards surrounding it, which says that a single resource needs an identifier. There was some discussions around it already and if this conflicts how you want to design an api, then api-platform is probably not the right choice for you. There are alternatives in the Symfony space, e.g. FosRestBundle.
- I've read about DataProvider but in this case i don't really need one, in fact i tested creating a very simple one that simply supports PublicApi::class and it gives me no real advantage. Only one more class/file to maintain
Yes, I agree. For your version problem DataProvider seems overkill, but the underlying problem is, that the version is not really a resource and api-platform is very resource-centric. As mentioned in point 1. writing a generic controller which is not part of api-platform, but a standard Symfony controller might be a better solution.
Alternatively, you can have a Configuration-object, which has a (string) identifier, e.g. what client you want to retrieve the configuration for and then store the config, including version, in the database. Then it will “magically” fit with how api-platform approaches things, because you have an identifier you can use the default data providers and deserialization just works by virtue of each config entry being a field in the database.