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 SubscribeOur brand-new user is the proud owner of two treasures with IDs 7
and 44
. Let's update this user to see if we can make some changes to $dragonTreasures
. Use the PUT
endpoint, click "Try it out", and... let's see... the id
we need is 14
... so I'll enter that. I'll also remove every field except for dragonTreasures
so we can focus.
We know that this currently has two dazzling treasures - /api/treasures/7
and /api/treasures/44
. So if we send this request, in theory, that should do... nothing! And if we look down here... yeah: it made no changes at all.
Suppose we want to add a new DragonTreasure
to this resource. To do that, we list the two that it already has, along with /api/treasures/8
. I'm totally guessing that's a valid id
. When we hit "Execute"... that works beautifully. The serializer system noticed that it already had these first two, so it didn't do anything with those. It just added the new one with id 8
.
That's cool, but what I really want to talk about is removing a treasure. Let's say that our dragon left one of these treasures in their pants pocket and accidentally washed it in the laundry. I can't blame them. I lose my lip balm in there all the time. Since the treasure is soggy and useless now, we need to remove it from the list of treasures. No problem! We'll just mention the two our dragon still has and remove the other one. When we hit "Execute"... it explodes!
An exception occurred while executing a query: [...] Not null violation: 7. null value in column "owner_id"
What happened? Well, our app set the $owner
property for the DragonTreasure
we just removed to null
... and is now trying to save it. But since we have it set to nullable: false
, it's failing.
... lines 1 - 55 | |
class DragonTreasure | |
{ | |
... lines 58 - 97 | |
#[ORM\ManyToOne(inversedBy: 'dragonTreasures')] | |
#[ORM\JoinColumn(nullable: false)] | |
... lines 100 - 101 | |
private ?User $owner = null; | |
... lines 103 - 214 | |
} |
But... let's take a step back and look at the whole picture. First, the serializer noticed that treasures 7
and 8
are already owned by the User
... so it did nothing with those. But then it noticed that the treasure with id 44 - which was owned by this User
- is missing!
Because of that, over on our User
class, the serializer called removeDragonTreasure()
. What's really important is that it takes that DragonTreasure
and set the owner
to null
to break the relationship. Depending on your app, that might be exactly what you want. Maybe you allow dragonTreasures
to have no owner
... like... they're still undiscovered and waiting for a dragon to find them. If that's the case, you'll just want to make sure that your relationship allows null
... and everything will save just fine.
But in our case, if a DragonTreasure
no longer has an owner
, we want to delete it completely. We can do that in User
... way up on the dragonTreasures
property. After cascade
, add one more option here: orphanRemoval: true
.
... lines 1 - 22 | |
class User implements UserInterface, PasswordAuthenticatedUserInterface | |
{ | |
... lines 25 - 50 | |
#[ORM\OneToMany(mappedBy: 'owner', targetEntity: DragonTreasure::class, cascade: ['persist'], orphanRemoval: true)] | |
... lines 52 - 53 | |
private Collection $dragonTreasures; | |
... lines 55 - 171 | |
} |
This tells Doctrine that if any of these dragonTreasures
become "orphaned" - meaning they no longer have any owner - they should be deleted.
Let's try it. When we hit "Execute" again... got it! It saves just fine.
Next: Let's circle back to filters and see how we can use them to search across related resources.
Hey Julien!
Sorry for the slow reply - we had a hiccup in our notification system!
I'm a bit confused, my user put doesn't have a list of IRIs but a list of objects. I don't know if I missed something.
Just to be totally clear, when I make a PUT
request, for the dragonTreasures
property, I am:
A) SENDING an array of IRI strings
B) RECEIVING an array of objects
Let's look at each side more deeply (probably some of this you already understand, but just to be safe):
A) Since we're writing to User
, the user:write
group is being used. DragonTreasure
DOES have user:write
above name
and value
. So, should we be sending an object instead of IRI strings? The answer is that, because those 2 fields have user:write
, we have the OPTION to send an "object" to the dragonTreasures
property, but we're not forced to. We showed that off in an earlier chapter where we even sent a mixture or objects and IRI strings: https://symfonycasts.com/screencast/api-platform/collections-create#sending-embedded-objects-and-iri-strings-at-the-same-time
B) We are using the PUT
operation. But at this point, we're now talking about what data to RETURN. So that is "normalization". And so, the group that's used here is user:read
. Once again, inside DragonTreasure
, both name
and value
have this group. And so, the response contains an array of objects for dragonTreasures
.
Let me know if that helps :)
Cheers!
// 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
}
}
Hey there,
I'm a bit confused, my user put doesn't have a list of IRIs but a list of objects. I don't know if I missed something.
Looking at your DragonTreasure.php file, I feel like you should too.
Anyway, I removed the user:write group from my DragonTreasure's properties, but this is confusing.
Cheers