im developing the create student requiriment for my application.
I have my Student entity, the entity contains two properties:
User (instance of User entity)
Course (instance of Course entity)
I build the form, but i wish render the form same times via a click button. On this way the administrator could be add any students without refresh the page.
Its is possible ? How manage the submit on the controller ?
Any ideas ? Thanks
NOTE: Im search a similiar Phpmyadmin behavior when add a new record.
What you should do is create a new object and form (e.g. StudentCollection) that allows for adding the student form using the collection type. This will allow you to manage the dynamically adding/removing student forms a lot better.
More on form collections here http://symfony.com/doc/current/cookbook/form/form_collections.html
e.g. Assuming you have a student form called StudentFormType, something like this should work. There's a good example on the link above that you should use if you want to know how to dynamically add/remove student forms as well as handle the submission.
// src/PathTo/YourBundle/Form/Type/StudentCollectionFormType.php
// Form object
class StudentCollectionFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('students', 'collection', array(
'type' => new StudentFormType(),
'allow_add' => true,
'allow_delete' => true,
))
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'PathTo\YourBundle\Model\StudentCollection',
));
}
// ...
}
// src/PathTo/YourBundle/Model/StudentCollection.php
namespace PathTo\YourBundle\Model;
// ...
class StudentCollection
{
// ...
protected $students;
public function __construct()
{
$this->students = new \Doctrine\Common\Collections\ArrayCollection();
}
public function getStudents()
{
return $this->students;
}
public function addStudent(Student $student)
{
$this->students->add($student);
}
public function removeStudent(Student $student)
{
$this->students->removeElement($student);
}
}
Then in your controller
// src/PathTo/YourBundle/Controller/StudentController.php
public function editAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$collection = new StudentCollection();
// Prepopulate the collection with students
// ...
$form = $this->createForm(new StudentCollectionFormType(), $collection);
$form->handleRequest($request);
if ($form->isValid()) {
foreach ($collection->getStudents() as $student) {
$em->persist($student);
}
$em->flush();
// redirect back to some edit page
// ...
}
// render some form template
// ...
}
Related
I'm using Fos_rest to do a webservice.
I recive an entity from an Angular App in JSON.
JSON example :
{"model":
{
"trademark":
{"id":1,"name":"Alfa Romeo"},
"type":
{"id":1,"code":"car","name":"Car"},
"name":"147"
}
}
The entity is composed of two sub entities, called "trademark" and "type".
When receiving a POST, in controller does the following:
public function cpostAction(Request $request, $idTrademark)
{
$entity = new Model();
$form = $this->createForm(ModeloType::class, $entity);
$form->handleRequest($request);
if ($form->isValid()) {
$entity = $form->getData();
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
/*do things with the entity and return*/
}
}
The problem is given when doing Flush, since it recognizes "trademark" and "type" as new entities, since these already exist when owning an "id".
How can I force the entity manager to recognize the entities "trademark" and "type" from the database?
P.S:
Form Type:
class ModelType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name')
->add('trademark', TrademarkType::class)
->add('type', TypeType::class)
->add('id');
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Model',
'csrf_protection' => false,
'allow_extra_fields' => true,
));
}
}
You should not do a find() : it does a database request. Prefer:
$em->getReference(Type::class, $entity->getType()->getId())
It does not a database query. The only problem is getReference does not check if the entity is still existing in the database. For me getReference should only be used to transform an id to the doctrine proxy object
I'm using a Form Builder to create a form for use in an application. In the form, the user needs to be able to select a list of events that they are associated with. However, I'm unable to figure out how exactly I can pass the user's ID to the form builder?
My code is like this at the moment:
EvType.php
<?php
// src/Acme/MembersBundle/Form/Type/EvType.php
// This is to handle forms for the Members Form
namespace Acme\MembersBundle\Form;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class EvType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$centre = $this->centre;
$builder->add('id', 'integer', array('required'=>false));
$builder->add('list','entity', array('class'=>'Acme\InstructorBundle\Entity\MapLists', 'property'=>'name',
'query_builder' => function(EntityRepository $br) {
return $br->createQueryBuilder('ml')
->where('ml.user = :user')
->setParameter('user','1' );
}));
$builder->add('eventHorse', 'text', array('required'=>false));
}
public function getName()
{
return 'ev';
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\InstructorBundle\Entity\MapListCentreMembers',
'csrf_protection' => false,
'csrf_field_name' => '_token',
// a unique key to help generate the secret token
'intention' => 'task_item',
));
}
}
->setParameter('user','1' ); is where I want to be able to pass the User's ID from the form. For now, I've statically assigned the user ID.
DefaultController.php
// User ID
$userid = $mem['userID'];
// Get Tests from Entity for Form use
$memberEV = $dm->getRepository('InstructorBundle:MapListMembers')->find($memberint);
// Generate Form to edit Tests & Achievements
$ev = $this->createForm( new EvType(), $memberEV);
you can simply pass a value in the __construct.
See below:
EvType.php
class EvType extends AbstractType
{
private $user;
public function __construct($user)
{
$this->user = $user;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$user = $this->user;
....
}
DefaultController.php
$ev = $this->createForm( new EvType($user), $memberEV);
I'm writing my own CAPTCHA class and when the form doesn't validate, I don't want to pre-populate the captcha input with the previous answer, for obvious reasons. I just want to the clear the input before it's rendered.
I've discovered the data option is only for the default value, which is overwritten by what the user enters. I tried the following code:
$form->get('captcha')->setData(null);
.. After the request is bound with the form, but an AlreadyBoundException is thrown. I have actually managed to get it working with:
if (isset($formView->children['captcha'])) {
$formView->children['captcha']->vars['value'] = null;
}
But that just looks wrong, and definitely not up to Symfony standards. I've looked through the other options you can provide when building the form, but I can't see anything of note.
Does anyone have any idea?
By the way, I half expect Symfony2 comes packaged with a CAPTCHA solution, this is mainly a learning exercise while I get used to the framework.
I think you want to handle this form field like Symfony handles a password field: it won't get populated. Let's take a look at the PasswordType:
namespace Symfony\Component\Form\Extension\Core\Type;
class PasswordType extends AbstractType
{
public function buildView(FormView $view, FormInterface $form, array $options)
{
if ($options['always_empty'] || !$form->isSubmitted()) {
$view->vars['value'] = '';
}
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'always_empty' => true,
'trim' => false,
));
}
//...
}
So, it's pretty simple: just add $view->vars['value'] = '' in the buildView method of your FormType (i.e. CaptchaType). That means the data of the field is not being cleared, but it won't be passed to the Twig template. Different approach, but the result is the same: the password field stays empty after validation failed.
If you are really lazy, you can use the PasswordType, but since the input of that field will be masked (*****), will that make an annoying captcha field even worse.
Your Form Type maybe look like this:
class CaptchaType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['value'] = '';
}
/**
* {#inheritdoc}
*/
public function getParent()
{
return __NAMESPACE__.'\TextType';
}
/**
* {#inheritdoc}
*/
public function getName()
{
return $this->getBlockPrefix();
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'captcha';
}
}
Edit:
Just found that CaptchaBundle took the same approach.
There is a funny way to modify the Request before handling it. However I'd look into Stephan's answer as it seems more clean.
Something like so:
public function indexAction(Request $request)
{
$form = $this->createForm(Form::class);
$subData=$request->request->get('form');
$subData['task']=null;
$request->request->set('form',$subData);
$form->handleRequest($request);
if ($form->isValid()) {
//do stuff
}
return $this->render('default/index.html.twig', array(
'form' => $form->createView()
));
}
Get submitted data with the name 'form' as an array of values, change the said value to null, then set the request's value with the new one and have the form handle it.
And a simple form
class Form extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('task')
->add('save', SubmitType::class);
}
}
No matter what you type, the data will always be null after submitting the form. Of course, you need to verify the captcha before setting the value to null.
You can pass an incomplete entity to the action called when your control finds form invalid.
public function updateAction(Request $request, $id)
{
$entity = $this->EM()->getRepository('Bundle:Entity')->find($id);
if (!$entity) {
throw $this->createNotFoundException('Unable to find Entity entity.');
}
$form = $this->createForm(new RecommendationType()
,$entity
,array(
'attr' => array(
,'entity' => $entity
)
)
);
$form->bind($request);
if ($form->isValid()) {
$this->EM()->persist($entity);
$this->EM()->flush();
return $this->redirect($this->generateUrl('entity_show'
,array('id' => $id)));
} else {
$entity->setCapthca(Null);
}
return $this->render('Bundle:Entity:edit.html.twig'
,array(
'entity' => $entity
,'form' => $form->createView()
)
);
}
The create action would have similar modification.
Edited my post to be similar to Symfony Cookbook and added some code.
http://symfony.com/doc/current/cookbook/form/form_collections.html
Note that the Entity/Form code posted in the part is the same as the one in the doc linked above.
I have a "Task" entity, which is linked to a "Tag" entity.To keep it simple, "Task" has a single field "description", and a "tag" has a single field "name".A "Tag" is linked to one "Task", and a "Task" is linked to many "Tags".
Entity:
class Task
{
protected $description;
protected $tags;
public function __construct()
{ $this->tags = new ArrayCollection(); }
public function getDescription()
{ return $this->description;}
public function setDescription($description)
{ $this->description = $description; }
public function getTags()
{ return $this->tags; }
public function setTags(ArrayCollection $tags)
{ $this->tags = $tags; }
}
class Tag
{
public $name;
}
At the moment, I use a Collection of "Tags" in the "Task" form to edit all them at once, as described in Symfony CookBook:
Form:
class TagType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\TaskBundle\Entity\Tag',
));
}
public function getName()
{
return 'tag';
}
}
class TaskType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('description');
$builder->add('tags', 'collection', array('type' => new TagType()));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\TaskBundle\Entity\Task',
));
}
public function getName()
{
return 'task';
}
}
But a problem appears when I create a collection of "Tags" larger than a thousand elements. At this time, the form take seconds and seconds to load, and sometimes it crashes due to memory.
$task = new Task();
$tag1 = new Tag();
$tag1->name = 'tag1';
$task->getTags()->add($tag1);
$tag2 = new Tag();
$tag2->name = 'tag2';
$task->getTags()->add($tag2);
//Create a couple thousand more item here ...
//The script crashes here, when the form is being created
$form = $this->createForm(new TaskType(), $task);
The error does not come from Doctrine, which handles very well the whole thing, but from Symfony Form.
Is it a good idea to handle my form using Symfony2 built-in form system (with Collections) in this case, or should I handle it like in the old days, with raw html inputs and server-side validation/saves?
I'm wondering if your issue has nothing to do with the form part of this, but the hydration of the tag objects. If you're asking doctrine to hydrate a ton of objects, it's going to use a big chunk of memory. You may want to look into another method of hydrating the tags, perhaps HYDRATE_ARRAY instead of HYDRATE_OBJECT.
I have a Entity with a property:
/**
* #var string $name
*
* #Assert\NotBlank(groups={"foobar"})
* #ORM\Column(name="name", type="string", length=225, nullable=false)
*/
private $name;
The Form:
class MyType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('name');
}
public function getDefaultOptions(array $options)
{
return array(
'data_class' => '...',
'validation_group' => array('foobar'),
);
}
public function getName()
{
...
}
}
In the Controller I bind the Request and call $form->isValid()
But how to define the validation_group?
From inside your FormType class you can define the validation groups associated to that type by setting your default options:
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'Acme\MyBundle\Entity\MyEntity',
'validation_groups' => array('group1', 'group2'),
);
}
I had exactly the same problem. I solved it that way ...
// Entity
$employee = new Employee();
// Form creation
$form = $this->createForm(new EmployeeForm(), $employee, array('validation_groups'=>'registration'));
I hope that helps!
When building the form in the controller, add a 'validation_groups' item to the options array:
$form = $this->createFormBuilder($users, array(
'validation_groups' => array('foobar'),
))->add(...)
;
It is described in the forms page of the symfony2 book: http://symfony.com/doc/current/book/forms.html#validation-groups
For me, on symfony 2.1, i solved it by adding 'Default' in validation_groups like:
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\MyBundle\Entity\MyEntity',
'validation_groups' => array('Default', 'registration')
));
}
I made a little blog post related to this problem: http://marcjuch.li/blog/2013/04/21/how-to-use-validation-groups-in-symfony/
In this post I’m going to show how to use validation groups in symfony with the example of an order form which should offer the possibility to use separate billing and shipping adresses. This includes 3 steps:
Group validation contstraints for the shipping related form fields together
Determine which validation contraints are applied, depending on the
checkbox value in the submitted form
Copy data from non-shipping fields to shipping fields if checkbox is
not selected
You can also define validation groups dynamically:
// MyCustomType.php
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'validation_groups' => function (FormInterface $form) {
$data = $form->getData();
if (Client::TYPE_PERSON == $data->getType()) {
return array('person');
}
return array('company');
},
));
}