0

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
Diego
  • 1,610
  • 1
  • 14
  • 26

1 Answers1

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

dbrumann
  • 16,803
  • 2
  • 42
  • 58
  • Wow, i'm impressed by the response, thank you a lot just for spending your time answering, very appreciated. I've read about the listener and even digged into the code to a point where i was about to write a listener to the symfony kernel in order to override the default __invoke issue. There's a point where i could just change the FQN of the controller to a callable and make Api Platform use that but it seems an overkill. I'm puzzled about doing the 2 way thing of keeping some things attached to Api-platform and some other to symfony controller, leave alone the documentation (continues) – Diego Sep 15 '22 at 10:45
  • I would like to have the docs up to date because it's kinda useful and one of the reason i approached api-platform. I just feel like the documentation is not precise, their demo uses v3-rc while the latest is 2.6.8 so i can't even watch inside that. – Diego Sep 15 '22 at 10:49
  • If it helps, you are not alone with this. I encounter this quite often in client projects and I haven’t found something fully satisfying yet. Regarding the options, I did a talk at SymfonyCon a few months ago. I think its behind a paywall, but feel free to send me an email and I can at least send you the transcript next week. It helps a bit with the extension points, but is unlikely to solve all your irks with api-platform. – dbrumann Sep 15 '22 at 10:51
  • You can add these custom controllers to the docs automatically as well: https://api-platform.com/docs/core/openapi/#overriding-the-openapi-specification – dbrumann Sep 15 '22 at 10:52
  • I've read that openApi specification, i've found this too: https://spec.openapis.org/oas/v3.1.0#operation-object to have an idea of what i can pass as configuration, in some way it helped a bit. Some things are still obscure to me, i managed to make an itemOperation docs not requiring the identifier (moving them inside collectionOperation was giving me some other headache, this was the fastest way i've found to obtain the result i want) – Diego Sep 15 '22 at 10:56