Note: I don't want to create services. It's just a simple application. In flat php this would be a piece of cake, but for some reason it's inordinately difficult in symfony/doctrine... :( The EntityManager is available in controllers, but for some reason it's not available in other classes. I don't care about MVP and clean code and all that. I just want it to work. I don't understand why it has to be such a pain in the butt.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$current_year=getdate()['year'];
$em = new \Doctrine\ORM\EntityManager;
$approving_teachers = $em->createQuery('select u from Teacher t where IsAuthorized = 1')->getResult();
$builder
->add('startDatetime', 'datetime', array('years' => range($current_year-1, $current_year), 'time_widget' => 'text'))
->add('hoursServed', 'number', array('constraints'=> array(new \Symfony\Component\Validator\Constraints\Range(array('min'=>0, 'max'=>500)))))
->add('activity')
->add('student')
->add('approvingTeacher', 'entity', array(
'class' => 'Teacher',
'choices' => $approving_teachers
))
;
}
Why don't you use an 'entity field type': http://symfony.com/doc/current/reference/forms/types/entity.html?
With entity field type you can use EntityRepository to retrieve anything you need.
Add query_builder to your approvingTeacher field. And use EntityRepository.
use Doctrine\ORM\EntityRepository;
...
->add('approvingTeacher', 'entity', array(
...
'query_builder' => function(EntityRepository $er) {
return something...
}
))
Also, check the docs: http://symfony.com/doc/current/reference/forms/types/entity.html#using-a-custom-query-for-the-entities
Related
I am building a form using the Symfony framework and am trying to understand how to pass an instance of an entity to the form builder.
Controller:
$organization = $user->getOrganization();
$form = $this->createForm(OrganizationCourseType::class, $organization);
OrganizationCourseType class:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('courses', EntityType::class, [
'class' => Course::class,
'choice_label' => 'name',
'multiple' => true,
'expanded' => true,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('course')
->andWhere('course.organization = :organization')
->setParameter('organization', $organization);
},
]);
}
However I get the error:
Notice: Undefined variable: organization
How can I access the entity (organization) within the form builder? Do I need to pass it as an option? If so what is the point of including it in the createForm call in the controller?
To address the second part of your question: "what is the point of including it in the createForm call ?"
When you pass an object as the second argument to createFormBuilder you are passing the initial data for the form. Symfony will try to find properties (or getters/setters) that match the form field name in the object and assign its value to the field. Then, on submission, it'll update your model accordingly.
You'd tipically pass the same type of object as the form's data_class, so in your case that would be an OrganizationCourse. You could do something like the following:
$organizationCourse = new OrganizationCourse();
$organizationCourse->setOrganization($user->getOrganization());
$form = $this->createForm(OrganizationCourseType::class, $organizationCourse);
You could select many Courses and assign them to the Organization.
However, this doesn't look like your use case, since OrganizationCourse looks like a relation rather than an entity, so refer to #ehimel's answer.
In your controller, pass the instance of your entity as a third argument to your $form definition line:
$form = $this->createForm(OrganizationCourseType::class, null, ['organization' => $organization]);
Then retrieve it in your FormType class like so:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$organization = $options['organization'];
$builder->add('courses', EntityType::class, [
'class' => Course::class,
'choice_label' => 'name',
'multiple' => true,
'expanded' => true,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('course')
->andWhere('course.organization = :organization')
->setParameter('organization', $organization);
},
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setRequired(['organization']);
}
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.
I am using PHP with the Silex framework and after some hours trying to find a satisfying solution I am still blocked with the following problem regarding forms and objects containing array of Objects. The hours spent permitted me to find a working solution but I hope there is a better way to do that with Silex.
In my application I have a User class that is defined like :
class User implements UserInterface
{
private $id;
private $username;
private $displayname;
private $capacities;
//...
}
$capacities variable contains an array of objects from another class (Capacity). A Capacity is a specific role in my app with various information (label, place of the capacity ...) and I have added, in that Capacity class a boolean telling if the Capacity is active for a specific user, when attached to a user via the $capacities array.
At the moment I am able to create the form that looks as I want with the following code :
use Planning\Domain\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints as Assert;
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('username', TextType::class, array(
'required' => true,
'label' => "Login (pour Jean Durant → jdurant)"
))
->add('displayname', TextType::class, array(
'label' => "Nom à afficher"
));
$choices = array();
$choicesActive = array();
foreach ($builder->getData()->getCapacities() as $id => $capacity) {
$choices[$capacity->getLabel()] = $capacity->getId();
if ($capacity->getActive()) {
$choicesActive[] = $capacity->getId();
}
}
$builder->add('capacities', ChoiceType::class, array(
'choices' => $choices,
'data' => $choicesActive,
'label' => "Groupes",
'multiple' => True,
'expanded' => True
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => User::class,
));
}
public function getName()
{
return 'capacity';
}
}
but the User object I am getting after the form is validated contains for capacities :
[capacities:Planning\Domain\User:private] => Array
(
[0] => 2
[1] => 4
[2] => 1
)
capacities variable contains the list of values for checkboxes that have been checked in the form. The issue is that my User object is not consistant with its definition which says that the capacities property should be an array of Capacity objects. What I am doing at the moment is that I have added the following code to my controller when $userForm->isSubmitted() and $userForm->isValid() :
// Getting the array from the returned user, should contain Capacity object but just contains the IDs.
$capacitiesChecked = $userForm->getData()->getCapacities();
// We regenerate the full array of capacities for this user
$user->setCapacities($app["dao.capacity"]->findAll());
// Then we will activate capacities that have been checked, one by one
foreach ($capacitiesChecked as $capacityChecked) {
$user->getCapacityById($capacityChecked)->setActive(True);
}
This is working and I am happy with it but being new to Silex and the framework world, I am surprized that I have not been able to find an easier way to answer my problem which I believe should be quite common.
I might be missing something from the Silex/Symfony philosophy and I hope someone there will be able to point me to the correct place to get more information or find a solution!
Edit following #dbrumann answer
As it might not be clear how my data is organized, here are the tables in my database :
user
id
username
displayname
capacity
id
label
place
user_capacity
id_user
id_capacity
There might be an issue with the modeling of my project but I have a Capacity class and a User class and User has an attribute with an array containing all the Capacity available in the database and each one of this Capacity object has an active attribute that is set to True if there is an entry in the table user_capacity that links user and capacity. What I would like is ONE form that allows me to properly update data into tables user and user_capacity.
If I understand correctly Capacity is another entity that you want to link. In that case you could use the more specific EntityType and then filter the number of values by using the query_builder attribute as described in the documentation:
$builder->add('users', EntityType::class, array(
'class' => Capacity::class,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('capacity')
->where('capacity.active = 1')
->orderBy('capacity.id', 'ASC');
},
'choice_label' => 'id',
));
I have found a solution that I think is acceptable and might be useful for someone landing on this page. So what I have done is that I have changed my User class so that the $capacities attribute now contains only Capacity objects that are related to the user. But to get all the capacities available on the form, I am passing them as an option (allCapacities) and iterating over them to find which one are present in User->capacities to check them in the form.
The updated class used to build the form is as following:
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('username', TextType::class, array(
'required' => true,
'label' => "Login (pour Jean Durant → jdurant)"
))
->add('displayname', TextType::class, array(
'label' => "Nom à afficher"
));
$choices = array();
$choicesActive = array();
foreach ($options["allCapacities"] as $id => $capacity) {
$choices[] = $capacity;
if ($builder->getData()->hasCapacity($capacity)) {
$choicesActive[] = $capacity;
}
}
$builder->add('capacities', ChoiceType::class, array(
'choices' => $choices,
'data' => $choicesActive,
'choice_label' => function($category, $key, $index) {
return $category->getLabel();
},
'label' => "Groupes",
'multiple' => True,
'expanded' => True,
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => User::class,
));
$resolver->setRequired(array(
'allCapacities',
));
}
public function getName()
{
return 'capacity';
}
}
This is working as expected and does seem to be overcomplicated but there might be easier solution by changing the design, any comment on this would then be welcome!
I'm trying to build a form in Symfony 3 and am stuck with some issue which I think should be trivial to solve, but I guess I'm not looking at the correct place.
I have 2 objects, Entity and Supplier. 1 entity does have 1 supplier associated to it. 1 supplier can have many entities related to it. (One to Many association).
I'm trying to build the form for the Entity class, with a drop down list providing the supplier it has to be associated with.
Here is the Entity form class:
<?php
namespace VP\SupplierBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use VP\SupplierBundle\Entity\Supplier;
use VP\SupplierBundle\Repository\SupplierRepository;
class EntityType extends AbstractType {
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('name')
->add('supplier', Supplier::class, array(
'class' => 'VPSupplierBundle:Supplier',
'property' => 'name',
'query_builder' => function (\Doctrine\ORM\EntityRepository $er) {
return $er->findAllByIsDeleted(0);
},
'choice_label' => 'name'
))
->add('submitnew', SubmitType::class, array(
'label' => 'Add Entity',
'attr' => array(
'class' => 'btn btn-primary',
)));
}
Here is my SupplierRepository which is supposed to be called:
<?php
namespace VP\SupplierBundle\Repository;
use Doctrine\ORM\EntityRepository;
/**
* SupplierRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class SupplierRepository extends EntityRepository {
public function findAllByIsDeleted($isDeleted) {
$qb = $this->createQueryBuilder('s');
$qb
->where('s.isDeleted = :isDeleted')
->setParameter('isDeleted', $isDeleted);
return $qb;
}
}
Still, I get the following error:
Could not load type "VP\SupplierBundle\Entity\Supplier"
Any idea where it could come from? Tried to look at the official symfony doc and some forum topics but no luck so far...
Thanks a lot for your help!
Working solution provided by #Jeet
//
use Symfony\Bridge\Doctrine\Form\Type\EntityType as DoctrineEntityType;
//
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('name')
->add('supplier', DoctrineEntityType::class, array(
'class' => 'VPSupplierBundle:Supplier',
'query_builder' => function (EntityRepository $er) {
return $er->findAllByIsDeleted(0);
},
'choice_label' => 'name'
))
->add('submitnew', SubmitType::class, array(
'label' => 'Add Entity',
'attr' => array(
'class' => 'btn btn-primary',
)));
}
Thanks!
You are doing it here wrong :
->add('supplier', Supplier::class, array(
'class' => 'VPSupplierBundle:Supplier',
'property' => 'name',
'query_builder' => function (\Doctrine\ORM\EntityRepository $er) {
return $er->findAllByIsDeleted(0);
},
'choice_label' => 'name'
))
You are loading a Supplier::class which unknown to Form Type. I guess you should instead load a EntityType::class form type.
Check here the document. You want to show a choice Field with association to entity, hence you need to have EntityType::class. To recognize the Entity Type, you are already providing your Entity identity through class option. So it makes clear.
Hope it helps!
Maybe i have a very dumb question, but i'm new in Symfony2 and i was wondering if i can build a Search Form with Symfony Form Component, the same way I do with a Registration Form for example.
My Search form will have a Country select field, a Club select field, a Gender radio buttons field a Level select field and the submit button.
Is it possible to do that with the Form Component or to do something like this is better to just build the search form directly in the view?
I've been searching for information about this, but I didn't find anything.
Here is how my SearchPlayerType.php looks like.
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\FormEvents;
use Doctrine\ORM\EntityRepository;
class SearchPlayersType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('country', 'entity', array(
'placeholder' => 'Choose a country',
'class' => 'AppBundle:Country',
'property' => 'name',
'query_builder' => function(EntityRepository $er){
return $er->createQueryBuilder('c')->orderBy('c.name', 'ASC');
},
))
->add('club', 'entity', array(
'placeholder' => 'Choose a club',
'class' => 'AppBundle:Club',
'property' => 'name',
'query_builder' => function(EntityRepository $er){
return $er->createQueryBuilder('c')->orderBy('c.name', 'ASC');
},
))
->add('gender', 'entity', array(
'class' => 'AppBundle:Gender',
'property' => 'name',
'expanded' => true
))
->add('level', 'rating', array(
'label' => 'Playing Level',
'stars' => 5))
;
}
public function getName()
{
return 'SearchPlayer';
}
If it is possible to do it this way I don´t know what Entity my data_class needs to be
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Usuario'
));
}
}
?>
You can go two ways with this: you can either create a form specific model for your search form which will either be a really thin object with public properties; or you can remove the entry for data_class in your form options which will switch your form into returning an array rather than an object (documentation).
The former is the more OO way of doing things and allows you to add validation annotations without embedding those within the form, this way also means you can add getters and setters that transform your search data very easily and not cluttering up your controllers. So your model would look something like:
namespace MyBundle\Form\Model;
class SearchModel
{
public $country;
public $club;
// ...
}
The alternate is just:
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
// don't set 'data_class' in here
));
}
Then when you do $form->getData() you'll just get an array back rather than an object.