Creating collection of read-only entities in Symfony2 - forms

My question is about creating collection of entities. I know about "How To Embed Collection Forms" and successfully used it. But in this case I have:
Simple class
class Thing
{
/**
* #ORM\ManyToMany(targetEntity="DicStyle", mappedBy="things")
* ....
*/
protected $styles;
public function __construct()
{
$this->styles = new ArrayCollection();
}
}
Dictionary of styles
class DicStyle
{
.....
}
I don't need to create form for DicStyle objects, because this is read only objects = dictionary (unchangeable). So, I want to create a form with something like this:
$builder->add('styles', 'collection', array(
'type' => 'entity', 'options' => array(
'class' => 'MyEntityBundle:DicStyle'
)
))
Of course it's pseudo-code. I can not imagine how to implement it.
The result
Suppose, I have:
Table "Thing" with one row (id = 1).
Table "DicStyle" with 6 rows (id = from 1 to 6).
Table "mtm_thing_dicstyle" (many-to-many table)
In the form, I choose two DicStyle (id=3, id=5) for the Thing. So, the mtm_thing_dicstyle contains:
thing_id dicstyle_id
-------- ------------
1 3
1 5

Try this form:
$builder->add('styles', 'entity', array(
'class' => 'MyEntityBundle:DicStyle'
'property'=>'name' //what property do you want to see when you select,
'multiple" => true //you'll be able to select many DicStyle
'expanded' => false //it'll shown on a multiple choice select tag
)
);

To select one or more objects in a form to relate them to your result you can use the form field type "entity". (See form-types)
$builder->add('styles', 'entity', array(
'class' => 'MyEntityBundle:DicStyle',
'property' => 'name', // property you want to be displayed
'expanded' => true,
'multiple' => true
)
);
This would render checkboxes and therefore the possibility to select multiple entities to be referenced.
Keep in mind, if you use multiple => true there are two options:
expanded => false: renders a multiselect field
expanded => true: renders checkboxes
See form-entity-type-options

Related

Symfony: many to many relation with text fields

We have a many to many relation with two entity Product {properties: name, details } and Tag {properties: name}. Now when a user add product, he must be able to create tags as well. So I tried this
$builder->add('tags', 'entity', array(
'class' => 'AppBundle:Tag',
'multiple' => true,
'expanded' => true,
'property' => 'name',
'required' => true
'allow_add' => true ));
Unfortunately, this gives me a drop down list of tags. But what I really want is multiple text input fields where user can enter the name of the tags and then this should save in the database when user save the category form.
All solutions I found so far are all related to either drop down or checkbox choices. But in my case, I don't have a list of tags in the database and I want to create the tags when user create a product. So how can I do that?
The entity type field could be only select, checkboxes or radio buttons (depending on expanded and multiple options), so these are not option for you.
You can solve multiple text fields rendering by implementing Tag type and embed it to your form as a collection.
Alter ProductType by collection field:
...
$builder->add('tags', CollectionType::class, array(
'allow_add' => true,
'allow_delete' => true,
'entry_type' => TagType::class,
'entry_options' => array(
...
),
)
...
Create TagType itself:
class TagType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('tagName', FormType\TextType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Entity\Tag',
)
);
}
}
But be aware of downsides - if there are not any tags assigned to the Product, the won't be any fields rendered. You have basically two options - 1) add few empty tags to the Product entity before rendering the form, 2) implement some kind of javascript method to add new tag fields.

Symfony: how to place a specific item in first position with query_builder option?

In a form, I use an EntityType field, which allows selection of several items from entity Member. I am sending the id of a specific member to my form through the form's option (a variable named $selfId) and would like to use the query_builder function to return a list of members where this specific member would appear in first position. How could I achieve this? I'm using Symfony 3.
I'm thinking of something like this:
->add('members', EntityType::class, array(
'required' => true,
'label' => 'Members',
'class' => 'AppBundle:Member',
'multiple' => true,
'query_builder' => function (MemberRepository $er) use ($selfId) {
$qb = $er->createQueryBuilder('m');
return $qb
->orderBy('m.id = :selfId') // invented code!!!!!!!
->setParameter('selfId', $selfId)
;
}
))
;
There is an item you can add to the code above item called preferred_choices that should do what you are asking
http://symfony.com/doc/current/reference/forms/types/entity.html#preferred-choices

Remove null values coming from empty collection form item

I'm trying to implement a ManyToMany relation in a form between 2 entities (say, Product and Category to make simpe) and use the method described in the docs with prototype and javascript (http://symfony.com/doc/current/cookbook/form/form_collections.html).
Here is the line from ProductType that create the category collection :
$builder->add('categories', 'collection', array(
'type' => 'entity',
'options' => array(
'class' => 'AppBundle:Category',
'property'=>'name',
'empty_value' => 'Select a category',
'required' => false),
'allow_add' => true,
'allow_delete' => true,
));
When I had a new item, a new select appear set to the empty value 'Select a category'. The problem is that if I don't change the empty value, it is sent to the server and after a $form->bind() my Product object get some null values in the $category ArrayCollection.
I first though to test the value in the setter in Product entity, and add 'by_reference'=>false in the ProductType, but in this case I get an exception stating that null is not an instance of Category.
How can I make sure the empty values are ignored ?
Citing the documentation on 'delete_empty':
If you want to explicitly remove entirely empty collection entries from your form you have to set this option to true
$builder->add('categories', 'collection', array(
'type' => 'entity',
'options' => array(
'class' => 'AppBundle:Category',
'property'=>'name',
'empty_value' => 'Select a category'),
'allow_add' => true,
'allow_delete' => true,
'delete_empty' => true
));
Since you use embedded forms, you could run in some issues such as Warning: spl_object_hash() expects parameter 1 to be object, null given when passing empty collections.
Removing required=>false as explained on this answer did not work for me.
A similar issue is referenced here on github and resolved by the PR 9773
I finally found a way to handle that with Event listeners.
This discussion give the meaning of all FormEvents.
In this case, PRE_BIND (replaced by PRE_SUBMIT in 2.1 and later) will allow us to modify the data before it is bind to the Entity.
Looking at the implementation of Form in Symfony source is the only source of information I found on how to use those Events. For PRE_BIND, we see that the form data will be updated by the event data, so we can alter it with $event->setData(...). The following snippet will loop through the data, unset all null values and set it back.
$builder->addEventListener(FormEvents::PRE_BIND, function(FormEvent $event){
$data = $event->getData();
if(isset($data["categories"])) {
foreach($data as $key=>$value) {
if(!isset($value) || $value == "")
unset($data[$key]);
}
$event->setData($data);
});
Hope this can help others !
Since Symfony 3.4 you can pass a closure to delete_empty:
$builder
->add('authors', CollectionType::class, [
'delete_empty' => function ($author) {
return empty($author['firstName']);
},
]);
https://github.com/symfony/symfony/commit/c0d99d13c023f9a5c87338581c2a4a674b78f85f

Select an related entity manually

I have an entity that can have an image. This is done using a OneToMany relationship. Now I want the user to choose an image. I can of course use a form field like this:
$builder->add('image', 'entity', array(
'label' => 'Image',
'class' => 'VendorNameBundle:Image',
'property' => 'id',
) );
It renders as expected a dropdown with all the ids. But what I actually want is only a single field that is hidden and that stores the id of the selected image.
This would work but it seems not like a good thing …
In the type:
$builder->add('_image', 'text', array(
'data' => $object->getImage()->getId(),
'property_path' => false
));
In the controller:
$_image = $form["_image"]->getData();
if ($_image) {
$image = $this->getDoctrine()->getRepository('VendorNameBundle:Image')->find($_image);
if ($image) {
$object->setImage($image);
}
}
My question is: How can I get a hidden text field that contains the ID of the related object and stores the new selected one?

Example of Zend Form with Collection Element using Forms not Fieldsets in Zend Framework2

I need a straight forward working example how I can include a collection element in Zend Form, I have seen some examples from Zend Framework 2 site and from previous posts in StackOverflow where most of them pointed to this link. But right now I am not using Fieldsets and staying with Forms, so in case if someone can direct me in the right way, how I can include a simple collection element when the user gets a page where the user can choose multiple choices from the shown collection form. Much better would be populating the collection form from database.
I have searched in the internet for quite a sometime now and thought I would post here, so that Zend profis can give their suggestions.
Just For Information:
Normally one can include a static dropdownbox in Zend Form in this fashion
$this->add(
array(
'name' => "countr",
'type' => 'Zend\Form\Element\Select',
'options' => array(
'label' => "Countries",
'options' => array(
'country1' => 'Brazil',
'country2' => 'USA',
'country3' => 'Mexico',
'country4' => 'France',
)
)
)
);
So I am expecting a simple example which could give me a basic idea how this can be done.
To be honest, I don't see your problem here. Since form collections extend Fieldset which extends Element, you can just add it to the form as a regular element. The view helpers will take care of the rendering recursively.
Step 1: Create a form collection (create an instance of Zend\Form\Element\Collection). If the elements have to be added dynamically in some way, I'd create a factory class for this purpose.
Step 2: Add it to the form. (For example using $form->add($myCollectionInstance).)
Step 3: Render it. Zend\Form\View\Helper\Collection is a pretty good view helper to render the whole form without any pain.
You can also create a new class extending Zend\Form\Element\Collection and use the constructor to add the fields you need. Thus, you can add it to the form using the array you've pasted in your question. Also, you could directly use it in annotations.
Hope this helps.
If you just want to fill in a select list with option values you can add the array to the select list in a controller:
$form = new MyForm();
$form->get('countr')->setOptions(array('value_options'=>array(
'country1' => 'Brazil',
'country2' => 'USA',
'country3' => 'Mexico',
'country4' => 'France',
));
the array can be fetched from db.
this is a different example for using form collections in the simplest way.
In this example it creates input text elements in a collection and fills them in. The number of elements depends on the array:
class MyForm extends \Zend\Form\Form
{
$this->add(array(
'type' => '\Zend\Form\Element\Collection',
'name' => 'myCollection',
'options' => array(
'label' => 'My collection',
'allow_add' => true,
)
));
}
class IndexController extends AbstractActionController
{
public function indexAction
{
$form = new MyForm();
$this->addElementsFromArray($form, array(
'country1' => 'Brazil',
'country2' => 'USA',
'country3' => 'Mexico',
'country4' => 'France',
));
//the above line can be replaced if fetching the array from a db table:
//$arrayFromDb = getArrayFromDb();
//$this->addElementsFromArray($form, $arrayFromDb);
return array(
'form' => $form
);
}
private function addElementsFromArray($form, $array)
{
foreach ($array as $key=>$value)
{
$form->get('myCollection')->add(array(
//'type' => '\Zend\Form\Element\SomeElement',
'name' => $key,
'options' => array(
'label' => $key,
),
'attributes' => array(
'value' => $value,
)
));
}
}
}
index.phtml:
$form->setAttribute('action', $this->url('home'))
->prepare();
echo $this->form()->openTag($form);
echo $this->formCollection($form->get('myCollection'));
echo $this->form()->closeTag();