Home > Blockchain >  Api platform - public custom api
Api platform - public custom api

Time:09-15

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:

  1. 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?
  2. 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
  3. 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
  4. 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
  5. 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:

  1. 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.

  1. 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.

  1. 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.

  1. 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.

  1. 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.

  • Related