How to save a Nested Relation with Entity on API Platform
Solution 1
you can try implement Denormalization
Question:
<?php
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
use DateTimeInterface;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* @ORM\Entity(repositoryClass="App\Repository\QuestionRepository")
* @ApiResource(
* denormalizationContext={"groups"={"post"}}
* )
*/
class Question implements CreatedAtEntityInterface, UpdatedAtEntityInterface
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\Token", inversedBy="questions")
* @ORM\JoinColumn(nullable=false)
* @Assert\NotBlank()
* @Groups({"post"})
*/
private $token;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\Question", inversedBy="question_versions")
* @Groups({"post"})
*/
private $question;
/**
* @ORM\OneToMany(targetEntity="App\Entity\Question", mappedBy="question")
* @Groups({"post"})
*/
private $question_versions;
/**
* @ORM\Column(type="integer")
* @Assert\NotBlank()
* @Groups({"post"})
*/
private $version;
/**
* @ORM\Column(type="string", length=100)
* @Assert\Length(max="100")
* @Assert\NotBlank()
* @Groups({"post"})
*/
private $name;
/**
* @ORM\Column(type="integer")
* @Assert\NotBlank()
* @Groups({"post"})
*/
private $type;
/**
* @ORM\Column(type="text", length=65535)
* @Assert\NotBlank()
* @Assert\Length(max="65535")
* @Groups({"post"})
*/
private $enunciation;
/**
* @ORM\Column(type="string", length=255, nullable=true)
* @Assert\Length(max="255")
* @Groups({"post"})
*/
private $material;
/**
* @ORM\Column(type="text", length=65535, nullable=true)
* @Assert\Length(max="65535")
* @Groups({"post"})
*/
private $tags;
/**
* @ORM\Column(type="boolean")
* @Assert\NotNull()
* @Groups({"post"})
*/
private $public;
/**
* @ORM\Column(type="boolean")
* @Assert\NotNull()
* @Groups({"post"})
*/
private $enabled;
/**
* @ORM\Column(type="datetime")
* @Assert\DateTime()
* @Groups({"post"})
*/
private $createdAt;
/**
* @ORM\Column(type="datetime")
* @Assert\DateTime()
* @Groups({"post"})
*/
private $updatedAt;
/**
* @ORM\OneToMany(targetEntity="App\Entity\Alternative", mappedBy="question")
* @Groups({"post"})
*/
private $alternatives;
/**
* @ORM\OneToMany(targetEntity="App\Entity\QuestionProperty", mappedBy="question")
* @Groups({"post"})
*/
private $properties;
/**
* @ORM\OneToMany(targetEntity="App\Entity\QuestionAbility", mappedBy="question")
* @Groups({"post"})
*/
private $abilities;
/**
* @ORM\OneToMany(targetEntity="QuestionCompetency", mappedBy="question")
* @Groups({"post"})
*/
private $competencies;
}
Alternative:
<?php
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
use DateTimeInterface;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* @ORM\Entity(repositoryClass="App\Repository\AlternativeRepository")
* @ORM\Table(name="alternatives")
* @ApiResource()
*/
class Alternative implements CreatedAtEntityInterface, UpdatedAtEntityInterface
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\Question", inversedBy="alternatives")
* @ORM\JoinColumn(nullable=false)
* @Assert\NotBlank()
* @Groups({"post"})
*/
private $question;
/**
* @ORM\Column(type="text", length=65535)
* @Assert\NotBlank()
* @Assert\Length(max="65535")
* @Groups({"post"})
*/
private $enunciation;
/**
* @ORM\Column(type="datetime")
* @Assert\DateTime()
* @Groups({"post"})
*/
private $createdAt;
/**
* @ORM\Column(type="datetime")
* @Assert\DateTime()
* @Groups({"post"})
*/
private $updatedAt;
/**
* @ORM\OneToMany(targetEntity="App\Entity\AlternativeProperty", mappedBy="alternatives")
* @Groups({"post"})
*/
private $properties;
}
Now you can this send JSON to POST/PUT without IRI:
{
"token": "/api/tokens/1",
"question": "string",
"version": "string",
"name": "string",
"type": 0,
"enunciation": "string",
"public": true,
"enabled": true,
"alternatives": [
{
"enunciation": "String",
"createdAt": "2018-01-01 11:11:11",
"updatedAt": "2018-01-01 11:11:11"
}
]
}
Solution 2
For anyone who has a problem with the error cascade persist you have to add it in the OneToMany property.That is, for our question:
@ORM\OneToMany(targetEntity="App\Entity\Alternative", mappedBy="question", cascade={"persist"})
Comments
-
Bruno de Souza Rocha almost 2 years
I have two entities, Question and Alternative where Question has a OneToMany relation with Alternative and I'm trying to send a JSON with a nested document of Alternative on it via POST to Question API Platform.
The API Platform returns that error below :
Nested documents for "alternatives" attribute are not allowed. Use IRIs instead.
Searching about it I've found some people saying that is only possible using IRIs and some other people saying that is possible to use Denormalization and Normalization contexts to solve this problem but I can't find some example or tutorial about it.
TL;DR;
Is there a way to send a nested relation into an entity POST on API Platform without using IRIs?
UPDATE:
As asked, please see below the two mappings of Question and Alternative entities.
Question
<?php namespace App\Entity; use ApiPlatform\Core\Annotation\ApiResource; use DateTimeInterface; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; /** * @ORM\Entity(repositoryClass="App\Repository\QuestionRepository") * @ApiResource() */ class Question implements CreatedAtEntityInterface, UpdatedAtEntityInterface { /** * @ORM\Id() * @ORM\GeneratedValue() * @ORM\Column(type="integer") */ private $id; /** * @ORM\ManyToOne(targetEntity="App\Entity\Token", inversedBy="questions") * @ORM\JoinColumn(nullable=false) * @Assert\NotBlank() */ private $token; /** * @ORM\ManyToOne(targetEntity="App\Entity\Question", inversedBy="question_versions") */ private $question; /** * @ORM\OneToMany(targetEntity="App\Entity\Question", mappedBy="question") */ private $question_versions; /** * @ORM\Column(type="integer") * @Assert\NotBlank() */ private $version; /** * @ORM\Column(type="string", length=100) * @Assert\Length(max="100") * @Assert\NotBlank() * */ private $name; /** * @ORM\Column(type="integer") * @Assert\NotBlank() */ private $type; /** * @ORM\Column(type="text", length=65535) * @Assert\NotBlank() * @Assert\Length(max="65535") */ private $enunciation; /** * @ORM\Column(type="string", length=255, nullable=true) * @Assert\Length(max="255") */ private $material; /** * @ORM\Column(type="text", length=65535, nullable=true) * @Assert\Length(max="65535") */ private $tags; /** * @ORM\Column(type="boolean") * @Assert\NotNull() */ private $public; /** * @ORM\Column(type="boolean") * @Assert\NotNull() */ private $enabled; /** * @ORM\Column(type="datetime") * @Assert\DateTime() */ private $createdAt; /** * @ORM\Column(type="datetime") * @Assert\DateTime() */ private $updatedAt; /** * @ORM\OneToMany(targetEntity="App\Entity\Alternative", mappedBy="question") */ private $alternatives; /** * @ORM\OneToMany(targetEntity="App\Entity\QuestionProperty", mappedBy="question") */ private $properties; /** * @ORM\OneToMany(targetEntity="App\Entity\QuestionAbility", mappedBy="question") */ private $abilities; /** * @ORM\OneToMany(targetEntity="QuestionCompetency", mappedBy="question") */ private $competencies; }
Alternative
<?php namespace App\Entity; use ApiPlatform\Core\Annotation\ApiResource; use DateTimeInterface; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; /** * @ORM\Entity(repositoryClass="App\Repository\AlternativeRepository") * @ORM\Table(name="alternatives") * @ApiResource() */ class Alternative implements CreatedAtEntityInterface, UpdatedAtEntityInterface { /** * @ORM\Id() * @ORM\GeneratedValue() * @ORM\Column(type="integer") */ private $id; /** * @ORM\ManyToOne(targetEntity="App\Entity\Question", inversedBy="alternatives") * @ORM\JoinColumn(nullable=false) * @Assert\NotBlank() */ private $question; /** * @ORM\Column(type="text", length=65535) * @Assert\NotBlank() * @Assert\Length(max="65535") */ private $enunciation; /** * @ORM\Column(type="datetime") * @Assert\DateTime() */ private $createdAt; /** * @ORM\Column(type="datetime") * @Assert\DateTime() */ private $updatedAt; /** * @ORM\OneToMany(targetEntity="App\Entity\AlternativeProperty", mappedBy="alternatives") */ private $properties; }
-
Bruno de Souza Rocha almost 5 yearsit was exactly that! You saved my afternoon. Thanks a lot.
-
Irwuin over 2 yearsNice one ! this solution work me too, thank you