form.post_bind is fired twice - php

I added a form event to my Symfony Form. The problem is that this event seems to be fired twice. When I am debugging, it goes twice into the method and I don't know why.
I have a form Type that uses another form type. I have a VideoFileType form that is adding a field of ImageType. Both of these forms need some process to be done once the form is submitted.
ImageType
<?php
# src/Acme/PhotoBundle/Form/Type/ThumbnailType.php
namespace OSC\MediaBundle\Form\Type;
use OSC\MediaBundle\Manager\ImageManager;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\ChoiceList\ObjectChoiceList;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class ImageType extends AbstractType
{
private $imageManager;
public function __construct(ImageManager $imageManager) {
$this->imageManager = $imageManager;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('file', 'file');
$builder->addEventListener(
FormEvents::POST_SUBMIT,
array($this, 'onPostSetData')
);
}
public function getDefaultOptions(array $options) {
return array('data_class' => 'OSC\MediaBundle\Entity\Image');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'OSC\MediaBundle\Entity\Image'
));
}
public function onPostSetData(FormEvent $event) {
$image = $event->getData();
$form = $event->getForm();
//We need here to update the image file with the new content
$image = $this->imageManager->uploadImage($image);
$event->setData($image);
}
public function getName()
{
return 'image';
}
}
VideoFileType
<?php
# src/Acme/PhotoBundle/Form/Type/ThumbnailType.php
namespace OSC\MediaBundle\Form\Type;
use OSC\MediaBundle\Manager\VideoFileManager;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\ChoiceList\ObjectChoiceList;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
class VideoFileType extends AbstractType
{
public $container;
public $videoPresets = [];
public $videoFileManager;
public function __construct(VideoFileManager $videoFileManager, Container $container) {
$this->container = $container;
$videoPresets = $container->getParameter('osc_media.video.presets');
foreach ($videoPresets as $key => $videoPreset) {
array_push($this->videoPresets, $key);
}
$this->videoFileManager = $videoFileManager;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('thumbnail', 'image');
$builder->add('file', 'file');
$builder->add('videoPreset', 'choice', array(
'choices' => $this->videoPresets,
'multiple' => false,
'required' => true
));
$builder->addEventListener(
FormEvents::POST_SUBMIT,
array($this, 'onPostSetData')
);
$builder->add('save', 'submit');
}
public function onPostSetData(FormEvent $event) {
$videoFile = $event->getData();
$form = $event->getForm();
//We upload the video file
$videoFile = $this->videoFileManager->uploadVideoFile($videoFile);
$event->setData($videoFile);
}
public function getDefaultOptions(array $options) {
return array('data_class' => 'OSC\MediaBundle\Entity\VideoFile');
}
public function getName()
{
return 'video_file';
}
}
I don't know why the onPostSetData method is called twice...

Related

Symfony 5 - Filter some entities from DB for a collectionType

I have a form based on a collection. I would like that form to be set with a part of the data I have in my entity (not all).
I have search but haven't find the answer I'm looking for.
I tought using PRE_SET_DATA but the way I understand it doesn't work.
Can someone help me to do that?
I have an entity Service
<?php
namespace App\Entity;
use App\Repository\ServiceRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass=ServiceRepository::class)
*/
class Service
{
//[...]
/**
* #ORM\ManyToOne(targetEntity=User::class, inversedBy="services")
*/
private $performer;
/**
* #ORM\ManyToOne(targetEntity=User::class, inversedBy="performer_services")
*/
private $service_performer;
//[...]
public function getPerformer(): ?user
{
return $this->performer;
}
public function setPerformer(?user $performer): self
{
$this->performer = $performer;
return $this;
}
public function getServicePerformer(): ?User
{
return $this->service_performer;
}
public function setServicePerformer(?User $service_performer): self
{
$this->service_performer = $service_performer;
return $this;
}
}
My Collection Form
<?php
namespace App\Form;
use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class PerformerServiceType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('performer_services',CollectionType::class,[
'entry_type' => ServicePerformerNoMoneyUnitType::class,
'entry_options' => ['label' => false],
'allow_add' => true,
'allow_delete' => true,
])
->add('enregistrer', SubmitType::class)
;
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => User::class,
]);
}
}
My Entity Form (based on Service)
<?php
namespace App\Form;
use App\Entity\Service;
use App\Repository\UserRepository;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\MoneyType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Security\Core\Security;
use App\Entity\User;
class ServicePerformerNoMoneyUnitType extends AbstractType
{
public function __construct(Security $security)
{
$this->security = $security;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('price', MoneyType::class, [
'currency' => false])
->add('servicePerformer', EntityType::class, [
'class' => 'App\Entity\User',
'query_builder' => function (UserRepository $er) {
return $er->findByPartners($this->security->getUser());
},
])
->add('description')
;
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$service = $event->getData();
$userService = new User;
$performerService = new User;
if ($service <> null)
{
$userService = $service->getPerformer();
dump($userService);
$performerService = $service->getServicePerformer();
dump($performerService);
}
$form = $event->getForm();
// checks if the Product object is "new"
// If no data is passed to the form, the data is "null".
// This should be considered a new "Product"
if ($userService == $performerService) {
$form->removeElement();
}
});
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Service::class,
]);
}
}
What I would like to do is not show rows where Performer = Service Performer.

Populate form from two entities

I'm trying to populate form from database by using two entities. Here's the code I use
CategoryType
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class CategoryType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', 'text');
}
public function configureOptions(OptionsResolver $options)
{
return array(
'data_class' => 'AppBundle\Entity\Category',
);
}
public function getName()
{
return 'questionnaire';
}
}
ProductType
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ProductType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', 'text');
}
public function configureOptions(OptionsResolver $options)
{
return array(
'data_class' => 'AppBundle\Entity\Product',
);
}
public function getName()
{
return 'questionnaire';
}
}
CommonType
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use AppBundle\Form\CategoryType;
use AppBundle\Form\ProductType;
class CommonType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('category', new CategoryType());
$builder->add('product', new ProductType());
}
public function getName()
{
return 'app_common_type';
}
}
DefaultController
<?php
namespace AppBundle\Controller;
use AppBundle\Form\CommonType;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class DefaultController extends Controller
{
/**
* #Route("/", name="homepage")
*/
public function indexAction(Request $request)
{
$data = $this->getDoctrine()->getRepository('AppBundle:Product')->findOneBy(['id' => 2]);
$form = $this->createForm(new CommonType(), $data);
return $this->render('AppBundle:Default:index.html.twig', ['form' => $form->createView()]);
}
}
But I get this error
The form's view data is expected to be of type scalar, array or an instance of \ArrayAccess, but is an instance of class Proxies\__CG__\AppBundle\Entity\Category. You can avoid this error by setting the "data_class" option to "Proxies\__CG__\AppBundle\Entity\Category" or by adding a view transformer that transforms an instance of class Proxies\__CG__\AppBundle\Entity\Category to scalar, array or an instance of \ArrayAccess.
So what I'm doing wrong and is this the correct way to populate form based on two or more entities?
Your Common form's model data format is an array (as far as no data_class given). So the right data to set is an array with product and category keys:
$product = $this->getDoctrine()->getRepository('AppBundle:Product')->findOneBy(['id' => 2]);
$form = $this->createForm(new CommonType(), ['product' => $product]);

Expected argument of type "string or Symfony\Component\Form\FormTypeInterface"

the error that arises in symfony is as follows:
Expected argument of type "string or Symfony\Component\Form\FormTypeInterface", "Proyecto\LavocBundle\Entity\Datos" given
I will not put all code so that it does not become too large , I think the problem is in the Type:
DatosType:
<?php
namespace Proyecto\LavocBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class DatosType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('empresa');
$builder->add('cuit');
$builder->add('localidad');
$builder->add('calle');
$builder->add('altura');
$builder->add('areaTel');
$builder->add('telefono');
$builder->add('areaCel');
$builder->add('celular');
$builder->add('email');
}
public function getName()
{
return 'datos_form';
}
}
UserType:
<?php
namespace Proyecto\LavocBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('usuario');
$builder->add('pass');
}
public function getName()
{
return 'user_form';
}
}
RegistroUsuarioType (this is the embedded form) :
<?php
namespace Proyecto\LavocBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Proyecto\LavocBundle\Entity\Datos;
class RegistroUsuarioType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options) {
parent::buildForm($builder, $options);
$builder->add('usuario', 'text');
$builder->add('pass', 'text');
$builder->add('datos', new Datos());
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Proyecto\LavocBundle\Entity\User'
));
}
public function getName() {
return 'RegistroUsuario_form';
}
}
TablaController:
public function crearUsuarioDatosAction()
{
$request = $this->getRequest();
$usuario = new User();
$datos = new Datos();
$usuario->setDatos($datos);
$form = $this->createForm(new RegistroUsuarioType(), $usuario);
if($request->getMethod() == 'POST')
{
$form->bind($request);
if( $form->isValid() )
{
$em = $this->getDoctrine()->getEntityManager();
$em->persist($form);
$em->flush();
return $this->redirect($this->generateUrl('home'));
}
}
return $this->render('AtajoBundle:IngresarValores:crearUsuarioDatos.html.twig', array('form' => $form->createView()));
}
I thank you all very grateful for your help
$builder->add('datos', new Datos());
should be
$builder->add('datos', new DatosType());
Also your DatosType needs the data_class property set, add this function to your DatosType class:
public function setDefaultOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Proyecto\LavocBundle\Entity\Datos',
));
}
For newer versions of Symfony2 configureOptions instead of setDefaultOptions
Make sure your DatosType has:
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

Access Request parameters inside Symfony2 DataTransformer

Is it possible to get value from another form field inside DataTransformer for a field?
I can create an invitation linked to an email, then when the user register he must type an invitation code, it will work even if that code is not linked to the email he's entered, because Invitation field is a DataTransformer that checks the value inside DB. I would like to check inside that query, if the email exists.
MainForm
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('invitation', 'invitation_type', array(
'required' => true,
'label' => false,
'attr' => array(
'placeholder' => 'form.invitation_code',
'class' => 'form-control',
)
))
;
}
invitation_type is a service where I inject entityManager to a fieldtype that renders the dataTransformer:
InvitationType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$transformer = new InvitationTransformer($this->entityManager);
$builder->addModelTransformer($transformer);
}
And then the transformer
public function reverseTransform($value)
{
$invitation = $this->entityManager->getRepository('Invitation')
->findOneBy(array(
'code' => $value
));
return $invitation;
}
That's the query, which as I said is working without checking the email value, it would be something like 'email' => $emailValue but, I don't know how to access $emailValue
You can inject your value into transformer
public function buildForm(FormBuilderInterface $builder, array $options)
{
$entity = $builder->getData();
$email = $entity->getEmail();
$transformer = new MyTransformer($email);
$builder->add(
$builder->create('sample', 'choice', array(
'attr' => array('class' => 'test')
))->addModelTransformer($transformer)
)
}
class MyTransformer implements DataTransformerInterface {
private $emailValue;
public function __construct($emailValue)
{
$this->emailValue = $emailValue;
}
public function reverseTransform($value)
{
// Do something with $this->emailValue;
$invitation = $this->entityManager->getRepository('Invitation')
->findOneBy(array('code' => $value));
return $invitation;
}
}
So if someone has the same problem I've found a solution.
First create a service for your custom field type, injecting #request_stack
foo.form.type.invitation:
class: Foo\BarBundle\Form\Type\InvitationType
arguments: [ "#doctrine.orm.entity_manager" ]
tags:
- { name: form.type, alias: invitation_type}
calls:
- [setRequest, [#request_stack]]
Then create your custom field type class, which will inject our Request to the DataTransformer
<?php
namespace Foo\BarBundle\Form\Type;
use Doctrine\ORM\EntityManager;
use Foo\BarBundle\Form\DataTransformer\InvitationTransformer;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\HttpFoundation\RequestStack;
class InvitationType extends AbstractType
{
private $entityManager;
protected $request;
public function __construct(EntityManager $em)
{
$this->entityManager = $em;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$transformer = new InvitationTransformer($this->entityManager, $this->request);
$builder->addModelTransformer($transformer);
}
public function getParent()
{
return 'text';
}
public function getName()
{
return 'invitation_type';
}
public function setRequest(RequestStack $request_stack)
{
$this->request = $request_stack->getCurrentRequest();
}
}
And then our DataTransformer needs to fetch the data, change values to match your requests, of course
<?php
namespace Foo\BarBundle\Form\DataTransformer;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Component\HttpFoundation\Request;
class InvitationTransformer implements DataTransformerInterface
{
protected $entityManager;
protected $request;
public function __construct(EntityManager $entityManager, Request $request)
{
$this->entityManager = $entityManager;
$this->request = $request;
}
public function transform($value)
{
if (null === $value) {
return null;
}
if (!$value instanceof Invitation) {
throw new UnexpectedTypeException($value, 'Foo\BarBundle\Entity\Invitation');
}
return $value->getCode();
}
public function reverseTransform($value)
{
if (null === $value || '' === $value) {
return null;
}
if (!is_string($value)) {
throw new UnexpectedTypeException($value, 'string');
}
$formData = $this->request->get('registration_form'); // Your MAIN form goes here
$email = $formData['email']; // The value you need
$invitation = $this->entityManager->getRepository('FooBarBundle:Invitation')
->findOneBy(array(
'code' => $value,
'email' => $email
));
if($this->entityManager->getRepository('FooBarBundle:User')->findOneBy(array("invitation" => $invitation))){
return null;
}
return $invitation;
}
}
Now you have access to your parameters bag inside your DataTransformer, piece of cake.

Calling class using service in Form

I have a question about service and Form in Symfony2,so I created my calss form and I hope to add a multiselect list of cities then I want to get list of cities from another class "city",so how I can call my class "city" in my form using "Service" to get a function "getcities" to return me a list of cities? (I dont use Doctrine here)...
Edit
namespace Acme\DemoBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class CityType extends AbstractType
{
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'choices' => array( /**
* Here I will call function getcities(return list of cities)
*/
)
));
}
public function getParent()
{
return 'choice';
}
public function getName()
{
return 'gender';
}
}
class City.php :
Class City {
/**
* here i will get list of cities
*/
public function getcities()
{
.....
return $Listcities;
}
}
So I would like to use "Service" to call function "getcities" in form?
The form objects are not container-aware...at least, they're not meant to be. That said, your controller should use the service to get the cities, and then it should pass that list into the form object either through a constructor or a method.
Controller:
class SomethingController
{
public function someAction()
{
...
$cities = $this->get("citiesService")->getCities();
$form = $this->createForm(new SomeType($cities), $someEntity);
...
}
}
Form:
class SomeType extends AbstractType
{
private $cities;
public function __construct($cities)
{
$this->cities = $cities;
}
public function buildForm(FormBuilder $builder, array $options)
{
// Now you have access to $this->cities, so you can use it to build the form
}
}
Do you can set City object as form data object?
so it can looks like that...
$form = $this->createForm(new SomeType(), new City());
class SomeType extends AbstractType
{
public buildForm(FormBuilderInterface $builder, array $options)
{
$formFactory = $builder->getFormFactory();
$builder->addEventListener(
FormsEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formFactory) {
$event->getForm()->add(
$formFactory->createNamed(
'gender',
'choice',
null,
array(
'choices' => $event->getData()->getCites()
)
)
);
}
);
}
}

Categories