Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Q: Symfony4 EasyAdmin oneToMany not saving in DB

I have 2 entities, Product (Produit) and Manufacturer (Fabricant).

When I try to update them from Easy Admin, if I change the Manufacturer on the Product side, all good, it works and the Manufacturer admin does show the right amount of child products.

However, if I do it the other way around - choosing child products from the Manufacturer admin - it doesn't save it in the database. It does save the name, but not the child list.

A few topics here and there indicate that I have to make sure that in the addProduct function, I also act on the product with a $product->setManufacturer($this); ... which I did (see code below).

Others mentioned that in the admin configuration, I should put the by_reference option to false. Which I also did. Yet no success.

Another suggestion was to make sure that the cascading was right between the 2 entities, I've put it to "all" until I can figure out what's wrong, but it still doesn't work. No error message, no warning, it even saves the other fields, but not this one. Any idea ?

Product :

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="App\Repository\ProduitRepository")
 */
class Produit
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $nom;

    /**
     * @ORM\ManyToOne(targetEntity="Fabricant", inversedBy="produits", cascade="all")
     */
    private $fabricant;

    public function __toString()
    {
        return ($this->nom != null) ? $this->nom : '';
    }

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

    public function setId($id)
    {
        $this->id = $id;

        return $this;
    }

    public function getNom()
    {
        return $this->nom;
    }

    public function setNom($nom)
    {
        $this->nom = $nom;

        return $this;
    }

    public function getFabricant()
    {
        return $this->fabricant;
    }

    public function setFabricant($fabricant)
    {
        $this->fabricant = $fabricant;

        return $this;
    }
}

Manufacturer :

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;

/**
 * @ORM\Entity(repositoryClass="App\Repository\FabricantRepository")
 */
class Fabricant
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $nom;

    /**
     * @ORM\OneToMany(targetEntity="Produit", mappedBy="fabricant", cascade="all")
     */
    private $produits;

    public function __toString()
    {
        return ($this->nom != null) ? $this->nom : '';
    }

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

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

    public function setId($id)
    {
        $this->id = $id;

        return $this;
    }

    public function getNom()
    {
        return $this->nom;
    }

    public function setNom($nom)
    {
        $this->nom = $nom;

        return $this;
    }

    public function getProduits()
    {
        return $this->produits;
    }

    public function addProduit(Produit $produit)
    {
        if ($this->produits->contains($produit)) {
            return;
        }
        $this->produits[] = $produit;
        $produit->setFabricant($this);
        return $this;
    }

    public function removeProduit($produit)
    {
        $this->produits->removeElement($produit);
        $produit->setFabricant(null);
    }
}

Easy Admin yaml config :

easy_admin:
    entities:
        Produit:
            class: App\Entity\Produit
            list:
                fields:
                    - id
                    - nom
                    - fabricant
            new:
                fields:
                    - nom
                    - { property: 'fabricant', type_options: { 'by_reference': false } }
        Fabricant:
            class: App\Entity\Fabricant
            list:
                fields:
                    - id
                    - nom
                    - produits
            new:
                fields:
                    - nom
                    - { property: 'produits', type_options: { by_reference: false } }
            edit:
                fields:
                    - nom
                    - { property: 'produits', type_options: { multiple: true, by_reference: false } }
like image 726
lblessig Avatar asked Jan 28 '23 20:01

lblessig


2 Answers

Cause is bug in EasyAdmin controller which calls flush() with concrete $entity, so other changes are not persisted. You can wait for new release or extend AdminController and override methods.

Change of control class:

easy_admin_bundle:
    resource: 'App\Controller\AdminController'
    prefix: /admin
    type: annotation

Extended admin controller:

<?php
namespace App\Controller;

use EasyCorp\Bundle\EasyAdminBundle\Controller\AdminController as BaseAdminController;

class AdminController extends BaseAdminController
{
    protected function persistEntity($entity)
    {
        $this->em->persist($entity);
        $this->em->flush();
    }

    protected function updateEntity($entity)
    {
        $this->em->flush();
    }

    protected function removeEntity($entity)
    {
        $this->em->remove($entity);
        $this->em->flush();
    }
}
like image 112
Bonewolf Avatar answered Feb 02 '23 09:02

Bonewolf


The issue appears very often and I just ran into it myself. Here is my solution without overriding any controller action:

For anyone who is on EasyAdminBundle v1.17.08 - v1.17.22 you should upgrade first. Apparently Issue #1679 introduced a bug that results in this error.

The next step is to make sure the cascade option in the owning entity is set:

@ORM\OneToMany(targetEntity="PageBlock", mappedBy="page", cascade="all", orphanRemoval=true)

Also set the by_reference option in your form:

# in your easy_admin config block...
      - property: 'blocks'
        type: 'collection'
        type_options:
          entry_type: ...PageBlockType
          by_reference: false

The last but not least I was missing the add and remove methods in my parent entity. Make also sure the naming is correct. So for the property named $block use this:

public function addBlock(PageBlock $block) : void
{
    $block->setPage($this);
    $this->blocks->add($block);
}

public function removeBlock(PageBlock $block) : void
{
    $block->setPage(null);
    $this->blocks->remove($block);
} 
like image 44
ferdynator Avatar answered Feb 02 '23 08:02

ferdynator