I must be missing something. My relevant entities are:
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;
}
}
My admin entities:
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');
}
}
I'm editing my EducationalModule
entity, moreover I have "delete" checkbox next to every EducationalUnit
(which are the children of the EducationalModule
). After selecting checkbox and updating my EducationalModule
no EducationalUnit
is deleted.
What is interesting is that when I will change in my EducationalModuleAdmin
'by_reference' => false
to true
then deleting works (but there is another problem - created children do not have reference to the parent).
I tried to debug it pretty thoroughly and I can confirm that the entity is indeed modified before going into ->persist()
and ->flush()
in Sonata's ModelManager
, but for some reason, change in the number of children is not persisted in the end.
Try to remove setUnits
method. Sonata should find add/remove
methods if by_reference => false
is set. For now only the setUnits
method is using and in this method you only set relationship to the entries, not unbinding non-existent ones.
You should also rename methods to addUnit
and removeUnit
.