Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Lucky you! You found an early release chapter - it will be fully polished and published shortly!

Operations / Endpoints

This Chapter isn't
quite ready...

Rest assured, the gnomes are hard at work
completing this video!

Browse Tutorials

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


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!

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.

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.

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.

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.

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.

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!

Login or Register to join the conversation
EinzigTech Avatar
EinzigTech Avatar EinzigTech | posted 17 hours 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,

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