Tengo una relación OneToOne entre Page y 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; //... }Luego, en la clase de administración de la página tengo:
<?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, ]) // ... ; } // ... } El problema es que cuando creo o edito una Page , el campo super_grid es una etiqueta de selección con todo el SuperGridContent existente como opciones. Incluso cuando ya tienen una relación con Page . Si selecciono uno de esos, por supuesto que falla, porque se supone que la relación es única.
¿Me estoy perdiendo algo o hay alguna forma en que Sonata pueda manejarlo?
Estoy usando SonataAdminBundle 3.4
Si entiendo correctamente su problema, creo que podría usar Symfony Entity Type en lugar del Model Type como se indica en la documentación de Sonata.
Pero también creo que tu vida sería mucho más fácil si invirtieras la propiedad de la relación OneToOne. En su ejemplo SuperGridContent posee la relación, por lo que cuando desee actualizar la super_grid de una Page , es posible que se encuentre con una violación de restricción. Si cambia inversedBy a mappedBy en la clase SuperGridContent y mappedBy a inversedBy en la clase Page (+ si regenera sus tablas y se asegura de que la lógica en cascada se ajuste a sus necesidades), debería estar bien.
Si entiendo bien, desea que cualquier Page nueva tenga solo las super_grid disponibles como opciones y cualquier Page existente tenga su super_grid actual + todas las super_grid disponibles como opciones.
Entonces, algo como lo siguiente haría el trabajo (cuidado, estoy usando Symfony 4.4, puede haber ligeras diferencias de sintaxis con su implementación):
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') ; }, ]); } } }