Sonata 3:删除 OneToMany 关系中的实体不适用于 'by_reference' => false

Posted

技术标签:

【中文标题】Sonata 3:删除 OneToMany 关系中的实体不适用于 \'by_reference\' => false【英文标题】:Sonata 3: Deleting entities in OneToMany relationship doesn't work with 'by_reference' => falseSonata 3:删除 OneToMany 关系中的实体不适用于 'by_reference' => false 【发布时间】:2018-02-01 09:36:24 【问题描述】:

我一定错过了什么。我的相关实体是:

EducationalModule

namespace AppBundle\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Table(name="educational_module")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\CourseUnitRepository")
 *
 * Class EducationalUnit
 */
class EducationalModule

    /**
     * @ORM\Column(type="integer")
     * @ORM\Id()
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\ManyToOne(targetEntity="AppBundle\Entity\EducationalUnitCategory", inversedBy="educationalUnits")
     * @ORM\JoinColumn(name="category", referencedColumnName="course")
     */
    private $category;

    /**
     * @var string
     *
     * @ORM\Column(type="string", nullable=false)
     */
    private $name;

    /**
     * @ORM\OneToMany(targetEntity="AppBundle\Entity\Course", mappedBy="module")
     */
    private $courses;

    /**
     * @ORM\OneToMany(targetEntity="AppBundle\Entity\EducationalUnit", mappedBy="module", cascade="persist","remove", orphanRemoval=true)
     * @ORM\OrderBy("position" = "ASC")
     * @Assert\Count(min="1", minMessage="Module has to have at least one unit.")
     * @Assert\Valid()
     */
    private $units;


    public function __construct()
    
        $this->units = new ArrayCollection();
    

    /**
     * @return mixed
     */
    public function getCategory()
    
        return $this->category;
    

    /**
     * @param mixed $category
     */
    public function setCategory($category)
    
        $this->category = $category;
    

    /**
     * @return integer
     */
    public function getId()
    
        return $this->id;
    

    /**
     * @return string
     */
    public function getName() : ?string
    
        return $this->name;
    

    /**
     * @param string $name
     */
    public function setName(string $name)
    
        $this->name = $name;
    

    /**
     * @return mixed
     */
    public function getCourses()
    
        return $this->courses;
    

    /**
     * @return Collection
     */
    public function getUnits()
    
        return $this->units;
    


    public function setUnits(Collection $units)
    
        $this->units = new ArrayCollection();

        foreach ($units as $unit) 
            $this->addUnits($unit);
        

        return $this;
    


    public function addUnits(EducationalUnit $unit)
    
        $unit->setModule($this);
        $this->units->add($unit);
    

    /**
     * @param $unit string
     *
     * @return $this
     */
    public function removeUnits($unit)
    
        $this->units->removeElement($unit);

        return $this;
    

EducationalUnit

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Table(name="educational_unit")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\CourseUnitRepository")
 *
 * Class EducationalUnit
 */
class EducationalUnit

    /**
     * @ORM\Column(type="integer")
     * @ORM\Id()
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\ManyToOne(targetEntity="AppBundle\Entity\EducationalModule", inversedBy="units")
     */
    private $module;

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

    /**
     * @ORM\Column(type="integer", options="default" : 0)
     */
    private $position = 0;

    /**
     * @ORM\ManyToOne(targetEntity="AppBundle\Entity\EducationalUnitCategory", inversedBy="educationalUnits")
     * @ORM\JoinColumn(name="category", referencedColumnName="course")
     */
    private $category;

    /**
     * @var EducationalFile
     *
     * @ORM\OneToOne(targetEntity="AppBundle\Entity\EducationalFile", cascade="persist","remove", orphanRemoval=true)
     * @Assert\NotBlank()
     * @Assert\Valid()
     */
    private $file;

    /**
     * @return mixed
     */
    public function getCategory()
    
        return $this->category;
    

    /**
     * @param mixed $category
     */
    public function setCategory($category)
    
        $this->category = $category;
    

    /**
     * @return mixed
     */
    public function getModule()
    
        return $this->module;
    


    public function setModule($module)
    
        $this->module = $module;
    

    /**
     * @return mixed
     */
    public function getPosition()
    
        return $this->position;
    

    /**
     * @param mixed $position
     */
    public function setPosition($position)
    
        $this->position = $position;
    

    /**
     * @return integer
     */
    public function getId()
    
        return $this->id;
    

    /**
     * @return string
     */
    public function getName()
    
        return $this->name;
    

    /**
     * @param string $name
     */
    public function setName(string $name)
    
        $this->name = $name;
    

    /**
     * @return EducationalFile
     */
    public function getFile()
    
        return $this->file;
    

    /**
     * @param EducationalFile $file
     */
    public function setFile($file)
    
        $this->file = $file;
    

我的管理实体:

EducationalModuleAdmin

namespace AppBundle\Admin;

use AppBundle\Entity\EducationalFile;
use AppBundle\Entity\EducationalModule;
use AppBundle\Entity\EducationalUnit;
use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Form\FormMapper;

/**
 * Class EducationalModuleAdmin
 */
class EducationalModuleAdmin extends AbstractAdmin

    protected function configureFormFields(FormMapper $formMapper)
    

        $formMapper->add('name', 'text');
        $formMapper->add('units', 'sonata_type_collection', [
            'required' => true,
            'by_reference' => false, // Has to be false. Thanks to that, our children entities will receive a reference to the parent.
            'btn_add' => 'Add unit',
        ], [
            'edit' => 'inline',
            'inline' => 'table',
            'sortable' => 'position',
            'multiple' => true,
        ]);

    

    protected function configureDatagridFilters(DatagridMapper $datagridMapper)
    
        $datagridMapper->add('id');
        $datagridMapper->add('name');
    

    protected function configureListFields(ListMapper $listMapper)
    
        $listMapper->add('id');
        $listMapper->add('name');
        $listMapper->add('_action', 'actions', [
            'actions' => [
                'show' => [],
                'edit' => [],
                'delete' => [],
            ],
        ]);
    

EducationalUnitAdmin

namespace AppBundle\Admin;

use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Form\FormMapper;

/**
 * Class EducationalUnitAdmin
 */
class EducationalUnitAdmin extends AbstractAdmin

    protected $parentAssociationMapping = 'module';

    protected function configureFormFields(FormMapper $formMapper)
    
        $is_creation = !$this->id($this->getSubject());

        $formMapper->add('module', 'sonata_type_model_hidden', [
            'attr' => ['hidden' => true],
        ]);

        $formMapper->add('id', 'integer', [
            'disabled' => true,
        ]);

        $formMapper->add('name', 'text');


        $formMapper->add('file', 'sonata_type_admin', [
            'required' => $is_creation,
            'by_reference' => true,
        ], [
            'edit' => 'inline',
            //'inline' => 'table',
        ]);

        $formMapper->add('position', 'hidden', [
            'attr' => ['hidden' => true],
        ]);
    

    protected function configureDatagridFilters(DatagridMapper $datagridMapper)
    
        $datagridMapper->add('id');
    

    protected function configureListFields(ListMapper $listMapper)
    
        $listMapper->add('id');
    

我正在编辑我的EducationalModule 实体,此外,每个EducationalUnit(它们是EducationalModule 的子代)旁边都有“删除”复选框。选中复选框并更新我的EducationalModule 后,EducationalUnit 被删除。

有趣的是,当我将我的EducationalModuleAdmin 'by_reference' => false 更改为true 然后删除作品(但还有另一个问题 - 创建的孩子没有参考父母)。

我尝试非常彻底地调试它,我可以确认实体在进入 Sonata 的 ModelManager 中的 ->persist()->flush() 之前确实被修改了,但由于某种原因,孩子数量的变化不会持续存在最后。

【问题讨论】:

【参考方案1】:

尝试删除setUnits 方法。如果设置了by_reference => false,则奏鸣曲应该找到add/remove 方法。目前只使用 setUnits 方法,并且在此方法中您只设置与条目的关系,而不是解除绑定不存在的条目。

您还应该将方法重命名为 addUnitremoveUnit

【讨论】:

删除 setUnits 方法会导致 NoSuchPropertyException 在删除时显示消息:Could not determine access type for property "units".。 :( 有什么办法可以“解除绑定”在父对象中删除的对象? 好的,感谢您的帮助,我找到了解决方案,这是不同但相似的东西,而且非常荒谬。 addUnitsremoveUnits 方法应该单独命名。没错,所以它们应该是addUnitremoveUnit,因为Symfony 在它的自动魔法中实际上是在寻找我的复数属性的单数名称。我不确定我是更生气还是对此感到惊讶......

以上是关于Sonata 3:删除 OneToMany 关系中的实体不适用于 'by_reference' => false的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Sonata Admin 中正确配置“sonata_type_collection”字段

Symfony 2.1 Sonata 管理包 OneToMany

Sonata 管理包:无法删除与 sonata_type_admin 的关系

配置子管理员时的 Sonata Bundle 错误

无法在休眠中删除 OneToMany 关系的实例

Doctrine symfony使用OneToMany删除实体 - ManyToOne关系