If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeFind your terminal and manually clear the cache directory:
rm -rf var/cache/*
I'm doing this so that, when we run all or our tests
symfony php bin/phpunit
we see a deprecation warning, which is fascinating. It says:
Since API Platform 3.1: in API Platform 4,
PUT
will always replace the data. setextraProperties["standard_put"]
totrue
on every operation to avoid breaking PUT's behavior. UsePATCH
for the old behavior.
Okay... what does that mean? Right now, it means nothing has changed: our PUT
operation behaves like it always has. But, in API Platform 4, the behavior of PUT
will change dramatically. And, at some point between now and then, we need to opt into that new behavior so that it doesn't suddenly break when we upgrade to version 4 in the future.
So what's changing exactly? Head over to the API docs and refresh. Use the GET
collection endpoint... and hit "Execute", so we can get a valid ID.
Great: we have a treasure with ID 1.
Right now, if we send a PUT
request with this ID, we can send just one field to update just that one thing. For example, we can send description
to change only that.
Oh, but before we Execute this, we do need to be logged in. In my other tab, I'll fill in the login form. Perfect. Now execute the PUT
operation.
Yup: we pass only the description
field, and it updates only the description
field: all the other fields remain the same.
Whelp, it turns out that this is not how PUT
is supposed to work according to the HTTP Spec. PUT
is supposed to be a "replace". What I mean is, if we send only one field, the PUT
operation is supposed to take that new resource - which is just the one field - and replace the existing resource. That's a complicated way of saying that, when using PUT, you need to send every field, even the fields that aren't changing. Otherwise, they'll be set to null
.
If that sounds kind of crazy, I kind of agree, but there are valid technical reasons for why this is the case. The point is that: this is how PUT
is supposed to work and in API Platform 4, this is how PUT
will work.
Honestly, it makes PUT
less useful. So you'll notice that I'll pretty much exclusively use PATCH
going forward.
So whether we like it or not, at some point between now and API platform 4, we need to tell API Platform that it is okay for it to change the behavior of PUT
to the "new" way. Let's do that now by adding some extra config to every ApiResource
attribute in our app.
Tip
To solve this globally for all your resources at once, you can add this as a default in the API Platform configuration:
# config/packages/api_platform.yaml
api_platform:
defaults:
extra_properties:
standard_put: true
Open src/Entity/DragonTreasure.php
... and add a new option called extraProperties
set to an array with standard_put
set to true
:
... lines 1 - 27 | |
( | |
... lines 29 - 64 | |
extraProperties: [ | |
'standard_put' => true, | |
], | |
) | |
... lines 69 - 89 | |
class DragonTreasure | |
{ | |
... lines 92 - 249 | |
} |
That's it! Copy that... because we're going to need that down here on this ApiResource
... even though it doesn't have a PUT
operation:
... lines 1 - 27 | |
( | |
... lines 29 - 64 | |
extraProperties: [ | |
'standard_put' => true, | |
], | |
) | |
( | |
... lines 70 - 81 | |
extraProperties: [ | |
'standard_put' => true, | |
], | |
) | |
... lines 86 - 89 | |
class DragonTreasure | |
{ | |
... lines 92 - 249 | |
} |
Then, over in User
, add that to both of the ApiResource
spots as well:
... lines 1 - 25 | |
( | |
... lines 27 - 44 | |
extraProperties: [ | |
'standard_put' => true, | |
], | |
) | |
( | |
... lines 50 - 59 | |
extraProperties: [ | |
'standard_put' => true, | |
], | |
) | |
... lines 64 - 66 | |
class User implements UserInterface, PasswordAuthenticatedUserInterface | |
{ | |
... lines 69 - 276 | |
} |
Now when we run our tests, the deprecation is gone! We're not using the PUT
operation in any tests, so everything still passes.
To see the new behavior, try out the PUT
endpoint again: still sending just one field. This time... check it out! A 422 validation error! All the fields that we did not include were set to null... and that caused the validation failure.
So... this makes PUT
a bit less useful... and we'll lean a lot more on PATCH
. If you don't want to have a PUT
operation at all anymore, that makes a lot of sense. One unique thing about the new PUT
behavior is that you could use it to create new objects... which could be useful in some edge-cases... or an absolute nightmare from a security standpoint as we now need to worry about objects being edited or created via the same PUT
operation. For that reason, as we go along, you'll see me remove the PUT
operation in some cases.
Next: let's get more complex with security by making sure that a DragonTreasure
can only be edited by its owner.
"Houston: no signs of life"
Start the conversation!
// composer.json
{
"require": {
"php": ">=8.1",
"ext-ctype": "*",
"ext-iconv": "*",
"api-platform/core": "^3.0", // v3.1.2
"doctrine/annotations": "^2.0", // 2.0.1
"doctrine/doctrine-bundle": "^2.8", // 2.8.3
"doctrine/doctrine-migrations-bundle": "^3.2", // 3.2.2
"doctrine/orm": "^2.14", // 2.14.1
"nelmio/cors-bundle": "^2.2", // 2.2.0
"nesbot/carbon": "^2.64", // 2.66.0
"phpdocumentor/reflection-docblock": "^5.3", // 5.3.0
"phpstan/phpdoc-parser": "^1.15", // 1.16.1
"symfony/asset": "6.2.*", // v6.2.5
"symfony/console": "6.2.*", // v6.2.5
"symfony/dotenv": "6.2.*", // v6.2.5
"symfony/expression-language": "6.2.*", // v6.2.5
"symfony/flex": "^2", // v2.2.4
"symfony/framework-bundle": "6.2.*", // v6.2.5
"symfony/property-access": "6.2.*", // v6.2.5
"symfony/property-info": "6.2.*", // v6.2.5
"symfony/runtime": "6.2.*", // v6.2.5
"symfony/security-bundle": "6.2.*", // v6.2.6
"symfony/serializer": "6.2.*", // v6.2.5
"symfony/twig-bundle": "6.2.*", // v6.2.5
"symfony/ux-react": "^2.6", // v2.7.1
"symfony/ux-vue": "^2.7", // v2.7.1
"symfony/validator": "6.2.*", // v6.2.5
"symfony/webpack-encore-bundle": "^1.16", // v1.16.1
"symfony/yaml": "6.2.*" // v6.2.5
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.2
"mtdowling/jmespath.php": "^2.6", // 2.6.1
"phpunit/phpunit": "^9.5", // 9.6.3
"symfony/browser-kit": "6.2.*", // v6.2.5
"symfony/css-selector": "6.2.*", // v6.2.5
"symfony/debug-bundle": "6.2.*", // v6.2.5
"symfony/maker-bundle": "^1.48", // v1.48.0
"symfony/monolog-bundle": "^3.0", // v3.8.0
"symfony/phpunit-bridge": "^6.2", // v6.2.5
"symfony/stopwatch": "6.2.*", // v6.2.5
"symfony/web-profiler-bundle": "6.2.*", // v6.2.5
"zenstruck/browser": "^1.2", // v1.2.0
"zenstruck/foundry": "^1.26" // v1.28.0
}
}