I have this error when I try to render the form AvisType
:
The form's view data is expected to be a "App\Entity\DTO\DocumentDTO", but it is a "array". You can avoid this error by setting the "data_class" option to null or by adding a view transformer that transforms "array" to an instance of "App\Entity\DTO\DocumentDTO".
I have an AvisType
form:
<?php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use App\Entity\DTO\AvisDTO;
use App\Entity\DTO\NomenclatureDTO;
use App\Entity\DTO\DataFormMapper\DataAvis;
use App\Entity\DTO\DocumentDTO;
class AvisType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('nomAuteur', TextType::class, [
'label' => "Nom",
])
->add('prenomAuteur', TextType::class, [
'label' => "Prénom"
])
->add('boEstTacite', ChoiceType::class, [
'required' => true,
'label' => "L'avis est-il tacite ?",
'label_attr' => [
'class' => "font-weight-bold"
],
'expanded' => true,
'multiple' => false,
'choices' => [
'Oui' => true,
'Non' => false,
],
])
->add('nomNatureAvisRendu', ChoiceType::class, [
'label' => "Nature de l'avis rendu",
'required' => false,
'choices' => $options['dataPostAvis']->nomNatureAvisRendu,
'choice_label' => function ($choice) {
return $choice->libNom;
},
'setter' => function (AvisDTO &$avisDto, NomenclatureDTO $nomenclatureDto) {
if ($nomenclatureDto) {
$avisDto->nomNatureAvisRendu = $nomenclatureDto->idNom;
}
}
])
->add('nomTypeAvis', ChoiceType::class, [
'label' => "Type d'avis",
'required' => false,
'choices' => $options['dataPostAvis']->nomTypeAvis,
'choice_label' => function ($choice) {
return $choice->libNom;
},
'setter' => function (AvisDTO &$avisDto, NomenclatureDTO $nomenclatureDto) {
if ($nomenclatureDto) {
$avisDto->nomTypeAvis = $nomenclatureDto->idNom;
}
}
])
->add('documents', CollectionType::class, [
'entry_type' => DocumentType::class,
'entry_options' => [
'data' => $options,
],
'prototype' => true,
'allow_add' => true,
'by_reference' => false,
])
->add('txAvis', TextareaType::class, [
'required' => true,
'attr' => [
'placeholder' => "Avis favorable avec prescriptions. \nPremière prescription : Les volets doivent être en bois"
]
])
->add('txFondementAvis', TextareaType::class, [
'attr' => [
'placeholder' => "L'avis de l'ABF est rendu en application de l'article R. 425-30 du Code de l'urbanisme."
]
])
->add('txHypotheses', TextareaType::class, [
'attr' => [
'placeholder' => "Dans l'hypothèse où la puissance électrique nécessaire est de x alors le coût de raccordement est de y"
]
])
->add('txQualiteAuteur', TextareaType::class, [
'attr' => [
'placeholder' => "Qualité"
]
])
->add('Envoyer', SubmitType::class);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => AvisDTO::class,
'dataPostAvis' => DataAvis::class,
]);
}
}
This is my AvisDTO
class:
<?php
namespace App\Entity\DTO;
use Symfony\Component\Serializer\Annotation\Groups;
use Doctrine\Common\Collections\ArrayCollection;
class AvisDTO
{
public string $dtAvis;
public string $dtEmission;
public string $idActeurAuteur;
public string $nomAuteur;
public string $prenomAuteur;
public bool $boEstTacite;
public ArrayCollection $documents;
public string $idConsultation;
public array $idsPieces;
public int $nomNatureAvisRendu;
public int $nomTypeAvis;
public string $txAvis;
public string $txFondementAvis;
public string $txHypotheses;
public string $txQualiteAuteur;
public function __construct()
{
$this->documents = new ArrayCollection();
}
}
And finally here is my DocumentType
:
<?php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Constraints\File;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use App\Entity\DTO\DocumentDTO;
use App\Entity\DTO\NomenclatureDTO;
class DocumentType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
// dd($options);
$builder
->add('nomTypeDocument', ChoiceType::class, [
'label' => "Type de document",
'required' => false,
'choices' => $options['data']['dataPostAvis']->nomTypeDocument,
'choice_label' => function ($choice) {
return $choice->libNom;
},
'setter' => function(DocumentDTO &$documentDto, NomenclatureDTO $nomenclatureDto) {
if ($nomenclatureDto) {
$documentDto->nomTypeDocument = $nomenclatureDto->idNom;
}
}
])
->add('nomTypeProducteurDoc', ChoiceType::class, [
'label' => "Type de producteur du document",
'required' => false,
'choices' => $options['data']['dataPostAvis']->nomTypeProducteurDoc,
'choice_label' => function ($choice) {
return $choice->libNom;
},
'setter' => function(DocumentDTO &$documentDto, NomenclatureDTO $nomenclatureDto) {
if ($nomenclatureDto) {
$documentDto->nomTypeProducteurDoc = $nomenclatureDto->idNom;
}
}
])
->add('upload_file', FileType::class, [
'label' => false,
'mapped' => false,
'attr' => [
'data-dossier-target' => 'fileName',
'data-action' => 'change->dossier#getFileName'
],
'constraints' => [
new File([
'mimeTypes' => [
'application/pdf', //PDF
'application/msword', //DOC
'application/vnd.openxmlformats-officedocument.wordprocessingml.document', //DOCX
'application/vnd.oasis.opendocument.spreadsheet', //ODS
'application/vnd.oasis.opendocument.text', //ODT
'application/vnd.ms-excel', //XLS
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', //XLSX
'image/bmp', //BMP
'image/x-ms-bmp', //BMP
'image/png', //PNG
'image/gif', //GIF
'image/tiff', //TIF & TIFF
'image/jpeg', //JPEG & JPG
'image/dib', //DIB
],
'mimeTypesMessage' => "Ce document n'est pas valide.",
])
]
])
->add('fileId', HiddenType::class)
->add('folderId', HiddenType::class)
->add('dtProduction', HiddenType::class)
->add('idActeurProducteur', HiddenType::class)
->add('idsPersonnesProductrices', HiddenType::class);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => DocumentDTO::class,
'dataPostAvis' => DataAvisType::class,
]);
}
}
And the DocumentDTO
:
<?php
namespace App\Entity\DTO;
class DocumentDTO
{
public string $fileId;
public string $folderId;
public string $dtProduction;
public string $idActeurProducteur;
public array $idsPersonnesProductrices;
public int $nomTypeDocument;
public int $nomTypeProducteurDoc;
}
My controller:
$avis = new AvisDTO();
$avis->idConsultation = $idConsultation;
$avis->idActeurAuteur = $idActeurAppelant; // A verifier
$avis->idsPieces = $idPiecesList;
$form = $this->createForm(AvisType::class, $avis, $options);
$form->handleRequest($request);
So what I understand is that Symfony can't create the DocumentType
form because it receives an array from AvisType
.
I need this nested form to be "dynamic", my user must be able to add new Document to the AvisType
.
I've been following this documentation: https://symfony.com/doc/current/form/form_collections.html#allowing-new-tags-with-the-prototype
How shall I proceed to solve this error without setting my DocumentType
data_class
to null
?
You can add methods in your DocumentDTO class to transform to/from array. If you do this then that class would look something like this:
<?php
namespace App\Entity\DTO;
class DocumentDTO
{
public string $fileId;
public string $folderId;
public string $dtProduction;
public string $idActeurProducteur;
public array $idsPersonnesProductrices;
public int $nomTypeDocument;
public int $nomTypeProducteurDoc;
public static function fromArray($data): self {
$dto = new self();
$dto->fileId = $data['fileId'] ?? null;
$dto->folderId = $data['folderId'] ?? null;
$dto->dtProduction = $data['dtProduction'] ?? null;
$dto->idActeurProducteur = $data['idActeurProducteur'] ?? null;
$dto->idsPersonnesProductrices = $data['idsPersonnesProductrices'] ?? null;
$dto->nomTypeDocument = $data['nomTypeDocument'] ?? null;
$dto->nomTypeProducteurDoc = $data['nomTypeProducteurDoc'] ?? null;
return $dto;
}
public function toArray(): array
{
return [
'fileId' => $this->fileId,
'folderId' => $this->folderId,
'dtProduction' => $this->dtProduction,
'idActeurProducteur' => $this->idActeurProducteur,
'idsPersonnesProductrices' => $this->idsPersonnesProductrices,
'nomTypeDocument' => $this->nomTypeDocument,
'nomTypeProducteurDoc' => $this->nomTypeProducteurDoc,
];
}
}
In your DocumentType class you can add a viewTransformer in which you use the methods from your dto class to convert to/from the data object. If you do this the App\Form\DocumentType will look something like this:
<?php
namespace App\Form;
use App\DataObject\PaymentProviderCredentialsDto;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\CallbackTransformer;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Constraints\File;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use App\Entity\DTO\DocumentDTO;
use App\Entity\DTO\NomenclatureDTO;
class DocumentType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
// dd($options);
$builder
->add('nomTypeDocument', ChoiceType::class, [
'label' => "Type de document",
'required' => false,
'choices' => $options['data']['dataPostAvis']->nomTypeDocument,
'choice_label' => function ($choice) {
return $choice->libNom;
},
'setter' => function(DocumentDTO &$documentDto, NomenclatureDTO $nomenclatureDto) {
if ($nomenclatureDto) {
$documentDto->nomTypeDocument = $nomenclatureDto->idNom;
}
}
])
->add('nomTypeProducteurDoc', ChoiceType::class, [
'label' => "Type de producteur du document",
'required' => false,
'choices' => $options['data']['dataPostAvis']->nomTypeProducteurDoc,
'choice_label' => function ($choice) {
return $choice->libNom;
},
'setter' => function(DocumentDTO &$documentDto, NomenclatureDTO $nomenclatureDto) {
if ($nomenclatureDto) {
$documentDto->nomTypeProducteurDoc = $nomenclatureDto->idNom;
}
}
])
->add('upload_file', FileType::class, [
'label' => false,
'mapped' => false,
'attr' => [
'data-dossier-target' => 'fileName',
'data-action' => 'change->dossier#getFileName'
],
'constraints' => [
new File([
'mimeTypes' => [
'application/pdf', //PDF
'application/msword', //DOC
'application/vnd.openxmlformats-officedocument.wordprocessingml.document', //DOCX
'application/vnd.oasis.opendocument.spreadsheet', //ODS
'application/vnd.oasis.opendocument.text', //ODT
'application/vnd.ms-excel', //XLS
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', //XLSX
'image/bmp', //BMP
'image/x-ms-bmp', //BMP
'image/png', //PNG
'image/gif', //GIF
'image/tiff', //TIF & TIFF
'image/jpeg', //JPEG & JPG
'image/dib', //DIB
],
'mimeTypesMessage' => "Ce document n'est pas valide.",
])
]
])
->add('fileId', HiddenType::class)
->add('folderId', HiddenType::class)
->add('dtProduction', HiddenType::class)
->add('idActeurProducteur', HiddenType::class)
->add('idsPersonnesProductrices', HiddenType::class);
$builder->addViewTransformer(new CallbackTransformer(
function ($documentDto) {
if (!is_array($documentDto)) {
return $documentDto;
}
return DocumentDTO::fromArray($documentDto);
},
function ($documentDto) {
if (!$documentDto instanceof DocumentDTO) {
return $documentDto;
}
return $documentDto->toArray();
}
));
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => DocumentDTO::class,
'dataPostAvis' => DataAvisType::class,
]);
}
}
Now every time the form view is loaded the transformer will be called and your data will be transformed in the correct format. You should change the transform methods which I suggested to better suit your project's needs.