I have a OneToOne relationship between Page
and SuperGridContent
:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="page")
* @ORM\HasLifecycleCallbacks
*/
class Page
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
//...
/**
* @ORM\OneToOne(targetEntity="AppBundle\Entity\SuperGridContent", mappedBy="page", cascade={"persist", "remove"})
*/
protected $super_grid;
//...
}
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="super_grid_content")
* @ORM\HasLifecycleCallbacks
*/
class SuperGridContent
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @ORM\OneToOne(targetEntity="AppBundle\Entity\Page", inversedBy="super_grid")
*/
private $page;
//...
}
Then in the page admin class I have:
<?php
namespace AppBundle\Admin;
use AppBundle\Entity\Page;
use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Form\Type\ModelType;
class PageAdmin extends AbstractAdmin
{
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('super_grid', ModelType::class, [
'label' => 'Super Grid',
'required' => false,
])
// ...
;
}
// ...
}
The problem is that when I create or edit a Page
, the super_grid
field is a select tag with all existing SuperGridContent
as options. Even when they already have a Page
relationship. If I select one of those, of course it fails, because the relationship is suposed to be unique.
Am I missing something or is there a way Sonata could handle it?
I'm using SonataAdminBundle 3.4
If I understand correctly you problem, I think you could use Symfony Entity Type instead of the Model Type as stated in Sonata documentation.
But I also think that your life would be much easier if you inverted the ownership of the OneToOne relationship. In your example SuperGridContent
is owning the relationship so when you want to update the super_grid
of a Page
you might run into a constraint violation. If you change inversedBy
to mappedBy
in the SuperGridContent
class and mappedBy
to inversedBy
in the Page
class (+ if you regenerate your tables and make sure the cascading logic fits your needs) you should be OK.
If I understand well, you want any new Page
to have only the available super_grid
s as choices and any existing Page
to have its current super_grid
+ all the available super_grid
s as choices.
Then something like the following would do the job (careful that I am using Symfony 4.4, there might be slight syntax differences with your implementation):
namespace App\Admin;
use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Form\FormMapper;
//use Sonata\AdminBundle\Form\Type\ModelType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Doctrine\ORM\EntityRepository;
use App\Entity\Page;
use App\Entity\SuperGridContent;
final class PageAdmin extends AbstractAdmin
{
protected function configureFormFields(FormMapper $form): void
{
$subject = $this->getSubject();
if($subject->getId()){
$form
->add('super_grid', EntityType::class, [
'class' => SuperGridContent::class,
'query_builder' => function (EntityRepository $er) use($subject) {
return $er->createQueryBuilder('s')
->leftjoin('s.page', 'p')
->where('p.super_grid is null')
->orWhere('p = :my_page')
->setParameter('my_page', $subject)
;
},
]);
}
else{
$form
->add('super_grid', EntityType::class, [
'class' => SuperGridContent::class,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('s')
->leftjoin('s.page', 'p')
->where('p.super_grid is null')
;
},
]);
}
}
}