Here's what i'm trying to achieve. I'm using the SymfonyContrib\Bundle\LinkButtonBundle\LinkButtonBundle to add a simple back/cancel link to my form right beside the submit button. Problem is that I don't know how to get to my router so that I can use the generate method to generate route url's. Anyone have an idea how to either inject the router into my form or pass the URL from my controller where the form is created with $this->createform('my_form_foo')
<?php
namespace My\Form;
use \Symfony\Component\Form\AbstractType;
use \Symfony\Component\Form\FormBuilderInterface;
class Foo extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'username',
null,
[
'label' => 'Username',
'attr' => [
'placeholder' => 'User name',
],
'required' => true
]
)
->add('actions', 'form_actions', [
'buttons' => [
'save' => [
'type' => 'submit'
],
'cancel' => [
'type' => 'button',
'options' => [
'url' => '/', // This needs to be generated from a route
'label' => 'Back'
]
],
]
]);
}
/**
* #return string
*/
public function getName()
{
return 'my_form_foo';
}
}
In your specific case, it's a better practice to add buttons directly in the view.
(Something like that)
{{ form_start(form) }}
{{ form_widget(form) }}
<button type="submit" value="Save" />
Back
{{ form_end(form) }}
You can inject the router into the form class if you register your form as a service in the service container.
See: http://symfony.com/doc/current/cookbook/form/create_custom_field_type.html#creating-your-field-type-as-a-service
I suggest you to manage all the form actions in the Controller Class. As described in the doc here, you can add your button for flow-control:
$form = $this->createFormBuilder($task)
->add('name', 'text')
->add('save', 'submit')
->add('save_and_add', 'submit')
->getForm();
And manage the flow in the controller as:
if ($form->isValid()) {
// ... do something
// the save_and_add button was clicked
if ($form->get('save_and_add')->isClicked()) {
// probably redirect to the add page again
}
// redirect to the show page for the just submitted item
}
Hope this help
Related
I have the following form type:
<?php
namespace Myproject\App\ErpBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormEvent;
use Myproject\OrderBundle\Entity\OrderProductType;
use Myproject\OrderBundle\Entity\OrderOrder;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\HttpFoundation\RequestStack;
class OrderSearchType extends AbstractType
{
protected $em;
protected $container;
protected $requestStack;
public function __construct(ObjectManager $em, ContainerInterface $container, RequestStack $requestStack)
{
$this->em = $em;
$this->container = $container;
$this->requestStack = $requestStack;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$data = $builder->getData();
$currentRequest = $this->requestStack->getCurrentRequest();
$pt = [
'class' => OrderProductType::class,
'choices' => [],
'required' => false
];
if ($productType = $currentRequest->query->get('ProductType', null)) {
$pt["data"] = $productType;
}
$id = [
'label' => 'id',
'required' => false
];
if ($idValue = $currentRequest->query->get('id', null)) {
$id["data"] = $idValue;
}
$name = [
'label' => 'név',
'required' => false
];
if ($nm = $currentRequest->query->get('name', null)) {
$name["data"] = $nm;
}
$builder
->add('productType', EntityType::class, $pt)
->add('id', IntegerType::class, $id)
->add('name', TextType::class, $name)
->add('status', ChoiceType::class, [
'choices' => [ 'Bármilyen',
OrderOrder::$ORDER_STATUS_TEXT[OrderOrder::$ORDER_STATUS_ORDERED],
OrderOrder::$ORDER_STATUS_TEXT[OrderOrder::$ORDER_STATUS_ACCEPTED],
OrderOrder::$ORDER_STATUS_TEXT[OrderOrder::$ORDER_STATUS_REFUSED]
],
'data' => $currentRequest->query->get('status', "1")
])
->add('search', SubmitType::class, [
'label' => 'Keresés',
'attr' => ['class' => 'btn btn-primary btn-sm']
]);
$builder->addEventListener(FormEvents::PRE_SUBMIT, array($this, 'onPresubmit'));
$builder->addEventListener(FormEvents::POST_SET_DATA, array($this, 'onPostSetData'));
}
public function onPostSetData(FormEvent $event)
{
$form = $event->getForm();
$data = $form->getData();
$event->getForm()
->add('productType', EntityType::class, [
'class' => OrderProductType::class,
'choices' => $this->em->getRepository(OrderProductType::class)->findAll(),
'choice_label' => function($orderProductType = null) {
return sprintf($orderProductType ? $orderProductType->getTitle() : "Kérem válasszon");
},
'label' => 'Termékfajta',
'required' => false,
'attr' => [
'class' => 'selectpicker',
'data-live-search' => 'true'
]
]);
}
public function onPresubmit(FormEvent $event)
{
$form = $event->getForm();
$data = $event->getData();
$event->getForm()
->add('productType', EntityType::class, [
'class' => OrderProductType::class,
'choices' => $this->em->getRepository(OrderProductType::class)->findAll(),
'choice_label' => function($orderProductType = null) {
return sprintf($orderProductType ? $orderProductType->getTitle() : "Kérem válasszon");
},
'label' => 'Termékfajta',
'required' => false,
'attr' => [
'class' => 'selectpicker',
'data-live-search' => 'true'
]
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => null,
'em' => $this->em,
]);
parent::configureOptions($resolver);
}
public function getBlockPrefix()
{
return 'route';
}
}
It is displayed on the ui like this:
{{ form_start(form, { 'attr': {'class': 'myprojectAdminForm pull-right', 'id': form.vars.name } }) }}
Szűrés:
{{ form_widget(form.productType) }}
{{ form_widget(form.id, {'attr': {'placeholder': form.id.vars.label, 'class':'form-control'}}) }}
{{ form_widget(form.name, {'attr': {'placeholder': form.name.vars.label, 'class':'form-control' }}) }}
{{ form_widget(form.status, {'attr': {'class':'form-control'}}) }}
{{ form_widget(form.search) }}
{{ form_widget(form._token) }}
{{ form_errors(form) }}
{{ form_end(form, {'render_rest': false}) }}
and whenever the user clicks on search, the following Javascript is executed:
$("#erpordersearch_search").click(function(e) {
e.preventDefault();
var productTypeID = document.querySelector("#erpordersearch_productType").value;
var ID = document.getElementById("erpordersearch_id").value;
var name = document.getElementById("erpordersearch_name").value;
var status = document.getElementById("erpordersearch_status").value;
var url = window.location.href.substring(0, window.location.href.lastIndexOf("/page"));
url = url + "/page1";
window.location.href = url + "[ProductTypeID=" + productTypeID + "&id=" + ID + "&name=" + name + "&status=" + status + "]";
});
This works, but the problem is that I am tracking user action history on the ui and if the system loads the full page, then I am losing all the history of the user's action, as the history is saved in the Javascript. I know I could save the history in the database or localStorage, but either of those are not viable options in my case, as they would violate the policies of the project. I was asked to do modifications which will consist of the following features:
the URL will contain [queryString=somevalue] after the request is responded, even if it did not contain it before the GET request
after the request all the defined Javascript variables should exist. If I have written var foo = 'bar'; in the console, after the request the variable should be defined
I have been given some vague description about how this should be achieved via the combination of Symfony and the particular project I am working on, but it is unclear and contains project-specific information I cannot share here.
So my question is: is there a way using PHP and Symfony, to change the URL when sending a request and still keeping all the variables which were defined in Javascript?
The only way I would say this is possible (prepare for more vague explanations) is to have your JavaScript variables stored in invisible form elements that are submitted on pressing the search button.
These should then be sent to the Symfony controller via post data, form here you can then use the method outlined here to send variables to JavaScript from PHP (but modified for twig syntax) - How can I store JavaScript variable output into a PHP variable?
If in a script file, you can try passing arguments this way - https://www.gun.io/blog/pass-arguments-to-embedded-javascript-tutorial-example
EDIT
After reading over this all again (and discussing this in the comments below), I think I understand the question better.
I personally agree with you, it looks like this will be impossible to do in pure Symfony via GET requests.
Even if there was a "hacky" method, it wouldn't be scaleable or dynamic and thus not worth doing.
I'm trying to build multi-tenancy support in Symfony, doing this I created a group_id column in my User, on top of that, all entities also have this same column. That way users only have access to their group's data.
I've managed to cut through this whole thing like butter with data access and display, but then came the challenge of EntityTypes for the Symfony Forms.
The question is basically, how do I make the EntityType display only the data that that particular group has inputted. Sorting it by the group_id that both the user and contact has. Whats the best way to maybe pass this in so the user only has access to their data?
<?php
namespace ContactBundle\Form;
use ContactBundle\Entity\Contact;
use ContactBundle\Entity\Organization;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
class ContactType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('first_name', TextType::Class, [
'attr' => ['class'=>'u-full-width'],
'label' => 'First Name',])
->add('last_name', TextType::Class, [
'attr' => ['class'=>'u-full-width'],
'label'=>'Last Name',
])
->add('email', TextType::Class, [
'attr' => ['class'=>'u-full-width'],
'label'=>'Email Address'
])
->add('organization_id', EntityType::Class, [
'attr' => ['class'=>'u-full-width'],
'required' => false,
'class'=>'ContactBundle:Organization',
'choice_label'=>'name',
'choice_value'=>'id',
'label'=>'Organization'
])
->add('phone', TextType::Class, [
'attr' => ['class'=>'u-full-width'],
'label'=>'Phone',
])
->add('role', TextType::Class, [
'attr' => ['class'=>'u-full-width'],
'label'=>'Role',
])
->add('submit', SubmitType::class, [
'label'=>'Submit',
'attr' => [
'class'=>'button-primary',
'style'=>'margin-top:30px;',]]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(['data_class'=>Contact::class,]);
}
}
?>
It is worth noting that I am using FOSUserBundle.
In Symfony it's very easy to inject whatever you need where you need it.
// ...
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class ContactType extends AbstractType
{
private $user;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->user = $tokenStorage->getToken()->getUser();
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// ...
->add('organization_id', EntityType::Class, [
'attr' => ['class'=>'u-full-width'],
'required' => false,
'class'=>'ContactBundle:Organization',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('u')
->where('u.group', '?0')
->setParameters([$this->user->getGroup()]);
},
'choice_label'=>'name',
'choice_value'=>'id',
'label'=>'Organization'
])
// ...
If you are not using autowire and autoconfigure (Symfony 3.3+), register your form as a service manually and tag it with form.type:
# config/services.yaml
services:
AppBundle\Form\ContactType:
arguments: ['#security.token_storage']
tags: [form.type]
Related reads
https://symfony.com/doc/current/form/form_dependencies.html#define-your-form-as-a-service
https://symfony.com/doc/current/reference/forms/types/entity.html#using-a-custom-query-for-the-entities
In controller when creating form
$form = $this->createForm(ContactType::class, $contact, ['group_id' => $groupId]);
In form configureOptions method
$resolver->setDefaults(['data_class'=>Contact::class, 'group_id' => null]);
In form buildForm you can get group_id by
$options['group_id']
I got it!
So what I ended up doing was getting rid of the whole idea of using a EntityType on the form side.
I then called the data from the controller and passed it in as an option.
Like so:
$contactManager = $this->get('contact.contact_manager');
$contact = new Contact($contactManager->nextId($group = $this->getUser()->getGroupId()), $group);
$form = $this->createForm(ContactType::class, $contact, ['organizations' => $this->get('contact.organization_manager')->findDataCollection($this->getUser()->getGroupId())]);
$form->handleRequest($request);
Turned my EntityType into a choice type and passed the array of organizations into the 'choices' field as an option. Kept everything else the same like so:
->add('organization_id', ChoiceType::Class, [
'attr' => ['class'=>'u-full-width'],
'required' => false,
'choices'=> $options['organizations'],
'choice_label'=>'name',
'choice_value'=>'id',
'label'=>'Organization'
])
Then, of course, set it within the options so it can expect a variable.
$resolver->setDefaults(['data_class'=>Contact::class, 'organizations' => null]);
I appreciate all of the help and ideas!! :)
You could do this in few different ways, one was to use combination of JavaScript and EntityType.
Basically just load all the entities and hide unwanted entities with JS depending on the previous list's selection. You can use data attribute to specify things needed to make that work.
My scenario is the following:
If the user choose true from "maxRedemptionForDiscount" and type "0" into the "maxRedemptionForDiscountValue" there should be an error message rendering to the specific field (at the position of the TextType field)
This is the form with an eventListener:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add(
'maxRedemptionForDiscount',
ChoiceType::class,
[
'placeholder' => false,
'multiple' => false,
'choices' => [
true => 'discount.form_fields.set_max_redemptions',
false => 'discount.form_fields.unlimited',
],
'label' => 'discount.form_fields.max_redemption_for_discount',
'translation_domain' => 'entities',
'required' => false,
'error_bubbling' => true,
'attr' => [
'class' => 'maxRedemptionForDiscountSelect',
],
]
)->add(
'maxRedemptionForDiscountValue',
TextType::class,
[
'label' => 'discount.form_fields.set_max_redemptions',
'translation_domain' => 'entities',
'required' => false,
]
)->addEventListener(
FormEvents::PRE_SUBMIT,
[$this, 'onPreSubmit']
);
}
and this is the onPreSubmit function:
/**
* #param FormEvent $event
*/
public function onPreSubmit(FormEvent $event)
{
$data = $event->getData();
$form = $event->getForm();
if ($data['maxRedemptionForDiscount'] == 1) {
if ($data['maxRedemptionForDiscountValue'] == 0) {
$form->addError(new FormError('error message'));
}
}
$event->setData($data);
}
Here is the twig code:
{{ form_row(form.maxRedemptionForDiscount) }}
<div id="maxRedemptionForDiscountValue">
{{ form_row(form.maxRedemptionForDiscountValue) }}
</div>
This render a error message above the form.
But what I want i to render a error message to the specific field.
This does not work:
$form->get('maxRedemptionForDiscountValue')->addError(new FormError('error message'));
If I try this the error message will disappear at the top of my form, but not showing up at the specific field position.
What I am doing wrong here?
First, you should set error_bubbling to false (or remove it as it's default behavior).
As documentation states
If true, any errors for this field will be passed to the parent field or form. For example, if set to true on a normal field, any errors for that field will be attached to the main form, not to the specific field.
Particularly for ChoiceType
Set that error on this field must be attached to the field instead of the parent field (the form in most cases).
Second, you should add error to specific form field
$form
->get('maxRedemptionForDiscountValue')
->addError(new FormError('error message'));
Third, you should edit your template
<div id="maxRedemptionForDiscountValue">
{{ form_errors(form.maxRedemptionForDiscountValue) }}
{{ form_row(form.maxRedemptionForDiscountValue) }}
</div>
I'm looking to display 2 instances of the same form inside another form with twig but I can't figure out how.
Here my first form:
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
// build form
$builder
->add('contenu', 'text', array(
'attr' => array('maxlength' => 255),
'empty_data' => "Question par défaut"
))
->add('file', 'file', array(
'attr' => array('accept' => "image/png, image/jpeg")
))
->add('contenuQuestion', 'collection', array(
'type' => new QuizzReponseType(), // here the embedded form
'allow_add' => true,
'allow_delete' => true,
'cascade_validation' => true,
))
;
}
with in the entity:
/**
* #ORM\ManyToMany(targetEntity="OC\QuizzBundle\Entity\QuizzReponse", cascade={"remove", "persist"})
*/
private $contenuQuestion;
function __construct() {
$this->contenuQuestion = new ArrayCollection();
}
Here the embedded form:
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
// build form
$builder
->add('contenu', 'text', array(
'attr' => array('maxlength' => 50),
'empty_data' => "Réponse par défaut"
))
->add('file', 'file', array(
'attr' => array('accept' => "image/png, image/jpeg")
))
;
}
The problem is in my view. I want to have 2 instances of the embedded form but the collection seems empty.
It's working well with only one embedded form (NB: my first form is also inside another form but it's not relevant for my question):
// embedded form content field
{{ form_widget(form.contenuQuizz.vars.prototype.contenuQuestion.vars.prototype.contenu) }}
And I'm looking to do something like that:
// embedded form1 content field
{{ form_widget(form.contenuQuizz.vars.prototype.contenuQuestion.0.vars.prototype.contenu) }}
// embedded form2 content field
{{ form_widget(form.contenuQuizz.vars.prototype.contenuQuestion.1.vars.prototype.contenu) }}
It looks like the problem is that the collection is not initialized but I'm not sure.
Is there a way to do that ? (with twig and not JavaScript if possible)
Thanks in advance,
Maugun
I am sitting three hours under symfony2 FormBuilder, when I try to make a simple contact form, on my OneSitePage website. I will notice that I am mostly frontend, but I need send emails via Swiftmailer across symfony2. Please do not ask, why I am using symfony:)
PROBLEM: I have issue with render form on my homePage, because Symfony says, like in subject:
"Variable "form" does not exist in YodaHomeBundle::layout.html.twig..."
and it point for line where I am useing twig form(attached below in TWIG section)
Ok, that was introduction. Below I present PHP class of controller and ContactType class, also below I had attached layout.html.twig file.
First comes controller, where I have two actions, index and contact.
namespace Yoda\HomeBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\Routing\Annotation\Route;
use Yoda\UserBundle\Entity\User;
use Yoda\HomeBundle\Form\ContactType;
use Symfony\Component\Form\FormInterface;
class HomeController extends Controller{
/**
* #Route("/home", name="homePage")
* #Template()
*
*/
public function indexAction(){
return $this->render('YodaHomeBundle::layout.html.twig');
}
public function contactAction(Request $request)
{
$form = $this->createForm(new ContactType());
$adress = 'grzegorz.developer#gmail.com';
if($request->isMethod('POST'))
{
$form->submit($request->request->get($form->getName()));
if($form->isValid())
{
$message = \Swift_Message::newInstance()
->setSubject($form->get('subject')->getData())
->setFrom($form->get('email')->getData())
->setTo($adress)
->setBody(
$this->renderView('#YodaHome/mail/contact.html.twig',
array(
'ip' => $request->getClientIp(),
'name' => $form->get('name')->getData(),
'message' => $form->get('message')->getData()
))
);
$this->get('mailer')->send($message);
$request->getSession()->getFlashBag()->add('Success, your mail has been send! Thank you, I will back to you, as soon as it\'s possible!');
return $this->redirect($this->generateUrl('homePage'));
}
}
return array(
'form' => $form->createView()
);
}
}
now builder, Simple builder which is used on many tuts.
class ContactType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', 'text', array(
'attr' => array(
'placeholder' => 'What\'s your name?',
'length' => '.{2,}'
)
))
->add('email', 'email', array(
'attr' => array(
'placeholder' => 'So I can write back to you'
)
))
->add('subject', 'text', array(
'attr' => array(
'placeholder' => 'Subject of your message',
'pattern' => '.{5,}'
)
))
->add('message', 'text', array(
'attr' => array(
'cols' => '90',
'row' => '10',
'placeholder' => 'And ad your message to me...'
)
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$collectionConstraint = new Collection(array(
'name' => array(
new NotBlank(array('message' => 'You forgot about the Name.')),
new Length(array('min' => 2))
),
'email' => array(
new NotBlank(array('message' => 'Email should not be blank.')),
new Email(array('message' => 'Invalid email address.'))
),
'subject' => array(
new NotBlank(array('message' => 'Subject should not be blank.')),
new Length(array('min' => 3))
),
'message' => array(
new NotBlank(array('message' => 'Message should not be blank.')),
new Length(array('min' => 5))
)
));
$resolver->setDefaults(array(
'constraints' => $collectionConstraint
));
}
public function getName()
{
return 'homePage';
}
And for last place routing and TWIG:
mail_create:
path: /homePage
defaults: { _controller: "YodaHomeBundle:Home:contact" }
requirements: { _method: post }
[...]
<form action="{{ path('mail_create') }}" method="post">
{{ form_start(form) }}
{{ form_widget(form) }}
{{ form_end(form) }}
</form>
[...]
Please for support, everywhere are solutions for different routes for contact, and I have everything on one page. All hints are welcome, please for comments!
Uland
you need render your form on layout twig by:
public function indexAction(){
$form = $this->createForm(new ContactType());
return $this->render('YodaHomeBundle::layout.html.twig',array('form' => $form->createView());
}
Or you can split layout, one controller is one layout:
Controller:
class HomeController extends Controller{
/**
* #Route("/home", name="homePage")
* #Template()
*
*/
public function indexAction(){
return $this->render('YodaHomeBundle::layout.html.twig');
}
public function contactAction(Request $request)
{
$form = $this->createForm(new ContactType());
// do your code
return array(
'YodaHomeBundle::contactlayout.html.twig',
array('form' => $form->createView());
}
}
And for TWIG:
layout.html.twig:
[..]
<div>{{ render(controller('YodaHomeBundle:Home:contact')) }}</div>
[..]
contactlayout.html.twig:
[..]
<form action="{{ path('mail_create') }}" method="post">
{{ form_start(form) }}
{{ form_widget(form) }}
{{ form_end(form) }}
</form>
[..]
This is because you don't pass to your view the form object created in your controller because you don't call your contact controller.
If it's a one page, creat a twig view named contact.html.twig with your form and add in your index twig template where you want render the form :
{{ render(controller('YodaHomeBundle:Home:contact')) }}
This twig method will call your indexController and the contactAction