Vamos a ver cómo explotar los tipos de campo Jsonb de PostgreSQL en una interfaz generada de Sonata.
Comenzamos con el supuesto de que ya dominas los conceptos básicos de Symfony, Sonata y PostgreSQL.
Primero, creemos una tabla simple en PostgreSQL que contendrá un campo Jsonb.
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);
Luego, genera la entidad y su interfaz de administración
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
Notamos el tipo de campo en nuestra entidad
<?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;
}
}
Y nuestra interfaz de administración generada en su formato más 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')
;
}
}
Nuestro objetivo ahora es crear una estructura de formulario anidado para gestionar nuestros datos en json.
Para esto, utilizaremos las colecciones de Symfony.
Los pasos son los siguientes: vamos a crear un formulario que se integrará en el campo. Tendrá un botón de añadir/eliminar para agregar elementos a nuestra colección.
Luego vincularemos nuestro formulario a nuestro campo "var2".
Creamos nuestro formulario en el directorio 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)
{
}
}
?>
Luego, necesitamos vincular este formulario a nuestro campo.
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',
])
;
}
Ahora vamos a ver cómo podríamos tener una anidación de formularios dentro de nuestro formulario. En otras palabras, "colecciones anidadas".
Y para esto, tendremos que usar un componente de Colección de Sonata porque es el único que funcionará con múltiples anidaciones (teóricamente, infinitamente).
Añadiremos la referencia a Sonata\AdminBundle\Form\Type\CollectionType, crearemos un segundo formulario y lo implementaremos en nuestro primer formulario.
<?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,
]);
}
}
?>
Nuestra implementación de la colección de nivel 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,
]);
}
}
?>
Y todo lo que queda es modificar la configuración de nuestra colección para usar el componente 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')
;
}
}
Así es como se ve