There is a form to create Chain entity.
class ChainType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class, array('label' => 'Company name'))
->add('logoImageURL', TextType::class, array('label' => 'Company logo'));
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'CoreBundle\Entity\Chain'
));
}
}
Here is a newAction to create form and save entity
/**
* Creates a new Chain entity.
*
* #Route("/new", name="chain_new")
*/
public function newAction(Request $request)
{
$chain = new Chain();
$form = $this->createForm(ChainType::class, $chain);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($chain);
$em->flush();
return $this->redirectToRoute('chain_show');
}
return $this->render(
'AdminBundle:ChainPanel:new.html.twig',
array('form' => $form->createView())
);
}
Here is a button to create form
<button type="button" class="btn btn-primary">
<a href="{{ path('chain_new') }}">
Add Chain
</a>
</button>
Problem
When I click on 'Add chain' button the form is not created and I m just redirected to 'chain_show' route. What is wrong with my code?
UPDATE - MY SOLUTION
In controller I put newAction before showAction. This fixed problem. However I couldn't find explanation
I'm not sure what are you trying to do, but if you're trying to create a page with ChainType form you should also create a template for that page, like this:
.....
{% block YOUR_BLOCK %}
{{ form(form) }}
{% endblock %}
......
and, also, you don't have to wrap a tag with button tag, just give class="btn btn-primary" to that a tag.
Your issue is that you're using the variable which holds the new Chain object. Please see my example below and this should fix your problem...
public function newAction(Request $request)
{
$chain = new Chain();
$form = $this->createForm(ChainType::class, $chain);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$chain = $form->getData();
$em = $this->getDoctrine()->getManager();
$em->persist($chain);
$em->flush();
return $this->redirectToRoute('chain_show');
}
return $this->render(
'AdminBundle:ChainPanel:new.html.twig',
array('form' => $form->createView())
);
}
You're also creating the submit button incorrectly. To test it's all working properly, just use (in your .twig.html file):
{{ form(form) }}
Interesting though, I guess the HTML is wrong for a link. You shouldn't have button wrapped to an anchor tag. Some browsers doesn't support it. instead use as below :
<a href="{{ path('chain_new') }}" class="btn btn-primary">
Add Chain
</a>
If this doesn't solve your issue, Do you see a new entity being created when you click on the link?
In controller I put newAction before showAction. This fixed problem. However I couldn't find explanation
Related
I have my entity Article and one single table inheritance like this :
/**
* Article
*
* #ORM\Table(name="article")
* #ORM\Entity(repositoryClass="PM\PlatformBundle\Repository\ArticleRepository")
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="media", type="string")
* #ORM\DiscriminatorMap({"article" = "Article", "movie" = "Movie", "image" = "Image", "text" = "Text"})
*/
class Article
{
protected $id;
protected $title;
protected $description;
protected $author;
//other attributes and setters getters
}
class Image extends Article
{
private $path;
//getter setter
}
class Movie extends Article
{
private $url;
//getter setter
}
So my article's object type is either Image or movie or text only. Ok now I would like build a form wherein users can post a new article : in this form, the user has to choice between tree type (3 radios button) : image OR movie OR text only and of course the other fields : title and description. How I can do that ? Because with the command
php bin/console doctrine:generate:form myBundle:Article
The form rendered is :
class ArticleType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', TextType::class)
->add('description', TextareaType::class)
->add('save', SubmitType::class);
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'PM\PlatformBundle\Entity\Article'
));
}
}
I don't know the way to implement my STI relation in this form. Because I have not field in my Article entity/object for the type (only in my table).
I have to add a Custom ChoiceType() field but it require a attribute.
When I try to add this in the form :
->add('path', SearchType::class)
->add('url', UrlType::class)
I got this error :
Neither the property "path" nor one of the methods "getPath()", "path()", "isPath()", "hasPath()", "__get()" exist and have public access in class "PM\PlatformBundle\Entity\Article".
Because I have create an instance of Article, not an instance of Image or Movie. Initially I created a STI thinking a new instance of Article would allow me also to define the "type" of article. But not ? Right ?
You will have to make three forms (one for an Article, one for a Movie and one for an Image). Then, in your controller, you have to options to deal with them:
Either you use one action to handle the three forms (you can check wich one is submitted by using $form->isSubmitted())
You create one action by form, and set the form action URL for each form to the correct controller.
Finally, in your template, you encapsulate your forms in a div, and use the example in my previous post.
{% extends "CoreBundle::layout.html.twig" %}
{% block title %}{{ parent() }}{% endblock %}
{% block btn_scrollspy %}
{% endblock %}
{% block bundle_body %}
<div class="well">
<div class="selector">
<input type="radio" name="form-selector" value="article-form"> Article
<input type="radio" name="form-selector" value="movie-form"> Movie
<input type="radio" name="form-selector" value="image-form"> Image
</div>
<div class="form article-form" style="display: none;">
{{ form(articleForm) }}
</div>
<div class="form movie-form" style="display: none;">
{{ form(movieForm) }}
</div>
<div class="form image-form" style="display: none;">
{{ form(imageForm) }}
</div>
</div>
{% endblock %}
I agree with dragoste in the comments: you can't expect the form to deduce by himself the type of class you want to instantiate based on a value.
Roughly, Image and Movie are the same type as Article, but an Article is not an Image and/or a Movie.
You will have to check that manually. You can do that server side like explained in the comments, with a field using mapped: false to determine the type of entity you need to instantiate, or client side with javascript by using three forms (one for a movie, one for an article, one for an image) and by displaying the correct one based on your radio button.
Edit: How to display the correct form in JS?
I created a JSFiddle to show you how you can do this using jQuery : https://jsfiddle.net/61gc6v16/
With jQuery documentation, you should be able to quickly understand what this sample do, and to adapt it to your needs. :)
#Boulzy I did this and it works:
class ArticleController extends Controller
{
public function addAction(Request $request)
{
$form1 = $this->get('form.factory')->create(TextOnlyType::class);
$form2 = $this->get('form.factory')->create(ImageType::class);
$form3 = $this->get('form.factory')->create(MovieType::class);
return $this->render('PMPlatformBundle:Article:add.html.twig', array(
'ArticleTextform' => $form1->createView(),
'ArticleImageform' => $form2->createView(),
'ArticleMovieform' => $form3->createView(),
));
}
public function addArticleTextAction(Request $request)
{
$ArticleText = new TextOnly;
$form = $this->get('form.factory')->create(TextOnlyType::class, $ArticleText);
if ($request->isMethod('POST') && $form->handleRequest($request)->isValid()) {
$ArticleText->setAuthor(5);
$ArticleText->setLikes(0);
$em = $this->getDoctrine()->getManager();
$em->persist($ArticleText);
$em->flush();
$request->getSession()->getFlashBag()->add('notice', 'Annonce bien enregistrée.');
$listComments = $ArticleText->getComments();
return $this->render('PMPlatformBundle:Article:view.html.twig', array(
'article' => $ArticleText,
'listComments' => $listComments
));
}
return $this->render('PMPlatformBundle:Article:add.html.twig', array(
'form' => $form->createView(),
));
}
public function addArticleImageAction(Request $request)
{
//the same system as TextOnly
}
public function addArticleMovieAction(Request $request)
{
//the same system as TextOnly
}
}
(I override action directly in my template. With POST method.)
addAction is my contoller which is called by route for display the view of three forms. As you can see this code works as long as there is no error on submitted form. Because, in this case (when error occured when a form is submitted) each controller needs to return the initial view with the 3 forms. How I can do that ?
I would like to generate page with list of users names and next to every user I would like to have button which would activate/deactivate this user.
I could of course create link with user ID and GET method in controller which would perform operation when link would be clicked. As far as I know it is not recommended though to do it this way due to security concerns. So instead of links to route which would perform operation I would like to have forms and buttons to submit request to PUT route which would change user status.
QUESTION: how to generate such forms (buttons) based on list of users returned by Doctrine?
Form code used to create form/button in user profile:
/**
* Creates a form to activate/deactivate a User entity by id.
*
* #param mixed $id The entity id
*
* #return \Symfony\Component\Form\Form The form
*/
private function createActivationDeactivationForm($id)
{
return $this->createFormBuilder()
->setAction($this->generateUrl('user_activate', array('id' => $id)))
->setMethod('PUT')
->add('submit', 'submit', array('label' => 'Activate/Deactivate'))
->getForm()
;
}
Controller code used for user profile:
/**
* #Route("/user/{id}", name="user_show")
* #Method("GET")
* #Template()
*/
public function showUserAction($id)
{
$em = $this->getDoctrine()->getManager();
$user = $em->getRepository('TestUserBundle:User')->find($id);
if (!$user) {
throw $this->createNotFoundException('Unable to find user');
}
$deleteForm = $this->createDeleteForm($id);
$activateForm = $this->createActivationDeactivationForm($id);
return array(
'user' => $user,
'delete_form' => $deleteForm->createView(),
'activate_form' => $activateForm->createView(),
);
}
Controller PUT method to perform operation from user profile:
/**
* Activate a user.
*
* #Route("/{id}", name="user_activate")
* #Method("PUT")
*/
public function activateAction(Request $request, $id)
{
$form = $this->createActivationDeactivationForm($id);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$user = $em->getRepository('TestUserBundle:User')->find($id);
if (!$user) {
throw $this->createNotFoundException('Unable to find user');
}
$current_user_activity_flag = $user->getActive();
$user->setActive(abs($current_user_activity_flag-1));
$em->persist($user);
$em->flush();
}
return $this->redirect($this->getRequest()->headers->get('referer'));
}
Controller code to be used for users list:
/**
* #Route("/users", name="users_list")
* #Method("GET")
* #Template()
*/
public function listUsersAction()
{
$em = $this->getDoctrine()->getManager();
$users = $em->getRepository('TestUserBundle:User')->findExistingUsers();
//$deleteForm = $this->createDeleteForm($id);
//$activateForm = $this->createActivationDeactivationForm($id);
return array(
'users' => $users,
//'delete_form' => $deleteForm->createView(),
//'activate_form' => $activateForm->createView(),
);
}
I can not pass ID to form like I did for operation from profile cause for every user there is different ID and more of that Symfony generates only first form and ignores rest.
Any idea how to handle it? Or maybe my approach with form/buttons is incorrect and I should just use links instead?
I found solution which works though I am not sure about if it's compliant with best practices.
Instead of passing one form object in controller I generated an array of them with keys based on user ID. Than when looping through array in TWIG template I use user ID to refer form object created for current user.
Mentioned in question controller for user listing should than look like this:
/**
* #Route("/users", name="users_list")
* #Method("GET")
* #Template()
*/
public function listUsersAction()
{
$em = $this->getDoctrine()->getManager();
$users = $em->getRepository('PSUserBundle:User')->findExistingUsers();
$activate_forms = array();
$delete_forms = array();
foreach($users as $user)
{
$activate_forms[$user->getId()] = $this->createActivationDeactivationForm($user->getId())->createView();
$delete_forms[$user->getId()] = $this->createDeleteForm($user->getId())->createView();
}
return array(
'users' => $users,
'delete_forms' => $delete_forms,
'activate_forms' => $activate_forms,
);
}
... and in TWIG form within foreach should be refered like this:
{{ form_start(activate_forms[user.id], {'attr': {'novalidate': 'novalidate'}}) }}
{% if user.active %}
{{ form_widget(activate_forms[user.id].submit, {'attr': {'class': 'btn btn-xs btn-warning btn-block'}, 'label' : 'Deactivate'}) }}
{% else %}
{{ form_widget(activate_forms[user.id].submit, {'attr': {'class': 'btn btn-xs btn-success btn-block'}, 'label' : 'Activate'}) }}
{% endif %}
{{ form_end(activate_forms[user.id]) }}
I'm new with Symfony2 (2.4.4).
I want to create a HTML layout which shows always a form on top (searchbar). I send the form via post and would like to redirect to another controller, which should pass the user input and generate an output. I created a new function like this:
public function searchFormAction(Request $request)
{
//$defaultData = array('sstring' => 'Suche');
$form = $this->createFormBuilder()
->add('fnr', 'hidden')
->add('sstring', 'search', array('label' => false))
->add('submit', 'submit', array('label' => 'suchen'))
->getForm();
$form->handleRequest($request);
if($request->isMethod('POST'))
{
return $this->redirect('SchmanEmployeeBundle:Employee:search', array(
'sstring' => $form->get('sstring')->getData();
));
}
return $this->render('SchmanEmployeeBundle:Employee:searchForm.html.twig', array(
'form' => $form->createView()
));
}
I extended my base layout (base.html.twig) and include the form with the render function
{% render(controller('SchmanEmployeeBundle:Employee:searchForm')) %}
This works fine and the form is always present in my layout. The given HTML looks like this:
<form name="form" method="post" action="/app_dev.php/">
<div><input type="search" id="form_sstring" name="form[sstring]" required="required"></div>
<div><button type="submit" id="form_submit" name="form[submit]">suchen</button></div>
Now I have 3 questions:
If I submit the form, I don't want to be redirected to the searchAction Controller. This is because the $request->isMethod is always GET. Why? The form actions is post?
In the Symfony Webtool the form section is also empty. I see all form fields (sstring) and the data is always null. Where's the user input?
First off, your form is set to be POST by default, so you should be good. Second, you don't pass any data to be filled by your form, and I think you should. Third, you don't check if the form is valid, which includes the test if it's submitted. You should do this:
$defaultData = array(); // No need for a class object, array is enough
$form = $this->createFormBuilder($defaultData)
->add('fnr', 'hidden')
->add('sstring', 'search', array('label' => false))
->add('submit', 'submit', array('label' => 'suchen'))
->getForm();
$form->handleRequest($request);
if($form->isValid())
{
// Happens if the form is submitted
return $this->redirect('SchmanEmployeeBundle:Employee:search', array(
'sstring' => $form->get('sstring')->getData(); // TODO: This will probably produce an error, fix it
));
}
return $this->render('SchmanEmployeeBundle:Employee:searchForm.html.twig', array(
'form' => $form->createView()
));
Also, I think you shouldn't worry about the form method because you don't have different implementations for other methods. This is the usual way the forms are handled in Symfony. You should read on forms in detail before proceeding, the article is quite informative.
I guess its because you didnt specified, in your routing configuration, that the method of this function is POST.
Because the form never submitted to your function (your function want GET, but send POST)
Where is the last question?
There is a great code to make your search function, it should work (sorry if you dont use annotation).
One good point, you can now use your searchType everywhere in your project, you should make your form like that instead of formbuilder into your controller. Easier to read and to use.
Controller:
/**
* To search something
*
* #Route("/search", name="search")
* #Template()
*/
public function searchAction()
{
$form = $this->createForm(new searchType());
$request = $this->get('request');
if ($request->getMethod() == 'POST')
{
$form->bind($request);
if ($form->isValid())
{
$informations = $form->get('search')->getData();
//make things here
}
}
}
And here is the searchType class:
class searchType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('fnr', 'hidden')
->add('sstring', 'search', array('label' => false))
->add('submit', 'submit', array('label' => 'suchen'));
}
/**
* #return string
*/
public function getName()
{
return 'yournamespace_searchType';
}
}
Currently in a bit of a pickle. I have 2 entities: Main and Sub, it is a OneToMany relationship (one main can have many subs) I made a collection of form embedded together. I first had a search form where a user can search a Main by one of its attributes, then it sends them to a page where there is a form with the searched Main, its attributes listed on the form but is disabled so users cannot edit them, and the enabled fields are from the embedded Sub form which users need to enter in for submission.
1) User searches Main by its attribute, i.e. "pono" (PO number)
2) User is redirected to a page that shows the row that he/she searched for with the listed (pono), (cano), (bano) - it is disabled so it cannot be edited
3) Enabled fields are empty and users must enter the information that would be submitted into the Sub entity.
In my Main entity
/**
* #var Sub
* #ORM\OneToMany(targetEntity="Sub", mappedBy="mainId")
*/
protected $sub;
public function __construct() {
$this->sub = new ArrayCollection();
}
And my Sub entity:
/**
* #var integer
*
* #ORM\Column(name="main_id", type="integer")
*/
protected $mainId;
/**
* #ORM\ManyToOne(targetEntity="Main", inversedBy="sub", cascade={"persist"})
* #ORM\JoinColumn(name="main_id", referencedColumnName="id")
*/
protected $main;
In my Main form:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('pono', 'text', array(
'label' => 'PO: ',
'disabled' => true
))
->add('cano','text', array(
'label' => 'CA: ',
'disabled' => true
))
->add('bano', 'text', array(
'label' => 'BA: ',
'disabled' => true
))
->add('sub', 'collection', array('type' => new SubType()));
}
In my Sub form:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('qty','integer', array(
'label' => 'Qty: '
))
->add('location','text', array(
'label' => 'Location: '
))
->add('priority','text', array(
'label' => 'Priority: '
));
}
So on my controller
public function submitItemAction(Request $request, $pono) {
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository('ItemBundle:Main')
->findOneByPono($pono);
$cano = $entity->getCano();
$bano = $entity->getBano();
$main = new Main();
$main->setPono($pono);
$main->setCano($cano);
$main->setBano($bano);
$sub = new Sub();
$sub->setMain($main);
$main->getSub()->add($sub);
$form = $this->createForm(new MainType(), $main, array(
'method' => 'POST'
))
->add('submit','submit');
$form->handleRequest($request);
if($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($sub);
$em->flush();
return $this->redirect($this->generateUrl('success'));
}
Now when this is submitted, it's submitting BOTH Main and Sub. It's giving me a duplicate Main and the newly added Sub. I know it is what it's supposed to do, but I need it to only submit the Sub. I tried retrieving the id from Main with a $mainid = $entity->getId(); and putting it into $sub->setMainId($mainid) and I keep getting the error message that main_id cannot be null.
Any takers?
Edit: Twig template:
{{ form_start(form) }}
{{ form_label(form.pono) }}
{{ form_widget(form.pono) }} <br>
{{ form_label(form.cano) }}
{{ form_widget(form.cano) }}<br>
{{ form_label(form.bano) }}
{{ form_widget(form.bano) }} <br>
{% for sub in form.sub %}
{{ form_label(sub.qty) }}
{{ form_widget(sub.qty) }} <br>
{{ form_label(sub.location) }}
{{ form_widget(sub.location) }} <br>
{{ form_label(sub.priority) }}
{{ form_widget(sub.priority) }}<br>
{% endfor %}
{{ form_widget(form.submit) }}
{{ form_end(form) }}
After looking at your code, I think it is possible to make it work, there are a few things you will need to fix. I will edit this answer in a few steps.. Also make a backup/commit of your code before you start changing it.
1) In your MAIN entity (add cascade persist)
/**
* #var Sub
* #ORM\OneToMany(targetEntity="Sub", mappedBy="main", cascade={"persist"})
*/
protected $sub;
2) SUB entity:
Remove protected $mainId; and it's annotation.
Remove cascade={"persist"} from ManyToOne
3) Look at your controller action.
$sub = new Sub();
$sub->setMain($main);
$main->getSub()->add($sub);
Pay attention to the setMain() method. You do not want to do this in controller, but automatically in entity. And also you should add to collection manually, but make a method for it. So you will only have this:
$sub = new Sub();
$main->addSub($sub);
4) In MAIN entity add (you might need to import Sub):
public function addSub(Sub $sub) {
$sub->setMain($this);
$this->sub->add($sub);
return $this;
}
You should also add other methods like removeSub(), removeSub(), getSub(). getSub() returns collection, while the first two will return $this.
5) Controller
Do not persist Sub, but Main. (Doctrine will cascade persistance to Sub)
$em->persist($main);
6) You will need to add 'by_reference' option to sub collection inside you Main Form Type.
->add('sub', 'collection', array('type' => new SubType(), 'by_reference' => false));
This will call the actual addSub() method and not call the add method directly.
7) I do not know why you make a new Main entity below.
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository('ItemBundle:Main')
->findOneByPono($pono);
$cano = $entity->getCano();
$bano = $entity->getBano();
$main = new Main();
$main->setPono($pono);
$main->setCano($cano);
$main->setBano($bano);
Try to change to:
$em = $this->getDoctrine()->getManager();
$main = $em->getRepository('ItemBundle:Main')
->findOneByPono($pono);
You probably should define Pono as unique.
I've been trying to create a simple form in symfony but each time I try to submit I get the following error:
ERROR: The CSRF token is invalid. Please try to resubmit the form.
After surfing on the Internet and reducing the code to almost empty. I still get that error. Most of the people who I've seen asking for that solved the error using the following twig code
{{ form_rest(form) }}
The problem is that I'm using it, it's like when I bind the request it doesn't do it correctly. I don't know what else can I do.
This is my small twig template:
<div><h2>Insert new activity</h2></div>
<div>
<form id="new-activity" action="{{ path('create') }}" method="post" {{ form_enctype(form) }}>
{{ form_rest(form) }}
<p>
<button type="submit">Submit</button>
</p>
</form>
</div>
As you can see it is pretty small code. This is my form render code:
/**
* Displays a form to create a new Activity entity.
*
* #Route("/new", name="sucr_new")
* #Template()
*/
public function newAction() {
$initialConfig = new SucrConfiguration();
$finalConfig = new SucrConfiguration();
$activity = new SucrActivity();
$data = array("activity" =>$activity,
"initialConfig" => $initialConfig,
"finalConfig" => $finalConfig);
$form = $this->createForm(new ActivityType(), $data);
return array(
'data' => $data,
'form' => $form->createView()
);
}
And this is the code that should handle the submition:
/**
* Displays a form to create a new Activity entity.
*
* #Route("/create", name="create")
* #Method("post")
* #Template("EusocSucrBundle:Sucr:new.html.twig")
*/
public function createAction() {
$initialConfig = new SucrConfiguration();
$finalConfig = new SucrConfiguration();
$activity = new SucrActivity();
$data = array("activity" =>$activity,
"initialConfig" => $initialConfig,
"finalConfig" => $finalConfig);
$form = $this->createForm(new ActivityType(), $data);
if ($this->getRequest()->getMethod() == 'POST') {
$form->bindRequest($this->getRequest());
if ($form->isValid()) {
return $this->redirect($this->generateUrl('sucr_show', array('id' => 1)));
}
var_dump($form->getErrorsAsString());
}
return array(
'data' => $data,
'form' => $form->createView()
);
}
Also note that I can see the hidden token in my browser.
Any ideas?