symfony custom form field not valid - php

I am using symfony 6 with everything updated to date.
I created a form type with which I intend to receive a url or an image and internally transform the image into a url that will indicate where it is stored on the server, or keep the given url. The idea is to be able to receive one or the other (url or file) and only return as data the new url.
Here is my code:
parent form
class EditProfileForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('imgProfile', ImageType::class, [
'required' => false,
'label' => "Profile Image",
'bucket' => 'tw-user-files',
'key' => $options["key"],
'constraints' => [
new File([
'maxSize' => '7M',
'mimeTypes' => [
'image/*'
],
'mimeTypesMessage' => 'That is not a valid image format',
])
],
'setter' => function (User &$user, string $data, FormInterface $form): void {
$user->setImgProfile($data);
}
])
;
}
}
ImageType
class ImageType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$bucketName = $options["bucket"];
$bucket = $this->flySystemS3->getBucket($bucketName);
$constraints = $options["constraints"];
$builder->add('url', UrlType::class, [
'error_bubbling' => false,
'required' => false,
])
->add('file', FileType::class, [
"error_bubbling" => false,
"multiple" => false,
'required' => false,
'attr' => [
'accept' => 'image/*'
],
'constraints' => $constraints
])
->addViewTransformer(new UrlOrFileToImage($bucket, $options["key"]));
}
}
Data tranformer
class UrlOrFileToImage implements DataTransformerInterface
{
public function transform($value)
{
return [
"url" => "",
"file" => null
];
}
public function reverseTransform($value)
{
if (!empty($value["url"])) {
return $value;
}
if (!empty($value["file"])) {
return $this->fileToImage($value["file"]);
}
return "#";
}
private function fileToImage(UploadedFile $file): string
{
$fileName = $this->uploadToServer();
return $fileName;
}
}
the situation is that when validating the parent form I get the error "The file could not be found.", this only happens when validating the parent form, the child form "ImageType" passes all validations and does not generate error.
I debugged it step by step and when I debug it, it passes correctly, the validation is correct and returns everything ok. So I think it is some validation that takes too long, and at the time of debugging gives it a chance to validate it, and when I let it run the code, the validation fails.

Related

Export form data to dompdf

for a few days I am blocked, if someone can help me with the good practice, I explain:
I want to retrieve data from the database: entity (expression_besoin) contains the information of a purchase with ref , requester ... and the entity besoin_article contains the items products in the previous entity, there is a OneToMany relationship between the two entities.
the objective is to retrieve the several expression needs of articles in a form in order to modify whether the quantities are necessary or to delete them, then to be able to generate a PDF file which contains all the needs and the articles which correspond to them.
Controller :
/**
* #Route("/extraire_commandes", name="export_commandes", methods={"GET","POST"})
*/
public function exportCommandes(ExBesoinRepository $exBesoinRepository, Request $request): Response
{
$datas = new FiltreBesoin();
$form = $this->createForm(FiltreBesoinType::class, $datas);
$form->handleRequest($request);
$exBesoin = $exBesoinRepository->exportCommande($datas);
$valueFournisseur = $form['fournisseur']->getData();
$formExport = $this->createFormBuilder(array('besoin' => $exBesoin));
$formExport->add('besoin', CollectionType::class, array(
'entry_type' => ExBesoinToCommandeType::class,
));
$formExport = $formExport->getForm();
$formExport->handleRequest($request);
$formValue = $formExport->getData();
if ($formExport->isSubmitted() && $formExport->isValid()) {
$html = $this->renderView('admin/besoins/epicerie/exportPdf.html.twig', [
'besoins' => $formValue,
]);
$html .= '<link rel="stylesheet" href="/build/css/app.css"> ';
$html .= '<script src="/build/vendors~js/app.js"></script><script src="/build/runtime.js"></script><script src="/build/vendors-node_modules_popperjs_core_lib_index_js-node_modules_symfony_stimulus-bridge_dist_ind-f4bfca.js"></script>';
$name = 'test';
$options = new Options();
$options->set('isHtml5ParserEnabled', true);
$options->set('isRemoteEnabled', true);
$dompdf = new Dompdf($options);
$dompdf->loadHtml($html);
// (Optional) Setup the paper size and orientation
$dompdf->setPaper('A4', 'portrait');
// Render the HTML as PDF
$dompdf->render();
// Output the generated PDF to Browser
$dompdf->stream($name, array('Attachment' => 0));
return new Response('', 200, [
'Content-Type' => 'application/pdf',
]);
}
return $this->render('admin/besoins/epicerie/export.html.twig', [
'besoins' => $exBesoin,
'form' => $form->createView(),
'valueFournisseur' => $valueFournisseur,
//'idBesoin' => $idBesoin,
'formExport' => $formExport->createView(),
'valu'=> $formValue
]);
}
FormType
class ExBesoinToCommandeType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('besoinArs', CollectionType::class, [
'entry_type' => BesoinArToCommandeType::class,
'allow_delete' => true,
//'allow_add' => false,
'prototype' => true,
'by_reference' => false,
'delete_empty' => true,
'entry_options' => [
'label' => true,
]
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => ExBesoin::class,
]);
}
public function getBlockPrefix()
{
return 'ExBesoinToCommandeType';
}
}
class BesoinArToCommandeType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('article', EntityType::class, [
'class' => Article::class,
'label' => false,
'attr' =>[
'class' => 'arSelect form-control-sm',
'data-live-search' => 'true',
],
'placeholder' => 'Selectionner un article ',
'query_builder' => function (EntityRepository $er) {
$qb = $er->createQueryBuilder('a')
->innerJoin('a.category', 'c')
->where('c.is_legume = 0')
;
return $qb;
},
'group_by' => function (Article $article) {
return $article->getCategory()->getLibele();
}
])
->add('quantity', TextType::class, [
'label' => ' ',
'attr' =>[
'class' => 'quantity form-control-sm'
]
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => BesoinAr::class,
]);
}
public function getBlockPrefix()
{
return 'BesoinArToCommandeType';
}
}
Until I get my data back without problem, the getData() of the form contains all my data before the submit, but when I validate to generate my pdf file, it does not contain all the data. I use a filter system that allows me to select the expressions of need between two dates, by requester or by supplier, and the problem is linked to this filter because when I generate the pdf of the data without filter it works but when I filter my data this is where my data changes and I don't recover the filtered data, I will be grateful if someone unblocks me or tell me where the problem comes from.

Symfony 6 prefill field in dynamic form type

I am making a dynamic form but I can not prefill a field according to the value of the previous field. If the name equals jean i want to add a color field and I want to prefill the jean's favorite color by example...
class TestEventType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('name', ChoiceType::class, [
'choices' => [
'jean' => 'jean',
'pierre' => 'pierre',
'marie' => 'marie',
],
]);
$builder->get('name')->addEventListener(FormEvents::PRE_SUBMIT, [$this, 'addColor']);
}
public function addColor(FormEvent $event)
{
$form = $event->getForm();
$data = $event->getData();
$parentForm = $form->getParent();
if ($data === 'jean') {
$builder = $parentForm->getConfig()
->getFormFactory()
->createNamedBuilder('color', TextType::class, null, [
'auto_initialize' => false,
'required' => false,
// 'empty_data' => wrong behavior
]);
$parentForm->add($builder->getForm());
}
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => TestObject::class,
]);
}
}
If I use empty_data, it puts the value in the field, OK. The problem is that if I want to submit a form with this empty field, it will take the value of empty_data, which is incorrect.
I try data => 'red' but it doesn't prefill the field. I try 'blue' in the third param of the createNamedBuilder method but nothing too.
To add data on a field you can use the value attribute, as example
$builder->add('body', TextType::class, [
'attr' => ['value' => 'red'],
]);

Symfony : how to set a file type in an edit form?

I have this error when I want to edit an "event" in my form with symfony :
"The form's view data is expected to be a "Symfony\Component\HttpFoundation\File\File", but it is a "string". You can avoid this error by setting the "data_class" option to null or by adding a view transformer that transforms "string" to an instance of "Symfony\Component\HttpFoundation\File\File"."
I follow the documentation and adding this in my controller :
$event->setEventImage(
new File($this->getParameter('images_dir').'/'.$event->getEventImage())
);
But it does not change anything I don't understand why.
So there is my entity :
/**
*
* #ORM\Column (type="string")
* #Assert\NotBlank(message="Merci de charger un fichier au format image.")
* #Assert\File(mimeTypes={ "image/*" })
*/
private $eventImage;
public function getEventImage(): ?string
{
return $this->eventImage;
}
public function setEventImage(string $eventImage): self
{
$this->eventImage = $eventImage;
return $this;
}
And my Controller :
#[Route('/{id}/edit', name: 'events_edit', methods: ['GET','POST'])]
public function edit(Request $request, Events $event, SluggerInterface $slugger): Response
{
$event->setEventImage(
new File($this->getParameter('images_dir').'/'.$event->getEventImage())
);
$form = $this->createForm(EventsType::class, $event);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
/** #var UploadedFile $eventImage */
$eventImage = $form->get('eventImage')->getData();
// this condition is needed because the 'eventImage' field is not required
// so the Image file must be processed only when a file is uploaded
if ($eventImage) {
$originalFilename = pathinfo($eventImage->getClientOriginalName(), PATHINFO_FILENAME);
// this is needed to safely include the file name as part of the URL
$safeFilename = $slugger->slug($originalFilename);
$newFilename = $safeFilename . '-' . uniqid() . '.' . $eventImage->guessExtension();
// Move the file to the directory where images are stored
try {
$eventImage->move(
$this->getParameter('images_dir'),
$newFilename
);
} catch (FileException $e) {
// ... handle exception if something happens during file upload
}
// updates the 'eventImage' property to store the image file name
// instead of its contents
$event->setEventImage($newFilename);
}
$this->getDoctrine()->getManager()->flush();
return $this->redirectToRoute('events_index', [], Response::HTTP_SEE_OTHER);
}
return $this->renderForm('events/edit.html.twig', [
'event' => $event,
'form' => $form,
]);
}
And my EventsType :
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('title')
->add('subtitle')
->add('content', CKEditorType::class, [
'label' => 'content',
'required' => false,
])
->add('dateEvent', DateTimeType::class, [
'widget' => 'single_text',
'label' => 'Date d\'arrivée : '
])
->add('place')
->add('dateDead', DateType::class, [
'widget' => 'single_text',
'label' => 'Date limite d\'inscription : '
])
->add('numberPersonRestricted')
->add('eventImage', FileType::class, [
'label' => 'Image (fichier image)',
'required' => false,
'constraints' => [
new File([
'maxSize' => '1024k',
'mimeTypes' => [
'image/*',
],
'mimeTypesMessage' => 'Please upload a valid image',
])
]
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Events::class,
]);
}
->add('eventImage', FileType::class, [
'label' => 'Image (fichier image)',
'required' => false,
'constraints' => [
new File([
'maxSize' => '1024k',
'mimeTypes' => [
'image/*',
],
'mimeTypesMessage' => 'Please upload a valid image',
])
]
'data_class' => null
])
in your formtype, you can add the parameter 'data_class' => null (like the example above) to eventImage will remove warning and work if you add new file but for edit if you don't add new file it should persist null in your bd.
To overcome this problem in order to be able to pre-fill the file field if a file exists is to do something like pointing to the location of the file. i.e. the path of the file that is saved in your database.
So what you're doing is checking your view to see if a file exists for the file path that exists in the database and then pre-filling it by doing something like this (don't forget to change with your upload path):
{% if form.eventImage.vars.data %} <img src="{{ asset('/uploads/directory/' ~ form.eventImage.vars.data.filename) }}"/>{% endif %}

Symfony forms - mapping of checkbox

Can I specify how the form field shold be mapped on data class?
Let's say I have form with check box and on my data entity the field is stored as string.
class FormType extends AbstractType {
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults([
'data_class' => DataEntity::class,
]);
}
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('issueType', CheckboxType::class, [
'label' => 'issueType',
]);
}
}
class DataEntity {
/** #var string Either PLASTIC or PAPER */
private $issueType;
public function getIssueType() {
return $this->issueType;
}
public function setIssueType($issueType) {
$this->issueType = $issueType;
}
}
Can I made the checkbox to be mapped as 'PLASTIC' if ture and 'PAPER' if false?
You can use data transformer to cast bolean to the string. See this tutorial: https://symfony.com/doc/current/form/data_transformers.html.
$builder->get('issueType')
->addModelTransformer(new CallbackTransformer(
function ($type) {
// your logic here
return $type;
},
function ($type) {
// your logic here
return $type;
}
));
try :
$builder->add('newsletter', 'choice', array(
'label' => 'Newsletter erhalten',
'attr' => array(
'class' => 'form-control',
),
'choices' => array(array('yes' => 'plastic'), array('no' => 'paper')),
'expanded' => true,
'multiple' => true,
'required' => false,
));
also check the answer here Symfony2 Change checkbox values from 0/1 to 'no'/'yes'

ZF2 form element collection validation

So I have a "simple" form
class SiteAddForm extends Form
{
public function __construct()
{
parent::__construct('add_site_form');
$site = new SiteFieldSet();
$this->add($site);
}
public function getTemplate()
{
return 'site_add.phtml';
}
}
The form it self does nothing. It adds a field_set and returns a template name.
The SiteFieldSet looks likes:
class SiteFieldSet
extends FieldSet
implements InputFilterProviderInterface
{
public function __construct()
{
parent::__construct('site');
$name = new Text('name');
$this->add($name);
$domains = new Collection('domains');
$domains->setTargetElement(new DomainFieldSet())
->setShouldCreateTemplate(true);
$this->add($domains);
}
public function getTemplate()
{
return 'site.phtml';
}
/**
* Should return an array specification compatible with
* {#link Zend\InputFilter\Factory::createInputFilter()}.
*
* #return array
*/
public function getInputFilterSpecification()
{
return [
'name' => [
'required' => true,
'validators' => [
new StringLength([
'min' => 200,
])
]
],
'domains' => [
'required' => true,
],
];
}
}
It adds a text and collection element to the fieldset. The field set implements InputFilterProviderInterface to validate the data thrown into it.
The name must be at least 200 chars (for testing) and the collection is required.
But now comes my problem. With the field set that is thrown into the collection, code:
class DomainFieldSet
extends FieldSet
implements InputFilterProviderInterface
{
public function __construct()
{
parent::__construct('domain');
$host = new Url('host');
$this->add($host);
$language = new Select('language', [
'value_options' => [
'nl_NL' => 'NL',
],
]);
$this->add($language);
$theme = new Select('theme', [
'value_options' => [
'yeti' => 'Yeti',
]
]);
$this->add($theme);
}
public function getTemplate()
{
return 'domain.phtml';
}
/**
* Should return an array specification compatible with
* {#link Zend\InputFilter\Factory::createInputFilter()}.
*
* #return array
*/
public function getInputFilterSpecification()
{
return [
'host' => [
'required' => true,
'validators' => [
new StringLength([
'min' => 200,
])
]
],
'language' => [
'required' => true,
],
'theme' => [
'required' => true,
],
];
}
}
Again nothing special. There are now three elements defined host, theme & language. Again the field set implements InputFilterProviderInterface. So there must be an getInputFilterSpecification in the class.
When I fill in the form
site[name] = "test"
site[domains][0][host] = 'test'
site[domains][0][theme] = 'yeti'
site[domains][0][language] = 'nl_NL'
It gives an error for site[name] saying it must be atleast 200 chars, so validations "works"
But it should also give an error on site[domains][0][host] that it needs to be atleast 200 chars (code was copy pasted, and the use is correct).
So why doesn't the validation kicks in, and or how can I solve the issue so a element/field set inside a collection is properly validated
Try using setValidationGroup in the form __construct method
like:
public function __construct()
{
$this->add(array(
'type' => 'Your\Namespace\SiteFieldSet',
'options' => array(
'use_as_base_fieldset' => true,
),
));
$this->setValidationGroup(array(
'site' => array(
'domain' => array(
'host',
'language',
'theme',
),
),
));
}
or this may also work...
$this->setValidationGroup(FormInterface::VALIDATE_ALL);

Categories