Search code examples
phpapisymfonydoctrine-ormfosrestbundle

Unable to include Collection of Entities inside API POST-call


The error in question:

Entity of type App\Entity\Nutt is missing an assigned ID for field  'squirrel'.
The identifier generation strategy for this entity requires the ID field to be populated before EntityManager#persist() is called.
If you want automatically generated identifiers instead you need to adjust the metadata mapping accordingly.

I'm perfectly able to call the api POST to add a Squirrel entity into the database. And using the id of this Squirrel, I can preform the POST call for the Nutt entity with the result being a correctly related record in the Nutt tabel.

What I can't seem to get working, is allowing the Squirrel api call to include the related collection of Nutts I want to insert in the same api call.

What am I doing wrong?

The JSON call:

{
    "name": "Jake",
    "nutts": [
        {
            "size": 10,
            "color": "blue"
        }
    ]
}

Entity Squirrel

/**
 * @ORM\Entity
 * @ORM\Table(name="squirrel")
 */
class Squirrel {
  /**
   * @ORM\Column(type="integer")
   * @ORM\Id
   * @ORM\GeneratedValue(strategy="AUTO")
   */
  private $id;
  /**
   * @ORM\Column(type="string", length=100)
   * @Assert\NotBlank()
   *
   */
  private $name;
  /**
   * @ORM\OneToMany(targetEntity="App\Entity\Nutt", mappedBy="squirrel", cascade={"persist", "remove"})
   */
  private $nutts;
  public function __construct()
  {
    $this->nutts = new \Doctrine\Common\Collections\ArrayCollection();
  }
  public function getId()
  {
    return $this->id;
  }
  public function setId($id)
  {
    $this->id = $id;
  }
  public function getName()
  {
    return $this->name;
  }
  public function setName($name)
  {
    $this->name = $name;
  }
  public function getNutts(): ?Collection
  {
    return $this->nutts;
  }
  public function setNutts(Collection $nutts)
  {
    foreach ($nutts as $nutt)
    {
      $this->nutts->add($nutt);
    }
  }
  public function addNutt(Nutt $nutt): Squirrel
  {
    $this->nutts->add($nutt);
    return $this;
  }
}

Entity Squirrel Is updated.
setNutts has been changed to:

public function setNutts(Collection $nutts)
{
  foreach ($nutts as $nutt)
  {
    $nutt->setSquirrel($this);
    $this->nutts->add($nutt);
  }
}

Entity Nutt

/**
 * @ORM\Entity
 * @ORM\Table(name="nutt")
 */
class Nutt {
  /**
   * @ORM\ManyToOne(targetEntity="App\Entity\Squirrel", inversedBy="nutts")
   * @ORM\Id
   */
  private $squirrel;
  /**
   * @ORM\Column(type="integer")
   * @ORM\Id
   */
  private $size;
  /**
   * @ORM\Column(type="text")
   * @Assert\NotBlank()
   */
  private $color;
  /**
   * @return Squirrel|null
   */
  public function getSquirrel(): ?Squirrel
  {
    return $this->squirrel;
  }
  /**
   * @param Squirrel|null $squirrel
   * @return $this
   */
  public function setSquirrel(?Squirrel $squirrel): self
  {
    $this->squirrel = $squirrel;
    return $this;
  }
  //getters and setters for the rest
}

Entity Nutt has been updated. Property $squirrel has its id notation removed as it is a relation:

/**
 * @ORM\ManyToOne(targetEntity="App\Entity\Squirrel", inversedBy="nutts")
 */
private $squirrel;

SquirrelController

/**
 * Squirrel controller.
 * @Route("/api", name="api_")
 */
class SquirrelController extends AbstractFOSRestController
{
  /**
   * Lists all Squirrels.
   * @Rest\Get("/squirrels")
   * @return Response
   */
  public function getSquirrelAction()
  {
    $repository = $this->getDoctrine()->getRepository(Squirrel::class);
    $squirrels = $repository->findall();
    return $this->handleView($this->view($squirrels));
  }
  /**
   * Create Squirrel.
   * @Rest\Post("/squirrel")
   *
   * @return Response
   */
  public function postSquirrelAction(Request $request)
  {
    $squirrel = new Squirrel();
    $form = $this->createForm(SquirrelType::class, $squirrel);
    $data = json_decode($request->getContent(), true);
    $form->submit($data);
    if ($form->isSubmitted() && $form->isValid()) {
      $em = $this->getDoctrine()->getManager();
      $em->persist($squirrel);
      $em->flush();
      return $this->handleView($this->view(['status' => 'ok'], Response::HTTP_CREATED));
    }
    return $this->handleView($this->view($form->getErrors()));
  }
}

And my current focus The Squirrel Form

class SquirrelType extends AbstractType
{
  public function buildForm(FormBuilderInterface $builder, array $options)
  {
    $builder
      ->add('name')
      ->add(
        'nutts',
        CollectionType::class, [
          'entry_type' => NuttType::class,
          'allow_add' => true,
          'by_reference' => false
        ])
      ->add('save', SubmitType::class);
  }
  public function configureOptions(OptionsResolver $resolver)
  {
    $resolver->setDefaults(array(
      'data_class' => Squirrel::class,
      'csrf_protection' => false
    ));
  }
}

There is a nutt form but it works fine.

Question has been solved by @mel in a comment


Solution

  • The Id annotation declares the column as a primary key, so it becomes mandatory. But it's not needed in this case, since squirrel is a relation.

    The error itself also hints at the field being null when saving the entity, so setSquirrel is not being called.

    You can remove the annotation from size as well.