How to save a Nested Relation with Entity on API Platform

10,973

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"})
Share:
10,973
Bruno de Souza Rocha
Author by

Bruno de Souza Rocha

PHP and Delphi Developer since 2011

Updated on June 09, 2022

Comments

  • Bruno de Souza Rocha
    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
    Bruno de Souza Rocha almost 5 years
    it was exactly that! You saved my afternoon. Thanks a lot.
  • Irwuin
    Irwuin over 2 years
    Nice one ! this solution work me too, thank you