Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Operations / Endpoints

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

API Platform works by taking a class like DragonTreasure and saying that you want to expose it as a resource in your API. We do that by adding the ApiResource attribute:

... lines 1 - 10
#[ApiResource(
description: 'A rare and valuable treasure.'
)]
class DragonTreasure
{
... lines 16 - 117
}

Right now, we're putting this above a Doctrine entity, though, in a future tutorial, we'll learn that you can really put ApiResource above any class.

Hello Operations

Out-of-the-box, every ApiResource includes 6 endpoints, which API Platform calls operations. You can actually see these in the profiler. This is the profiler for GET /api/dragon_treasures.json. Click on the "API Platform" section. On top, we see metadata for this API resource. Below, we see the operations. This... is more info than we need right now, but there's Get, GetCollection, Post, Put, Patch and finally Delete. These are the same things we see on the Swagger documentation.

Let's take a quick look at these. First, which operations return data? Actually, all of them - except for Delete. This Get, the Post, Put and Patch endpoints all return a single resource - so a single treasure. And GET /api/dragon_treasures returns a collection.

Which endpoints do we send data to when we use them? That's POST to create, and PUT and PATCH to update. We don't send any data for DELETE or either GET operation.

PUT vs PATCH

Most of the endpoints are pretty self-explanatory: get a collection of treasures, a single treasure, create a treasure and delete a treasure. The only confusing ones are put versus patch. PUT says "replaces" and PATCH says "updates". That... sounds like two ways of saying the same thing!

Tip

In API Platform 4, PUT will become a "replace": meaning if you only sent a single field, all of the other fields in your resource will be set to null: your object is completely "replaced" by the JSON you send. Starting in API Platform 3.1, you can "opt into" this new behavior by adding an extraProperties option to every ApiResource:

#[ApiResource(
    // ...

    extraProperties: [
        'standard_put' => true,
    ],
)]#

The topic of PUT versus PATCH in APIs can get spicy. But in API Platform, at least today, PUT and PATCH work the same: they're both used to update a resource. And we'll see them in action along the way.

Customizing Operations

One of the things that you might want to do is customize or remove some of these operations... or even add more operations. How could we do that? As we saw on the profiler, each operation is backed by a class.

Back over above the DragonTreasure class, after description, add an operations key. Notice that I'm getting auto-completion for the options because these are named arguments to the constructor of the ApiResource class. I'll show you that in a minute.

Set this to an array and then repeat every operation we currently have. So, new Get(), hit tab to auto-complete that, GetCollection, Post, Put, Patch and Delete.

... lines 1 - 5
use ApiPlatform\Metadata\Delete;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Put;
... lines 12 - 16
#[ApiResource(
description: 'A rare and valuable treasure.',
operations: [
new Get(),
new GetCollection(),
new Post(),
new Put(),
new Patch(),
new Delete(),
]
)]
class DragonTreasure
{
... lines 30 - 131
}

Now, if we move over to the Swagger documentation and refresh... absolutely nothing changes! That's what we wanted. We've just repeated exactly the default configuration. But now we're free to customize things. For example, suppose we don't want treasures to be deleted... because a dragon would never allow their treasure to be stolen. Remove Delete.. and I'll even remove the use statement.

... lines 1 - 5
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Put;
... lines 11 - 15
#[ApiResource(
description: 'A rare and valuable treasure.',
operations: [
new Get(),
new GetCollection(),
new Post(),
new Put(),
new Patch(),
]
)]
class DragonTreasure
{
... lines 28 - 129
}

Now when we refresh, the DELETE operation is gone.

ApiResource Options

Ok, so every attribute we use is actually a class. And knowing that is powerful. Hold command or control and click on ApiResource to open it. This is really cool. Every argument to the constructor is an option that we can pass to the attribute. And almost all of these have a link to the documentation where you can read more. We'll talk about the most important items, but this is a great resource to know about.

Changing the shortName

One argument is called shortName. If you look over at Swagger, our "model" is currently known as DragonTreasure, which obviously matches the class. This is called the "short name". And by default, the URLs - /api/dragon_treasures - are generated from that.

Let's say that we instead want to shorten all of this to just "treasure". No problem: set shortName to Treasure.

... lines 1 - 15
#[ApiResource(
shortName: 'Treasure',
... lines 18 - 25
)]
class DragonTreasure
{
... lines 29 - 130
}

As soon as we do that, watch the name and URLs. Nice. This resource is now known as "Treasure" and the URLs updated to reflect that.

Operation Options

Though, that's not the only way to configure the URLs. Just like with ApiResource, each operation is also a class. Hold Command (or Ctrl) and Click to open up the Get class. Once again, these constructor arguments are options... and most have documentation.

One important argument is uriTemplate. Yup, we can control what the URL looks like on an operation by operation basis.

Check it out. Remember, Get is how you fetch a single resource. Add uriTemplate set to /dragon-plunder/{id} where that last part will be the placeholder for the dynamic id. For GetCollection, let's also pass uriTemplate set to /dragon-plunder.

... lines 1 - 15
#[ApiResource(
... lines 17 - 18
operations: [
new Get(uriTemplate: '/dragon-plunder/{id}'),
new GetCollection(uriTemplate: '/dragon-plunder'),
... lines 22 - 24
]
)]
class DragonTreasure
{
... lines 29 - 130
}

Ok! Let's go check the docs! Beautiful! The other operations keep the old URL, but those use the new style. Later, when we talk about subresources, we'll go deeper into uriTemplate and its sister option uriVariables.

Ok... since it's a bit silly to have two operations with weird URLs, let's remove that customization.

... lines 1 - 15
#[ApiResource(
... lines 17 - 18
operations: [
new Get(),
new GetCollection(),
... lines 22 - 24
]
)]
class DragonTreasure
{
... lines 29 - 130
}

Now that we know a bunch about ApiResource and these operations, it's time to talk about the heart of API Platform: Symfony's serializer. That's next.

Leave a comment!

2
Login or Register to join the conversation
EinzigTech Avatar
EinzigTech Avatar EinzigTech | posted 6 months ago

Hi Ryan,

I am unable to execute PUT/PATCH operations on my server (Error 405 Method not allowed). I am using Fastpanel where Nginx is running for the frontend. Tried some fixes from the internet but that didn't help. Any help would be really appreciated.

Best regards,
Stiff

Reply

Hey Stiff!

Hmm, well that's no fun :/. First, are you sure that the 405 Method not allowed is coming from Nginx? I mean, it probably is, but it could also, in theory, be coming from something like CloudFlare (if it is, it should be pretty obvious on that page) or Symfony itself (if it is, you would likely be seeing either your custom exception page or the generic "Symfony error page" for production).

Assuming that it IS coming from nginx, I'm familiar with Fastpanel or how much control it gives you over the raw nginx config. What have you tried so far?

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=8.1",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "api-platform/core": "^3.0", // v3.0.8
        "doctrine/annotations": "^1.0", // 1.14.2
        "doctrine/doctrine-bundle": "^2.8", // 2.8.0
        "doctrine/doctrine-migrations-bundle": "^3.2", // 3.2.2
        "doctrine/orm": "^2.14", // 2.14.0
        "nelmio/cors-bundle": "^2.2", // 2.2.0
        "nesbot/carbon": "^2.64", // 2.64.1
        "phpdocumentor/reflection-docblock": "^5.3", // 5.3.0
        "phpstan/phpdoc-parser": "^1.15", // 1.15.3
        "symfony/asset": "6.2.*", // v6.2.0
        "symfony/console": "6.2.*", // v6.2.3
        "symfony/dotenv": "6.2.*", // v6.2.0
        "symfony/expression-language": "6.2.*", // v6.2.2
        "symfony/flex": "^2", // v2.2.4
        "symfony/framework-bundle": "6.2.*", // v6.2.3
        "symfony/property-access": "6.2.*", // v6.2.3
        "symfony/property-info": "6.2.*", // v6.2.3
        "symfony/runtime": "6.2.*", // v6.2.0
        "symfony/security-bundle": "6.2.*", // v6.2.3
        "symfony/serializer": "6.2.*", // v6.2.3
        "symfony/twig-bundle": "6.2.*", // v6.2.3
        "symfony/ux-react": "^2.6", // v2.6.1
        "symfony/validator": "6.2.*", // v6.2.3
        "symfony/webpack-encore-bundle": "^1.16", // v1.16.0
        "symfony/yaml": "6.2.*" // v6.2.2
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.2
        "symfony/debug-bundle": "6.2.*", // v6.2.1
        "symfony/maker-bundle": "^1.48", // v1.48.0
        "symfony/monolog-bundle": "^3.0", // v3.8.0
        "symfony/stopwatch": "6.2.*", // v6.2.0
        "symfony/web-profiler-bundle": "6.2.*", // v6.2.4
        "zenstruck/foundry": "^1.26" // v1.26.0
    }
}
userVoice