I have following problem. I would like to create a form which works on two entities. I have followings entities: first entity is user entity, and it is connected relationally to the second entity(column bip relate to entity "BIP").
Now I have following code:
BIPType.php:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name');
}
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'AppBundle\Entity\Bip',
);
}
And UserType.php:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$biptype = new BIPType();
$builder->add('bip', $biptype);
}
public function getParent()
{
return 'FOS\UserBundle\Form\Type\RegistrationFormType';
// Or for Symfony < 2.8
// return 'fos_user_registration';
}
public function getBlockPrefix()
{
return 'app_user_registration';
}
// For Symfony 2.x
public function getName()
{
return $this->getBlockPrefix();
}
Unfortunately, error has occured
Catchable Fatal Error: Argument 1 passed to UserBundle\Entity\User::setBip() must be an instance of AppBundle\Entity\Bip, array given, called in /home/spake/php/gryf/vendor/symfony/symfony/src/Symfony/Component/PropertyAccess/PropertyAccessor.php on line 556 and defined
What to do, my friends? Thanks in advice.
You need to declare the Entity class of that Form Class like this:
$builder->add('bip', new BipType(), array(
'data_class' => 'namespace/to/BipEntity'
));
Related
I want to create a form in Symfony. The form needs a collection of task fields which are loaded from the database. Every single field should have a dropdown to select a person who is supposed to do this task.
What I want to do is to map those two entities together. What I have so far is this:
createFormType.php
class CreateFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add("dateValidFrom", DateTimeType::class)
->add("dateValidUntil", DateTimeType::class)
->add("generalTasks", CollectionType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
"data_class" => DTOPlan::class
]);
}
}
DTOPlan.php
class DTOPlan
{
public \DateTimeImmutable $dateValidFrom;
public \DateTimeImmutable $dateValidUntil;
public array $tasks;
public function setGeneralTasks(array $generalTasks): void
{
$this->generalTasks = array_fill_keys($generalTasks, null);
}
...getter and setter
}
Controller.php
public function newPlan(Request $request)
{
$generalTasks = $this->generalTaskRep->findAll();
$dtoPlan = new DTOPlan();
$dtoPlan->setGeneralTasks($generalTasks);
$form=$this->createForm(CreateFormType::class, $dtoPlan);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid())
{
$this->planRepository->updatePlan($form->getData());
$this->redirectToRoute("app.plan.show");
}
return $this->render("/Plan/plan_create.html.twig", [
"createForm" => $form->createView()
]);
}
The labels are now correct: Form
Now I want to insert a dropdown into them for selecting firstName and lastName of Person
How can I manage that?
I have a class RegisterType for my form:
<?php
class RegisterType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name');
$builder->add('price');
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Register'
));
}
public function getName()
{
return 'register';
}
}
?>
The Register Entity has a relation with an Entity called Product
I would like to add this condition to the buildForm function:
<?php
if ($product->getTitle() == 'SuperProduct') {
$builder->add('amount', 'money', [
'required' => FALSE,
]);
}
?>
If the product title has the value 'SuperProduct' then I add a field in the the form.
But I have no idea about the syntax I have to use to call another Entity value in a FormType.
Thanks in advance for your help.
Ah good you've implemented your FormType as an independent class rather than in a controller, so what I'd do is pass in your product entity as an option to a private variable within your FormType class. I'm not sure what class your product is or the name space it resides in, so you'll have to fill that in.
I've also written this in the expectation that you could have more than one option to pass through in the future, so it'll be useful to other FormType classes you write. Each variable you define in the class scope can be set when you create an instance of the FormType class. Be sure to initialise these variables as false instead of null, or the code in the constructor function won't see them.
<?php
class RegisterType extends AbstractType
{
private $product = false;
public function __construct(array $options = [])
{
foreach ($options as $name => $value) {
if (isset($this->$name)) {
$this->$name => $value;
}
}
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('price')
;
if ($this->product && is_a("\Namespace\To\Your\Entity\Called\Product", $this->product)) {
if ($this->product->getTitle() == 'SuperProduct') {
$builder
->add('amount', 'money', [
'required' => FALSE,
])
;
}
}
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Register'
));
}
public function getName()
{
return 'register';
}
}
?>
That "is_a()" function checks that the product variable passed in through the form type class constructor really is an instance of your product entity, so you can be sure that the getTitle() function exists.
Now when you create a new instance of your RegisterType class, you need to pass through an options array including your product. Two conceptual variable names for you to rename here.
<?php
$form = $this->createForm(new RegisterType([
"product" => $instanceOfProductEntityOrNull,
]), $theEntityTheFormWillBeHandling);
?>
Try something like this:
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$product = $event->getData();
$form = $event->getForm();
if ($product->getTitle() == 'SuperProduct') {
$form->add('name', 'text');
}
});
And do not forget the specific use
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
I am starting with coding in Symfony and I have the following problem:
Assume I have two Entities 'Client' and 'Project'. They are stored with Doctrine.
A Client has a Id, Name and an Email
A Project has a Id, client_id, name
So basically a project belongs to a Client and a Client has many projects.
My problem now:
When I'm creating a project, I want a dropdown with all possible clients. As I might be using a client drowpown somewhere else in my project I'm asking myself if there is a smart way to something like this:
class ProjectType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', 'text');
$builder->add('client', new ClientListType());
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Project'
));
}
public function getName()
{
return 'project';
}
}
class ProjectController extends Controller
{
public function createAction(Request $request)
{
$project = new Project();
$options = array( ... );
$form = $this->createForm(new ProjectType(), $project, $options);
$form->handleRequest($request);
if($form->isValid()){
// persist project
return $this->redirectToRoute('show_projects');
}
return $this->render('AppBundle:Client:create.html.twig', array(
'form' => $form->createView()
));
}
}
Where ClientListType adds a select statement for all possible Clients to the form.
And $form->isValid() checks if the client (id) is valid or not.
At the moment I have the followin code in ProjectType to generate the dropdown entries:
function __construct($clients)
{
$this->clients = $clients;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', 'text');
$builder->add('client', 'choice', array(
'choices' => $this->buildChoices()
));
}
public function buildChoices()
{
$res = array();
foreach ($this->clients as $client) {
$res[$client->getId()] = $client->getName();
}
return $res;
}
But I'm assuming there is a much better way to do this, because this seems like a common problem.
What I would do is simply this :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', 'text')
->add('client', 'entity', array(
'class'=>'AppBundle\Entity\Client',
'property'=>'name'
));
}
Hope this helps.
In my Symfony2, I have a table of a user's emails (entities in my database):
{% for email in emails %}
...
{{ email.subject }}
...
{% endfor %}
I would like to make these selectable by wrapping the table in a form and adding a checkbox to each of these rows.
What's the best way to approach this in Symfony2? I can only think that I'll have to create a Type inside a Type inside a Type, which seems less than ideal:
class SelectableEmailsType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('selectableEmails', 'collection', [ 'type' => new SelectableEmailType() ]);
}
public function getName()
{
return 'selectableEmails';
}
}
class SelectableEmailType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('email', new EmailType());
$builder->add('selected', 'checkbox');
}
public function getName()
{
return 'selectableEmail';
}
}
class EmailType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('subject', 'text');
...
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults([
'data_class' => 'EmailOctopus\Bundle\ListsBundle\Entity\ListEntity',
]);
}
public function getName()
{
return 'email';
}
}
This is answering the binding question.
// Query a list of emails
$emails = $emailRepository->findAll();
// Turn them into selectable emails
$selectableEmails = array();
foreach($emails as $email)
{
$selectableEmails[] = array('email' => $email, 'selected' => false);
}
// Pass this to the root form
$formData = array('selectableEmails' => $selectableEmails);
After processing a posted form you would pull out the list of selectableEmails and run through it to get the list actually selected.
This is just one approach. It works well for adding additional form related attributes to an entity without changing the entity itself.
I ended up assigning the id. of each email to a checkbox, using this solution:
Build a form having a checkbox for each entity in a doctrine collection
Here is the thing.
I want to make 2 forms in one view. One is bound to an entity and the other is a file type where I want to put .csv and catch the informations inside to fill the first entity.
I've already created the first one but I can't figure how to create the second and integrate it correctly in the same view so they don't fight each other. Here is my files (the csv process is not here yet)
My controller:
public function adminAction()
{
$form = $this->createForm(new StudentsType, null);
$formHandler = new StudentsHandler($form, $this->get('request'), $this->getDoctrine()->getEntityManager());
if( $formHandler->process() )
{
return $this->redirect( $this->generateUrl('EnsgtiEnsgtiBundle_ens'));
}
return $this->render('EnsgtiEnsgtiBundle:Appli:admin.html.twig', array(
'form' => $form->createView(),
));
}
First form:
class StudentsType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('nom', 'text')->setRequired(false)
->add('prenom', 'text')
->add('email', 'email')
->add('codeEtape', 'text')
->add('file', new StudentsListType)->SetRequired(false);
}
public function getName()
{
return 'ensgti_ensgtibundle_studentstype';
}
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'Ensgti\EnsgtiBundle\Entity\Students',
);
}
}
The Handler:
class StudentsHandler
{
protected $form;
protected $request;
protected $em;
public function __construct(Form $form, Request $request, EntityManager $em)
{
$this->form = $form;
$this->request = $request;
$this->em = $em;
}
public function process()
{
if( $this->request->getMethod() == 'POST' )
{
$this->form->bindRequest($this->request);
if( $this->form->isValid() )
{
$this->onSuccess($this->form->getData());
return true;
}
}
return false;
}
public function onSuccess(Students $students)
{
$this->em->persist($students);
$this->em->flush();
}
}
Second Form:
class StudentsListType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('', 'file');
}
public function getName()
{
return 'ensgti_ensgtibundle_studentslisttype';
}
}
The Handler:
class StudentsListHandler
{
protected $form;
protected $request;
protected $em;
public function __construct(Form $form, Request $request, EntityManager $em)
{
$this->form = $form;
$this->request = $request;
$this->em = $em;
}
public function process()
{
if( $this->request->getMethod() == 'POST' )
{
$this->form->bindRequest($this->request);
if( $this->form->isValid() )
{
//$this->onSuccess($this->form->getData());
print_r($this->form->getData());
return true;
}
}
return false;
}
public function onSuccess(/*StudentsList $studentsList*/)
{
//$this->em->persist($studentsList);
//$this->em->flush();
echo 'Petit test';
}
}
With this code I get this:
Neither property "file" nor method "getFile()" nor method "isFile()" exists in class "Ensgti\EnsgtiBundle\Entity\Students"
Which is normal since I'm bind to an entity with no file type. I'm a beginner so I keep reading docs but it's still hard for me to understand everything. Is there a way I can make 2 forms in the same view with one that is not bind to an entity but that will persist on it anyway via csv treatment??
The problem is that the form expects file to be a property of the bound entity. You just need to set the property_path to false. (Reference) e.g.
->add('file', new StudentsListType, array('property_path' => false))
Edit:
If it would make more sense to have the 2 forms completely separate, then remove the above ->add(/*...*/) entirely and just pass both forms to the view from the controller. You can then render each one individually.
use <iframe> to do that. Here is the link how to use it, just put in src atribute url to needed page. And you may display 2 pages in one layout. And this is how it looks