Search code examples
phprestdoctrine-ormsymfony5fosrestbundle

Symfony5: save related (oneToMany) entity with REST API


I have two entity classes: Product and Price. Product has many Prices. The relations are defined as (only relevant code is shown):

Product.php

public function __construct()
{
    $this->prices = new ArrayCollection();
}

/**
 * @var Collection|Price[]
 * @ORM\OneToMany(targetEntity="Price", mappedBy="product", cascade={"persist", "remove"})
*/
private Collection $prices;

/**
 * @return Collection|Price[]
 */
public function getPrices()
{
    return $this->prices;
}

/**
 * @param Collection|Price[] $prices
 */
public function setPrices(Collection $prices) : void
{
    $this->prices = $prices;
}

Price.php

/**
 * @ORM\ManyToOne(targetEntity="Product", inversedBy="prices")
*/
private Product $product;

public function getProduct() : Product
{
    return $this->product;
}

public function setProduct(?Product $product) : self
{
    $this->product = $product;
    return $this;
}

ProductType::buildForm()

$builder
->add('prices', EntityType::class, [
    'class' => Price::class,
    'multiple' => true,
    'constraints' => [
        new NotNull(),
    ],
]);

ProductController::add()

public function add(Request $request, EntityManagerInterface $manager) : Response
{
    $form = $this->container->get('form.factory')->createNamed('', ProductType::class);
    $form->handleRequest($request);

    if (! $form->isSubmitted() || !$form->isValid()) {
        return $this->handleView($this->view($form, Response::HTTP_BAD_REQUEST));
    }

    /** @var Product $product */
    $product = $form->getData();

    $manager->persist($product);
    $manager->flush();

    return $this->respond($product, Response::HTTP_CREATED);
}

Request JSON

{
  "name": "added",
  "prices": [
        {
        "currency": "EUR",
        "value": 100
        },
        {
        "currency": "PLN",
        "value": 400
        }
    ],
  "description": "test desc"
}

JSON Response

{
  "code": 400,
  "message": "Validation Failed",
  "errors": {
    "children": {
      "name": {},
      "description": {},
      "prices": {
        "errors": [
          "Not valid"
        ]
      }
    }
  }
}

The problem is specifically with prices - passing empty prices array creates the product without errors. I use FOSRestBundle.

I've been Googling many hours, but no success. I'm new to Symfony, so it's likely that I miss something obvious :)


Solution

  • The problem was in the ProductType class.

    Instead of:

    $builder
    ->add('prices', EntityType::class, [
        'class' => Price::class,
        'multiple' => true,
        'constraints' => [
            new NotNull(),
        ],
    ]);
    

    one should use

    $builder
    ->add('prices', CollectionType::class, [
        'entry_type' => PriceType::class,
        'allow_add' => true,
        'constraints' => [
            new NotNull(),
        ],
    ]);
    

    Answer found here