Embedded Write

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Here's an interesting question: if we fetch a single CheeseListing, we can see that the username comes through on the owner property. And obviously, if we, edit a specific CheeseListing, we can totally change the owner to a different owner. Let's actually try this: let's just set owner to /api/users/2. Execute and... yep! It updated!

That's great, and it works pretty much like a normal, scalar property. But... looking back at the results from the GET operation... here it is, if we can read the username property off of the related owner, instead of changing the owner entirely, could we update the current owner's username while updating a CheeseListing?

It's kind of a weird example, but editing data through an embedded relation is possible... and, at the very least, it's an awesome way to really understand how the serializer works.

Trying to Update the Embedded owner

Anyways... let's just try it! Instead of setting owner to an IRI, set it to an object and try to update the username to cultured_cheese_head. Go, go, go!

And... it doesn't work:

Nested documents for attribute "owner" are not allowed. Use IRIs instead.

So... is this possible, or not?

Well, the whole reason that username is embedded when serializing a CheeseListing is that, above username, we've added the cheese_listing:item:get group, which is one of the groups that's used in the "get" item operation.

The same logic is used when writing a field, or, denormalizing it. If we want username to be writable while denormalizing a CheeseListing, we need to put it in a group that's used during denormalization. In this case, that's cheese_listing:write.

Copy that and paste it above username.

... lines 1 - 22
class User implements UserInterface
{
... lines 25 - 51
/**
... line 53
* @Groups({"user:read", "user:write", "cheese_listing:item:get", "cheese_listing:write"})
... line 55
*/
private $username;
... lines 58 - 184
}

As soon as we do that - because the owner property already has this group - the embedded username property can be written! Let's go back and try it: we're still trying to pass an object with username. Execute!

Sending New Objects vs References in JSON

And... oh... it still doesn't work! But the error is fascinating!

A new entity was found through the relationship CheeseListing.owner that was not configured to cascade persist operations for entity User.

If you've been around Doctrine for awhile, you might recognize this strange error. Ignoring API Platform for a moment, it means that something created a totally new User object, set it onto the CheeseListing.owner property and then tried to save. But because nobody ever called $entityManager->persist() on the new User object, Doctrine panics!

So... yep! Instead of querying for the existing owner and updating it, API Platform took our data and used it to create a totally new User object! That's not what we wanted at all! How can we tell it to update the existing User object instead?

Here's the answer, or really, here's the simple rule: if we send an array of data, or really, an "object" in JSON, API Platform assumes that this is a new object and so... creates a new object. If you want to signal that you instead want to update an existing object, just add the @id property. Set it to /api/users/2. Thanks to this, API Platform will query for that user and modify it.

Let's try it again. It works! Well... it probably worked - it looks successful, but we can't see the username here. Scroll down and look for the user with id 2.

There it is!

Creating new Users?

So, we now know that, when updating... or really creating... a CheeseListing, we can send embedded owner data and signal to API Platform that it should update an existing owner via the @id property.

And when we don't add @id, it tries to create a new User object... which didn't work because of that persist error. But, we can totally fix that problem with a cascade persist... which I'll show in a few minutes to solve a different problem.

So wait... does this mean that, in theory, we could create a brand new User while editing a CheeseListing? The answer is.... yes! Well... almost. There are 2 things preventing it right now: first, the missing cascade persist, which gave us that big Doctrine error. And second, on User, we would also need to expose the $password and $email fields because these are both required in the database. When you start making embedded things writeable, it honestly adds complexity. Make sure you keep track of what and what is not possible in your API. I don't want users to be created accidentally while updating a CheeseListing, so this is perfect.

Embedded Validation

But, there is one weird thing remaining. Set username to an empty string. That shouldn't work because we have a @NotBlank() above $username.

Try to update anyways. Oh, of course! I get the cascade 500 error - let me put the @id property back on. Try it again.

Woh! A 200 status code! It looks like it worked! Go down and fetch this user... with id=2. They have no username! Gasp!

This... is a bit of a gotcha. When we modify the CheeseListing, the validation rules are executed: @Assert\NotBlank(), @Assert\Length(), etc. But when the validator sees the embedded owner object, it does not continue down into that object to validate it. That's usually what we want: if we were only updating a CheeseListing, why should it also try to validate a related User object that we didn't even modify? It shouldn't!

But when you're doing embedded object updates like we are, that changes: we do want validation to continue down into this object. To force that, above the owner property, add @Assert\Valid().

... lines 1 - 39
class CheeseListing
{
... lines 42 - 86
/**
... lines 88 - 90
* @Assert\Valid()
*/
private $owner;
... lines 94 - 197
}

Ok, go back, and... try our edit endpoint again. Execute. Got it!

owner.username: This value should not be blank

Nice! Let's go back and give this a valid username... just so we don't have a bad user sitting in our database. Perfect!

Being able to make modifications on embedded properties is pretty cool... but it does add complexity. Do it if you need it, but also remember that we can update a CheeseListing and a User more simply by making two requests to two endpoints.

Next, let's get even crazier and talking about updating collections: what happens if we start to try to modify the cheeseListings property directly on a User?

Leave a comment!

  • 2020-07-03 Gaetano Sottile

    Oh yeahhhhhh. Eccellentissimo :) catch..thank you so much....

  • 2020-07-03 weaverryan

    I think Hans Grinwis just nailed the answer :). If you have a "title" argument to the constructor, then it IS something you can set on CREATE, because API Platform will use your constructor. But if it has not setter, then you cannot update it. Excellent catch on that! It's a feature, but it *is* subtle.

  • 2020-07-03 Hans Grinwis

    Isn't this because the title must be set through the constructor and there is no setTitle method? The constructor is only used when the object is created, not when it is updated.

  • 2020-07-03 Gaetano Sottile

    Thanks Victor,
    I think I'll go crazy :). I deleted all kinds of cache (cache clear, pool, deleting by rm). Just the title is not editable....sorry. If you need more info, pictures or whatever....tell me. It's very strange.
    Thanks again

  • 2020-07-03 Victor Bocharsky

    Hey Gaetano,

    Hm, your code looks correct. Could you clear the cache and try again? Make sure the cache is cleared for the environment you have the problem with. I think it might be a cache error only, because it should just work.

    I hope this helps.

    Cheers!

  • 2020-07-01 Gaetano Sottile

    Hi Diego,

    thanks for your help. I think that if POST method works, the Groups is right.

    This is my code:


    /**
    * @ApiResource(
    * routePrefix="/market",
    * collectionOperations={"get", "post"},
    * itemOperations={
    * "get_chees"={
    * "method"="GET",
    * "normalization_context"={"groups"={"cheese_listing:read", "cheese_listing:item:get"}},
    * },
    * "put"
    * },
    * shortName="cheeses",
    * normalizationContext={"groups"={"cheese_listing:read"}, "swagger_definition_name"="Read"},
    * denormalizationContext={"groups"={"cheese_listing:write"}, "swagger_definition_name"="Write"},
    * attributes={
    * "pagination_items_per_page"=10
    * }
    *
    * )
    * @ApiFilter(BooleanFilter::class, properties={"isPublished"})
    * @ApiFilter(SearchFilter::class, properties={"title"="partial", "description"="partial"})
    * @ApiFilter(RangeFilter::class, properties={"price"})
    * @ApiFilter(PropertyFilter::class)
    *
    * @ORM\Entity(repositoryClass=CheeseListeningRepository::class)
    */


    class CheeseListing
    {
    /**
    * @ORM\Id()
    * @ORM\GeneratedValue()
    * @ORM\Column(type="integer")
    * @Groups({"cheese_listing:read"})
    */
    private $id;


    /**
    * @ORM\Column(type="string", length=255)
    * @Groups({"cheese_listing:read", "cheese_listing:write", "user:read"})
    * @Assert\NotBlank()
    * @Assert\Length(
    * min=2,
    * max=50,
    * maxMessage="Describe your cheese in 50 chars or less"
    * )
    */
    private $title;


    /**
    * @ORM\Column(type="text")
    * @Groups({"cheese_listing:read"})
    * @Assert\NotBlank()
    */
    private $description;


    /**
    * The price of this delicious cheese in Euro cents.
    * @ORM\Column(type="integer")
    * @Groups({"cheese_listing:read", "cheese_listing:write", "user:read"})
    * @Assert\NotBlank()
    */
    private $price;


    /**
    * @ORM\Column(type="datetime")
    */
    private $createdAt;


    /**
    * @ORM\Column(type="boolean")
    */
    private $isPublished= false;


    /**
    * @ORM\ManyToOne(targetEntity=User::class, inversedBy="cheeseListings")
    * @ORM\JoinColumn(nullable=false)
    * @Groups({"cheese_listing:read", "cheese_listing:write"})
    * @Assert\Valid()
    */
    private $owner;


    /**
    * CheeseListing constructor.
    * @param String $title
    */
    public function __construct(String $title = null)
    {
    $this->createdAt = new \DateTimeImmutable();
    $this->title = $title; //I'm using here the title and it works fine because I'm using the word title (same of property of entity)
    }


    public function getId(): ?int
    {
    return $this->id;
    }


    public function getTitle(): ?string
    {
    return $this->title;
    }
    //it is possible passing title also in the constructor
    // public function setTitle(string $title): self
    // {
    // $this->title = $title;
    //
    // return $this;
    // }


    public function getDescription(): ?string
    {
    return $this->description;
    }


    /**
    * @Groups("cheese_listing:read")
    */
    public function getShortDescription(): ?string
    {
    if(strlen($this->description) < 40){
    return $this->description;
    }


    return substr($this->description, 0, 40) . '...';
    }


    /**
    * The description of the cheese as raw text.
    * @SerializedName("description")
    * @Groups("cheese_listing:write")
    * @param string $description
    * @return CheeseListing
    */
    public function setTextDescription(string $description): self
    {
    $this->description = nl2br($description);


    return $this;
    }


    public function getPrice(): ?int
    {
    return $this->price;
    }


    public function setPrice(int $price): self
    {
    $this->price = $price;


    return $this;
    }


    public function getCreatedAt(): ?\DateTimeInterface
    {
    return $this->createdAt;
    }


    /**
    * How long ago in text that this cheese listing was added.
    *
    * @Groups("cheese_listing:read")
    */
    public function getCreatedAtAgo(): string
    {
    return Carbon::instance($this->getCreatedAt())->diffForHumans();
    }


    public function getIsPublished(): ?bool
    {
    return $this->isPublished;
    }


    public function setIsPublished(bool $isPublished): self
    {
    $this->isPublished = $isPublished;


    return $this;
    }


    public function getOwner(): ?User
    {
    return $this->owner;
    }


    public function setOwner(?User $owner): self
    {
    $this->owner = $owner;


    return $this;
    }
    }


    I don't know where is the problem....

  • 2020-07-01 Diego Aguiar

    Hey Gaetano Sottile

    I believe your Groups are not set correctly. Double check that the "title" field has the writing group. Also, double check the itemOperations of the CheeseListing class uses the same groups as the option denormalizationContext

    Cheers!

  • 2020-07-01 Gaetano Sottile

    Hello guys and thanks for your work about tutorials (bless you),
    I'm finished this chapter and when I did some test to understand Embedded Validation, I noticed something wrong: maybe I'm missing something or I don't understand. When I do a PUT on cheeses I'm able to edit price, description and, obviously, username but nothing happen when I edit title (it's always the same title used during POST creation). Could you help me? Thanks

  • 2020-06-16 weaverryan

    Hey Egor Ushakov!

    Wow :). So, 2.5.5 is where it's introduced. Even these patch releases are pretty big in API Platform, but I don't see anything that jumps out at me - https://github.com/api-plat... - though if this is a bug, it's pretty subtle, so it could be some tiny detail.

    > B) Unfortunately I haven’t managed to reproduce the issue on a brand new project

    Hmm, yea... if it IS a bug (especially due to the complexity), then you'll need a repeater. Since you haven't been able to reproduce it in a new project, maybe work backwards? Clone your project, then start ripping everything out? See if you can reduce it down to a tiny project that still has the behavior? Or, you could use a slightly less hard-core option, and start a new project, but then copy the composer.lock file from your old project (to ensure the *exact* same dependencies) and see if you can repeat it. If you can, start removing "extra" dependencies to make the app as small as you can.

    Let me know what you find out!

    Cheers and happy hunting!

  • 2020-06-12 Egor Ushakov

    A) OK, as we know “api-platform/core”: “2.5.2” works OK. But there is something new I found out... The doc displays OK only if Person::$firstName annotated with @Groups of noramlizationContext of Vehicle! That is @Groups{“person:read”, “”person:write”, “vehicle:read”, “vehicle:write”} As I remove “vehicle:read” it becomes just a "ownerPerson": "string" in Vehicle docs. I tried to create pictures combining screenshot but my image editing kung-fu is too bad. Let's start moving versions up. 2.5.3 - same behaviour. 2.5.4 - same behaviour. 2.5.5 - “ownerPerson” completely disappears from Vehicle docs until Person::$firstName has @Groups from Vehicle's denormalizationContext (i.e. “vehicle:write”). 2.5.6 - same as 2.5.5. Move to Clause B…

    B) Unfortunately I haven’t managed to reproduce the issue on a brand new project. I used exactly the same versions of FrameworkBundle and “api-platform/core” but in a new project it works OK. Sorry... Looks like something wrong with my own code. But on the other side manipulating with "api-platform/core" versions changes the result. May be you could give some direction to pointing me to debug the issue by myself on a local machine?

  • 2020-06-11 weaverryan

    Hey @Egor!

    Ah, then it seems we have our answer! This *is* likely a bug in ApiPlatform. I’d encourage you to open a bug report about this. Two things would make it likely that it could get fixed quickly:

    A) if you can identify the exact first version where the bug was introduced, that’s helpful - e.g. was it 2.5.3 or 2.5.4?

    B) if you’re able to create a small project that reproduces the issue and put it on GitHub. That’s a bit more work, but it makes it much easier to debug the cause for a maintainer :).

    Cheers! And... I guess... congrats on finding a bug!? 😉

  • 2020-06-10 Egor Ushakov

    Hey, weaverryan !

    A) I intentionally reduced an amount of Person's code in the gist and have to say that Person::setFirstName() was always in its place. I event tried to explicitly annotate setter with @Groups but with no luck.
    B) The oldest "api-platform/core" version I managed to downgrade to was 2.5.2: 2.5.0 requires Symfony ^4.0, 2.5.1 raised an error connected to ExceptionListener from Symfony HttpKernel package. And ta-da-am! 2.5.2 works as it should!
    C) Yeah, it works but "I'm wondering why the documentation is wrong"! At first I added embedded write on front-dev request without even checking the result, then he signaled me that there is no "person" property in the doc. After that I found out that the correct Vehicle schema structure is shown in UI and make a request and it worked! But it confused other people :)

  • 2020-06-10 weaverryan

    Hey Egor Ushakov!

    Ok, MUCH better - this was an *awesome* number of details. I think we're closer... but I still have some questions:

    A) Try adding a setFirstName() method to Person. I want to check to make sure that the fact that there is no setter (just the constructor argument) isn't confusing API Platform. It's possible that it incorrectly thinks that the firstName property is not "settable", and thus is not including it in the POST API docs (and then possibly because ownerPerson has *no* properties, it completely removes it from the docs - this is a TOTAL guess).

    B) Try adding "api-platform/core": "2.5.0" and then running composer update. That should downgrade your API Platform. I doubt this will help - but I want to eliminate the possibility that there is some bug introduced in a newer version.

    C) On the request-response you sent - https://gist.github.com/ero... - what is the *expected* behavior? It looks to me like this *does* create a new Person resource (IRI /api/people/9878dcf6-7b04-428c-bce6-b0307f04a370) and sets it on the Vehicle. Is this not what you want? If not, what were you expecting? Or is this correct, but you are wondering only why the documentation is wrong?

    Overall, when it comes to the missing field in the documentation, that *does* smell like a bug... especially if you ARE in fact able to POST this field successfully.

    Cheers!

  • 2020-06-09 Egor Ushakov

    Hey, weaverryan!

    Here are my code snippets:

    - Vehicle.php
    - Person.php
    - request-response JSONs

    As you can see in request-response JSONs the embedded write works. But the "ownerPerson" property disappeares from body template in UI. Here are screenshots:

    - before adding @Groups{"vehicle:write"} to Person::$firstName
    - after adding @Groups{"vehicle:write"} to Person::$firstName

    Moreover the Vehicle object in UI displays OK .

    Versions: Symfony 5.0.9, ApiPlatform Pack 1.2.2

  • 2020-06-08 weaverryan

    Hey Egor Ushakov!

    Ah, it sounds like a complex issue! Let's see if we can figure out out :). 2 things:

    1) Can you POST some additional info? I'd like to see: (A) the relevant code or the Person entity (B) the relevant code of the Vehicle entity (C) the exact POST request you are making and the exact data you are sending and (D) the exact response (including JSON) from that POST request. You have many of the details here... but because I'm can't see the full project, it's hard to follow.

    2) About the "service or alias has been removed or inlined when the container was compiled" - don't worry about that at all. That is simply notifying you of an optimization that's happening internally. So, this is a "red herring" as we say: it is something totally unrelated to the problem (and not actually a problem) so you can ignore it :).

    Btw, I might also try one thing - downgrade api-platform/core to maybe version 2.5.0 and see if it makes any difference. I can't remember the specifics, but I may have heard about a bug in recent API Platform versions regarding embedded writes.

    Cheers!

  • 2020-06-05 Egor Ushakov

    Looks like something totally wrong with \Symfony\Component\Serializer\Serializer in the app... I found out that same "removing" from JSON-template issue connects also to PhoneNumber (part of odolbeau/phone-number-bundle) typed Person::$phone property. Weird but all the services needed (PhoneNumberNormalizer, PhoneNumberUtil) are present in `debug:container`. The only thing it's a message

    ! [NOTE] The "Misd\PhoneNumberBundle\Serializer\Normalizer\PhoneNumberNormalizer" service or alias has been removed or inlined when the container was compiled.
  • 2020-06-04 Egor Ushakov

    I did embedded write a couple of times before. I just quickly created a project from scratch to make sure embedded write is still working. But in my project I’ve been developing within the last two months I have a weird issue - the embedded write works in an opposite way :) Front-end dev asked me to implement embedded write for the Owner (Person entity) of the Vehicle. This works perfectly while using Peron’s IRI. But as soon as I add vehicle:write @Groups to Person::$firstName, the Vehicle::$owner key is completely erased from JSON template for POST operation. Even created a new entity NewVehicle and associated it with Person - same result. Any ideas why this could happen or at least how to debug an issue?

  • 2020-03-19 Diego Aguiar

    I get your point, it would be simpler for users indeed. I think what you can do is to only add the fields you want to be editable of the UserAddress resource and don't allow to change the related ID between User and UserAddress

  • 2020-03-19 Emilio Clemente

    It sure makes sense, but in the context of my app, maybe it is more reasonable to do it in just one request, because I have many user properties that could be updated alongside the address and visually it is all in the same html form. So I was thinking more about security issues here, what if I allow the client to write to the embedded object (UserAddress) and a malicious user tricks the request to modify not only the object but the relationship itself.

    Maybe it is not possible, maybe I am getting the concept wrong, but I would like I could set up the API like that.

    And thank you very much for the really fast answer.

  • 2020-03-19 Diego Aguiar

    Hey Emilio Clemente

    I'm not sure if that's the way to update an embedded resource because you are trying to update a UserAddress record that's not related to the User endpoint (/users/1) you are accessing. I think you should update it directly by using the UserAddress endpoint, or by doing so through the User record that's related to UserAddress#2

    I hope this makes sense to you. Cheers!

  • 2020-03-19 Emilio Clemente

    Real good tutorial, thank you very much for the effort.

    I have an odd situation I wish you could help me with. I don't know if I am missing something or it is just the way it works.

    Let's take for example the relation User OneToOne UserAddress.

    Let's say I have a User#1 that is related to a UserAddress#1, but I also have an existing UserAddress#2

    If I do the following, it will associate the UserAddress#2 to User#1, which is something I don't want to (and also this would update de UserAddress#2)

    PUT /users/1
    {
    userAddress: {
    id: "/user_addresses/2",
    "postcode": 1234
    }
    }

    Is there a way to prevent this behaviour? I would expect the API to ignore the id and just update the current embedded object with the new postcode value.

    Thanks in advance!

  • 2020-02-03 coolfarmer

    Thanks for your time! I manage to fix the problem by myself, weirdly it was about validation with @Asset :/ Thanks again, I finally bought the tutorial because it's not so easy with the text only haha.

  • 2020-02-03 weaverryan

    Hey @coolfarmer!

    Well... I don't know if I have good news or bad news :). Actually, I think it's good news. Here's what I did:

    1) Installed a new Symfony 5.0 project using the website-skeleton (which just means that things like Doctrine, Twig, etc are pre-installed)
    2) Added your 2 entities exactly to my project
    3) Removed a few extra properties - like "job" - to get things working
    4) Then used the API Platform admin area to do exactly what you were trying to do. And... it worked!

    Here's the PUT request I made (I previously inserted 1 Employee with id 1 and 2 Users with id's 1 & 2):

    https://imgur.com/2gfPkia

    And here is the result:

    https://imgur.com/H8UQtCT

    As you can see, it worked as expected! By the way, here is the project I built: https://github.com/weaverry...

    I believe the problem is that you're sending the wrong Content-Type header. It must be: Content-Type: application/ld+json. If you instead send Content-Type: application/json, you will get the error you're getting. The reason is that it is the JSON-ld format that understand the significance of the "@id" stuff. If API Platform processes this as raw JSON, it just tries to always create a new object and it sees @id as a normal property that it might try to set.

    I can't see your headers to be sure, so let me know.

    Cheers!

  • 2020-01-31 coolfarmer

    No I'm not using POST, I'm using PUT to do an "Embedded Write", is PUT the correct verb for that operation?

    Here is it : https://i.imgur.com/wEFcGLZ...

  • 2020-01-31 Diego Aguiar

    Hey coolfarmer

    So you were using POST instead of PUT? Could you fix your problem?

    Cheers!

  • 2020-01-30 coolfarmer

    I have removed the /api because my domain already contain "api", I don't like duplicate. My IRI are fonctionnal because it work good at others places so I can confirm you that the problem don't come from there. For example, I can make a POST on my "/employees" with owner = /users/2 and all work good. :)

    My API is pretty new, I'm using the last version of API Platform with Symfony 5.0.1. I followed your tutorial since the beginning and all work nicely except for Embedded write.

    On my side, my entity are Employee (with an owner property just like you) and User.
    Employee: https://gist.github.com/coo...
    User: https://gist.github.com/coo...

    Is it possible for you to take a 2 minutes to look at my annotations? It's pretty weird :/
    I hope you can help me, thanks!

    EDIT: I use PUT on url http://api.domain.local:8785/employees/13 with this in body:

    {
    "owner": {
    "@id": "/users/2",
    "username": "newUsername"
    }
    }

  • 2020-01-30 weaverryan

    Hey coolfarmer!

    Yep, the tutorial should be up-to-date :) - API Platform has only had one minor release since we recorded this. So, hmmm. It definitely looks like it's *creating* a new User object instead of updating the existing one (as we would expect). Is the @id value correct? If you're using the normal API Platform setup, the IRI would be /api/users/2 (with /api</code)>

  • 2020-01-30 coolfarmer

    Is this tutorial up to date? Because even if I set the property @id in the owner object I am getting the same error "A new entity was found through the relationship CheeseListing.owner that was not configured to cascade persist operations for entity User.".

    {
    "owner": {
    "@id": "/users/2",
    "username": "MyNewUsername"
    }
    }

    Thanks!

  • 2019-06-21 Jérôme 

    Thanks for answering my question, weaverryan! I had a suspicion that it could indeed be an edge-case feature, but I just wanted to be sure. It's something cool to know, but I'm not sure I'll often use it. Thanks for your explanation and your availability ;)

  • 2019-06-20 weaverryan

    Hey Jérôme !

    That's an excellent question :). I honestly think this is an edge-case feature, and I covered it mostly because it *really* uncovers how the serializer works so that you can feel comfortable that you have full control (and there isn't any magic you don't know about). But, I *do* think that some users will need/want something like this. The best example is probably a OneToOne relationship in Doctrine (which I admit, I don't use a lot - but it makes for a great example in this case). Like, you have a User entity with a OneToOne to a UserAddress. The tricks in this chapter would allow you to update the "username" of your User *and* their address, all in one request. Basically, this specific chapter is *most* useful when you have an entity (e.g. User) that has a relation to another entity (UserAddress) where it really "owns" that entity (UserAddress is totally "owned" by a User). It has its use-cases, but won't be a main thing you use all the time.

    Cheers!

  • 2019-06-20 Jérôme 

    At first, let me thank you (again) for this well explained chapter! I understand how embedded write works now, however I'm not quite sure in which context it might come in handy in a real life project. Could you please give me an example of its usefulness so I can fully understand the concept? Thank you!