Nous allons voir comment exploiter les types de champs Jsonb de postgreSql dans une interface générée de sonata.

Nous partons du postulat que vous maîtrisez déjà les concepts basiques de Symfony, Sonata, et PostgreSql.

Tout d’abord créons une table toute simple dans postgreSql qui contiendra un champs de type Jsonb.

Sélection_161
CREATE SEQUENCE public.table1_id_seq;
CREATE TABLE public.table1 (
    id integer DEFAULT nextval('public.table1_id_seq'::regclass) NOT NULL,
    var1 character varying(250),
    var2 jsonb
);
ALTER TABLE ONLY public.table1
    ADD CONSTRAINT table1_pkey PRIMARY KEY (id);


Il faut ensuite générer l’entity et son interface d’admin

php bin/console doctrine:mapping:import "App\Entity" annotation --path=src/Entity
php bin/console make:entity --regenerate App
php bin/console make:sonata:admin App/Entity/Table1

On remarque le type de champ dans notre entity

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * Table1
 *
 * @ORM\Table(name="table1")
 * @ORM\Entity
 */
class Table1
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer", nullable=false)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="SEQUENCE")
     * @ORM\SequenceGenerator(sequenceName="table1_id_seq", allocationSize=1, initialValue=1)
     */
    private $id;

    /**
     * @var string|null
     *
     * @ORM\Column(name="var1", type="string", length=250, nullable=true)
     */
    private $var1;

    /**
     * @var json|null
     *
     * @ORM\Column(name="var2", type="json", nullable=true)
     */
    private $var2;

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

    public function getVar1(): ?string
    {
        return $this->var1;
    }

    public function setVar1(?string $var1): self
    {
        $this->var1 = $var1;

        return $this;
    }

    public function getVar2(): ?array
    {
        return $this->var2;
    }

    public function setVar2(?array $var2): self
    {
        $this->var2 = $var2;

        return $this;
    }


}

Et notre interface d’admin générée dans son format le plus simple

<?php

declare(strict_types=1);

namespace App\Admin;

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

final class Table1Admin extends AbstractAdmin
{

    protected function configureDatagridFilters(DatagridMapper $datagridMapper): void
    {
        $datagridMapper
            ->add('id')
            ->add('var1')
            ->add('var2')
            ;
    }

    protected function configureListFields(ListMapper $listMapper): void
    {
        $listMapper
            ->add('id')
            ->add('var1')
            ->add('var2')
            ->add('_action', null, [
                'actions' => [
                    'show' => [],
                    'edit' => [],
                    'delete' => [],
                ],
            ]);
    }

    protected function configureFormFields(FormMapper $formMapper): void
    {
        $formMapper
            ->add('id')
            ->add('var1')
            ->add('var2')
            ;
    }

    protected function configureShowFields(ShowMapper $showMapper): void
    {
        $showMapper
            ->add('id')
            ->add('var1')
            ->add('var2')
            ;
    }
}

Notre objectif maintenant est de créer une imbrication de formulaire pour gérer notre data en json.
Pour cela nous allons utiliser les collections de symfony.
Les étapes sont les suivantes, nous allons créer un formulaire qui sera intégré au champ. Il auras un bouton ajouter/supprimer pour ajouter des items à notre collection.
Nous allons ensuite lier notre formulaire à notre champs « var2 ».

Nous créons notre formulaire dans le répertoire Form de src.

<?php #src/Form/fieldvar2.php

namespace App\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type as FormType;

class fieldvar2 extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('var2_titre', FormType\TextType::class, [
                'label' => 'Var2 titre',
            ])
            ->add('var2_valeur', FormType\TextType::class, [
                'label' => 'var2 valeur',
            ]);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
    }
}

?>

Il faut ensuite lier ce formulaire à notre champ.

use Symfony\Component\Form\Extension\Core\Type as FormType;
use App\Form\fieldvar2;

final class Table1Admin extends AbstractAdmin
{

    protected function configureFormFields(FormMapper $formMapper): void
    {
        $formMapper
            //->add('id') #on supprime le champs id puisqu'il est auto incrémenté
            ->add('var1')
            ->add('var2', FormType\CollectionType::class, [
                'allow_add' => true,
                'allow_delete' => true,
                'entry_type' => 'App\\Form\\fieldvar2',
                'label' => 'Var jsonb',
            ])
            ;
        
           
            
    }
jsonb-insert

Maintenant nous allons voir comment nous pourrions avoir une imbrication de formulaires dans notre formulaire. En gros des « nested collection ».
Et pour cela il va falloir utiliser un composant Collection de Sonata car c’est le seul qui va fonctionner en imbrications multiples (en théorie, infinie).

On va rajouter la référence à Sonata\AdminBundle\Form\Type\CollectionType, créer un deuxième formulaire et l’implémenter dans notre premier formulaire.

<?php #src/Form/fieldvar2valeur.php

namespace App\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type as FormType;

class fieldvar2valeur extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('valeurs_multiples', FormType\TextType::class, [
                'label' => 'Netsted 2 val',
            ]);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'allow_extra_fields' => true,
            'allow_add' => true,
        ]);
    }
    
    
}

?>

Notre implémentation de la collection de niveau 2

<?php #src/Form/fieldvar2.php

namespace App\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type as FormType;
use App\Form\fieldvar2valeur;
use Sonata\AdminBundle\Form\Type\CollectionType;

class fieldvar2 extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        
        
       $builder->add('var2_titre', FormType\TextType::class, [
            'label' => 'Var2 titre',
        ])
        ->add('var2_valeur', CollectionType::class, [
                'allow_add' => true,
                'allow_delete' => true,
                'entry_type' => 'App\\Form\\fieldvar2valeur',
                'label' => 'Data Nested 2',
            ])
       ;
          


    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'allow_extra_fields' => true,
            'allow_add' => true,
        ]);
    }
}

?>

Et il ne nous reste plus qu’à modifier la configuration de notre collection pour utiliser le composant de SonataAdmin

<?php

declare(strict_types=1);

namespace App\Admin;

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

use App\Form\fieldvar2;


use Sonata\AdminBundle\Form\Type\CollectionType;

final class Table1Admin extends AbstractAdmin
{

    protected function configureDatagridFilters(DatagridMapper $datagridMapper): void
    {
        $datagridMapper
            ->add('id')
            ->add('var1')
            ->add('var2')
            ;
    }

    protected function configureListFields(ListMapper $listMapper): void
    {
        $listMapper
            ->add('id')
            ->add('var1')
            ->add('var2')
            ->add('_action', null, [
                'actions' => [
                    'show' => [],
                    'edit' => [],
                    'delete' => [],
                ],
            ]);
    }

    protected function configureFormFields(FormMapper $formMapper): void
    {
        $formMapper
            //->add('id')
            ->add('var1')
            ->add('var2', CollectionType::class, [
                'allow_add' => true,
                'allow_delete' => true,
                'entry_type' => 'App\\Form\\fieldvar2',
                'label' => 'Var jsonb',
            ])
            ;
        
            
            
    }

    protected function configureShowFields(ShowMapper $showMapper): void
    {
        $showMapper
            ->add('id')
            ->add('var1')
            ->add('var2')
            ;
    }
}

Ce qui donne ceci

jsonb-insert-nested2