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.
Related
I'm new to symfony and still learning, my question is how do I populate a select drop-down in a form with an static array of choices. Say I have a class named Cake, I'd like to be able to fill a drop-down for the status of Cake from the array statuses created in the same CakeEntity:
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\CakeRepository")
*/
class Cake
{
/**
* #ORM\Column(type="string", length=50)
*/
private $status;
private $statuses = array(
'not_ready' => 'Not Ready',
'almost_ready' => 'Almost Ready',
'ready'=>'Ready',
'too_late'=>'Too late'
);
public function getStatus(): ?string
{
return $this->status;
}
public function setStatus(string $status): self
{
$this->status = $status;
return $this;
}
public function getStatuses()
{
return $this->statuses;
}
}
My Controller looks like:
namespace App\Controller;
use App\Entity\Cake;
use App\Form\CakeType;
use App\Repository\CakeRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* #Route("/cake")
*/
class CakeController extends AbstractController
{
/**
* #Route("/new", name="cake_new", methods={"GET","POST"})
*/
public function new(Request $request): Response
{
$cake = new Cake();
$form = $this->createForm(CakeType::class, $cake);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$cake->setCreatedAt(\DateTime::createFromFormat('d-m-Y', date('d-m-Y')));
$cake->setCreatedBy(1);
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($cake);
$entityManager->flush();
return $this->redirectToRoute('cake_index');
}
return $this->render('cake/new.html.twig', [
'cake' => $cake,
'form' => $form->createView(),
]);
}
My CakeEntity:
<?php
namespace App\Form;
use App\Entity\cake;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
class CakeType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
->add('status', ChoiceType::class,
[
'choices'=>function(?Cake $cake) {
return $cake->getStatuses();
}
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Cake::class,
]);
}
}
When trying to browse /cake/new I get the error:
An error has occurred resolving the options of the form "Symfony\Component\Form\Extension\Core\Type\ChoiceType": The option "choices" with value Closure is expected to be of type "null" or "array" or "\Traversable", but is of type "Closure".
You could declare getStatuses on Cake as static, or use public constants. E.g.:
class Cake
{
// with static variables
private static $statuses = [
'not_ready' => 'Not Ready',
'almost_ready' => 'Almost Ready',
'ready' => 'Ready',
'too_late' => 'Too late',
];
public static function getStatuses()
{
return self::$statuses;
}
// or with public const
public const STATUSES = [
'not_ready' => 'Not Ready',
'almost_ready' => 'Almost Ready',
'ready' => 'Ready',
'too_late' => 'Too late',
];
}
This seems reasonable, as the return value is not instance but class specific.
You could then use:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('status', ChoiceType::class, [
'choices'=> Cake::getStatuses(),
]);
// or
$builder->add('status', ChoiceType::class, [
'choices'=> Cake::STATUSES,
]);
}
If the choices actually depend on a given Cake instance, you could pass it via the options array or use form events.
I want to know how I can recover the current user in my FormType (EntityType)?
Currently, I can only retrieve the list of registered users but not the current (connected) user.
My current FormType
<?php
namespace App\Form;
use App\Entity\User;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class OneNewCarType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'author',
EntityType::class, [
'label' => 'Ville',
'class' => User::class,
'attr' => [
'class' => 'selectpicker'
],
'choice_label' => function ($author) {
return $author->getCity();
}
]
);
}
public function getBlockPrefix()
{
return 'oneNewCarType';
}
}
I know how to recover it directly in my controller. But, I do not know how to do it when one uses the stepper bundle CraueFormFlowBundle
This is my current controller
/**
* #Route("/classified/{id}/edit", name="classified_edit")
* #param CarVehicle $carVehicle
* #param ObjectManager $manager
* #param CreateVehicleFlow $createVehicleFlow
* #return RedirectResponse|Response
*/
public function edit(CarVehicle $carVehicle, ObjectManager $manager, CreateVehicleFlow $createVehicleFlow)
{
$flow = $createVehicleFlow;
$flow->bind($carVehicle);
$form = $flow->createForm();
if ($flow->isValid($form)) {
$flow->saveCurrentStepData($form);
if ($flow->nextStep()) {
$form = $flow->createForm();
} else {
$manager->persist($carVehicle);
$manager->flush();
$flow->reset();
$this->addFlash(
'success',
"Votre annonce <i>{$carVehicle->getId()}</i> a bien été mise à jour"
);
return $this->redirect($this->generateUrl('account_index'));
}
}
return $this->render('front/classified/edit_vehicle.html.twig', [
'form' => $form->createView(),
'flow' => $flow,
'carVehicle' => $carVehicle,
]);
}
Thanks for the help !
With Symfony\Component\Security\Core\Security you can get user where you want. Inject this into FormType and use $user = $this->security->getUser();
private Security $security;
public function __construct(Security $security)
{
$this->security = $security;
}
In Symfony 5, With use
Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface
you can get user where you want. Inject this into FormType and use
$user = $this->token->getToken()->getUser();
private $token;
public function __construct(TokenStorageInterface $token)
{
$this->token = $token;
}
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...
As it stands isValid() returns true on form submission regardless of whether the fields have been filled out or not. The objective is to get isValid() method to actually check the data. Any help would be much appreciated.
Form Entity:
namespace Bookboon\Premium\AppBundle\Entity;
class Form {
protected $email;
protected $id,
$type,
$voucher,
$subscription,
$affiliate;
public function getEmail(){
return $this->email;
}
public function setEmail($email){
$this->email = $email;
}
// The rest of the getters & setters have been omitted for ease of reading, but they look very similar to the above.
}
services.yml:
services:
form.builder:
class: Symfony\Component\Form\FormBuilder
arguments: ['SignUpForm', 'Bookboon\Premium\AppBundle\Entity\Form', #event_dispatcher, #form.factory]
premium.form.entity:
class: Bookboon\Premium\AppBundle\Entity\Form
arguments: [ ]
premium.form.sign_up:
class: Bookboon\Premium\AppBundle\Form\SignUpFormType
tags:
- { name: form.type, alias: SignUpForm }
premium.controller.signup:
class: Bookboon\Premium\AppBundle\Controller\SignUpController
arguments: [#templating, #form.builder, #premium.form.sign_up, #form.factory, #premium.form.entity ]
validation.yml:
Bookboon\Premium\AppBundle\Entity\Form:
properties:
email:
- NotBlank: ~
- NotNull: ~
SignUpController.php:
namespace Bookboon\Premium\AppBundle\Controller;
use Bookboon\Premium\AppBundle\Entity\Form;
use Bookboon\Premium\AppBundle\Form\SignUpFormType;
use Bookboon\Premium\AppBundle\Form\SignUpDetailsForm;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Form\FormFactory;
class SignUpController implements DetectionController
{
private $_templating;
private $_formBuilder;
private $_form;
private $_formFactory;
private $_formData;
public function __construct(EngineInterface $templating, FormBuilder $formBuilder, SignUpFormType $signUpFormType, FormFactory $formFactory, Form $formData)
{
$this->_templating = $templating;
$this->_formBuilder = $formBuilder;
$this->_form = $signUpFormType;
$this->_formFactory = $formFactory;
$this->_formData = $formData;
}
public function signUpDetailsAction()
{
return $this->renderForm('SignUpDetailsForm', new SignUpDetailsForm(), 'details');
}
public function signUpAction(Request $request)
{
$form = $this->createForm($this->_form, $this->_formData);
$form->handleRequest($request);
if($request->getMethod() == 'POST'){
if($form->isValid()){
dump('Form Is Valid!');
}
return $this->renderForm($form->createView(), 'signup');
}
return $this->renderForm($form->createView(), 'signup');
}
public function renderForm($form, $type)
{
return $this->_templating->renderResponse( 'PremiumBundle:Connect:'.$type.'.html.twig', array('form'=>$form));
}
public function createForm($type, $data = null, array $options = array())
{
return $this->_formFactory->create($type, $data, $options);
}
}
SignUpFormType:
namespace Bookboon\Premium\AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Bookboon\Premium\AppBundle\Entity;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class SignUpFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email', 'text', array('label'=>' ',
'attr' => array('placeholder'=>'Your email', 'class'=>'email')))
->add('save', 'submit', array( 'label'=>'Access Premium',
'attr'=>array('class'=>'btn btnProceed'),
'validation_groups' => true))
->add('facebook', 'submit', array( 'validation_groups' => false,
'attr' =>array('class'=>'btn btnFacebook cancel')))
->add('linkedin', 'submit', array( 'validation_groups' => false,
'attr' =>array('class'=>'btn btnLinkedIn cancel')))
->add('id', 'hidden')
->add('type', 'hidden')
->add('voucher', 'hidden');
}
public function getName()
{
return 'SignUpForm';
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Bookboon\Premium\AppBundle\Entity\Form',
));
}
}
Problem: When form is submitted with no data isValid() returns true.
Form Entity (Data Class), stores data submitted:
<?php
namespace Bookboon\Premium\AppBundle\Entity;
class Form {
protected $email;
protected $id;
protected $type;
protected $voucher;
protected $subscription;
protected $affiliate;
public function getEmail(){
return $this->email;
}
public function setEmail($email){
$this->email = $email;
}
public function getId(){
return $this->id;
}
public function setId($id){
$this->id = $id;
}
public function getType(){
return $this->type;
}
public function setType($type){
$this->type = $type;
}
public function getVoucher(){
return $this->voucher;
}
public function setVoucher($voucher){
$this->voucher = $voucher;
}
public function setSubscription($subscription){
$this->subscription = $subscription;
}
public function getSubscription(){
return $this->subscription;
}
public function setAffiliate($affiliate){
$this->affiliate = $affiliate;
}
public function getAffiliate(){
return $this->affiliate;
}
}
Form Type Class:
namespace Bookboon\Premium\AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Bookboon\Premium\AppBundle\Entity;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class SignUpFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email', 'email', array('label'=>' ',
'attr' => array('placeholder'=>'Your email', 'class'=>'email')))
->add('save', 'submit', array( 'label'=>'Access Premium',
'attr'=>array('class'=>'btn btnProceed'),
'validation_groups' => true))
->add('facebook', 'submit', array( 'validation_groups' => false,
'attr' =>array('class'=>'btn btnFacebook cancel')))
->add('linkedin', 'submit', array( 'validation_groups' => false,
'attr' =>array('class'=>'btn btnLinkedIn cancel')))
->add('id', 'hidden')
->add('type', 'hidden')
->add('voucher', 'hidden');
}
public function getName()
{
return 'SignUpForm';
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Bookboon\Premium\AppBundle\Entity\Form',
));
}
}
Services.yml (templating service and builder service are written, just ommitted here for ease of reading):
premium.form.sign_up:
class: Bookboon\Premium\AppBundle\Form\SignUpFormType
tags:
- { name: form.type, alias: SignUpForm }
premium.controller.signup:
class: Bookboon\Premium\AppBundle\Controller\SignUpController
arguments: [ #templating, #form.builder, #premium.form.sign_up ]
Validation.yml:
Bookboon\Premium\AppBundle\Entity\Form:
properties:
email:
- NotBlank: ~
Form Controller:
namespace Bookboon\Premium\AppBundle\Controller;
use Bookboon\Premium\AppBundle\Form\SignUpFormType;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\HttpFoundation\Request;
class SignUpController implements DetectionController
{
private $_templating;
private $_formBuilder;
private $_form;
public function __construct(EngineInterface $templating, FormBuilder $formBuilder, SignUpFormType $signUpFormType)
{
$this->_templating = $templating;
$this->_formBuilder = $formBuilder;
$this->_form = $signUpFormType;
}
public function signUpAction(Request $request)
{
$form = $this->_formBuilder->create($this->_form->getName(), $this->_form);
$form = $form->getForm();
$form->handleRequest($request);
dump($form->isValid()); // Returns true regardless of data (even if blank)
if($request->getMethod() == 'POST'){
if($form->isValid()){
dump('yata!');
}
return $this->renderForm($form->createView(), 'signup');
}
return $this->renderForm($form->createView(), 'signup');
}
public function renderForm($form, $type)
{
return $this->_templating->renderResponse( 'PremiumBundle:Connect:'.$type.'.html.twig', array('form'=>$form));
}
}
Would really appreciate it if someone could help me with this....
Hmm ran into this problem a while back too and haven't figured - I'm new to symfony and my cursory search engine search revealed nada, I too would appreciate the quickmefix here!