I want to create a form select field which looks like this:
<select>
<option value="product.product_id">product_details.detail_name</option>
etc...
</select>
The value is not the problem, the problem is with the label.
I have a product entity and a productDetails entity which contains translated data about a product.
So, in my form type class, in the buildForm method, I have this:
$builder->add('product', 'entity', array(
'class' => 'MyBundle:Product',
'property' => 'details.detail_name',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('p')
->select('p, pd')
->join('p.details', 'pd')
->where('pd.language_id = :lang')
->setParameter('lang', 'en');
}));
I want the property to be the details.detail_name.
I tried different values for this property value. Like 'details.detail_name', 'pd.detail_name' and 'p.details.detail_name'.
But it seems to be impossible to get the property to display the detail name.
When I use to above mentioned code, I get this error:
Neither property "detail_name" nor method "getDetailName()" nor method "isDetailName()" exists in class "Doctrine\ORM\PersistentCollection"
This getDetailName() method does exist in the ProductDetails entity, and I have checked the entities and they all seem to be okay. Also, they work just fine when I use these entities outside the form.
I also tried to execute the resulting query directly on my database, and it gives me the expected results. The detail_name are in the right language.
So, can somebody help me on how to make the select choice list I want, with a joined query?
I finally managed to get this working. Below, I'm showing how I'm doing this, in case someone else has the same problem.
I am now using a custom form type.
And in the setDefaultOptions, I am calling a repository method, which returns an array with "product_id" => "detail_name".
class ProductChoiceType extends AbstractType
{
private $repository;
public function __construct(EntityRepository $repository)
{
$this->repository = $repository;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'choices' => $this->repository->findAllProductsForForm('nl', true)
));
}
public function getParent()
{
return 'choice';
}
public function getName()
{
return 'product_choice';
}
}
In the $this->repository->findAllProductsForForm method, I am using a query and a foreach loop to make the array suitable for the choice list.
Then, I had to register the repository and this type in my services.xml file:
<service id="mybundle.repository.product"
factory-service="doctrine.orm.default_entity_manager"
factory-method="getRepository"
class="MyBundle\Repository\ProductRepository" >
<argument>MyBundle:Product</argument> <!-- not sure why I need this, but it wouldn't work without it -->
</service>
<service id="mybundle.xxx.form.product_choice_type" class="Mybundle\Form\Type\ProductChoiceType">
<argument type="service" id="mybundle.repository.product" />
<tag name="form.type" alias="product_choice" />
</service>
And then, in the root form type (I think it's called that) I use the 'product_choice' as a form type.
I'm not sure if this the best way to do this, but at least it works.
Now I only need to figure out how to pass on the current language of the user on to the repository, but that's a problem for later.
From what I see your Method Product::getDetails returns Doctrine\ORM\PersistentCollection not `ProductDetails' entity (so collection not single object). It mean that product is related with details using one-to-many/many-to-many association.
You can try to do it from product details side then:
$builder->add(
'product',
'entity',
array(
'class' => 'MyBundle:ProductDetails',
'property' => 'detail_name',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('pd')
->select('pd, p')
->join('pd.product', 'p')
->where('pd.language_id = :lang')
->setParameter('lang', 'en');
}
)
);
Related
I have an entity Nace that holds hierarchical data. It is mapped as a many-to one association to an entity Organisation.
class Organisation
{
/**
* #ORM\ManyToOne(targetEntity="Pftp\UserBundle\Entity\Nace")
* #ORM\JoinColumn(name="nace_id", referencedColumnName="id")
*/
private $nace;
}
The following form renders the nace property as a drop-down with all records in Nace as expected (showing my starting point).
class OrganisationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('nace')
;
}
}
As Nace contains several hundreds of records, I want to implement a custom FieldType
which adds a second entity choice field with higher level options from the same table (categories)
which populates the main choice field via ajax when the user selects a category
When I add the naceCategory child field and try to render the form, I get the an error related to data mapping.
In order to figure the data flow, I added a ModelTransformer but the methods transform and reverseTransform are never called. Here is my code:
Template
{% block nace_widget %}
{{ form_widget(form) }}
{{ form_widget(form.naceCategory) }}
{% endblock %}
NaceType
class NaceType extends AbstractType
{
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'compound' => true,
'mapped' => true,
'required' => true,
'class' => 'Pftp\UserBundle\Entity\Nace',
'query_builder' => function(NaceRepository $er) {
return $er->createQueryBuilder('node')->where('1=0');
},
));
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add($builder->create('naceCategory', 'entity', array(
'mapped' => false,
'required' => false,
'class' => 'Pftp\UserBundle\Entity\Nace',
'query_builder' => function(NaceRepository $er) {
return $er->getCategoriesQueryBuilder();
}
)))
->addModelTransformer(new NaceTransformer())
;
}
public function getParent()
{
return 'entity';
}
public function getName()
{
return 'nace';
}
}
Error
Expected argument of type "object, array or empty", "string" given
at PropertyPathMapper ->mapDataToForms ('', object(RecursiveIteratorIterator))
in W:\Symfony2\Sources\pftpprod\vendor\symfony\symfony\src\Symfony\Component\Form\Form.php at line 385 +
at Form ->setData (null)
in W:\Symfony2\Sources\pftpprod\vendor\symfony\symfony\src\Symfony\Component\Form\Extension\Core\DataMapper\PropertyPathMapper.php at line 57 +
at PropertyPathMapper ->mapDataToF...
Analogy
Let me try and give you a better idea of what I'm trying to achieve as this Nace thing is pretty unknown.
Basically, I need 2 cascading drop-downs. Imagine you have a list of all the cities of the world in one table and you want the user to choose only 1. As the list is too long, you would want the user to first choose a country from another drop-down (not mapped with your target entity), which should populate your city drop-down with a limited list of cities.
Well, in my case nace corresponds to the city drop-down (initially empty), and naceCategory corresponds to the country drop-down (always populated with the repo method getCategoriesQueryBuilder()). The only difference to the city/country exmaple is, that both drop-downs are hydrated from the same table/entity (Nace), just with values from different hierachy levels.
Does anybody know what I'm missing here?
people..
My problem is:
I have a form, with a Collection, so, I setted a CollectionInputFilter to this Collection..
If i send two block of fields (collections) to validate, and one field of one of this block is validated, the same field in the another one block is automatically validated too, even if is wrong or not filled..
I don't know what to do.. I tried a lot of tips.. but, none worked..
Someone can help me? Or faced the same problem?
Here my code:
class ClienteEnderecoFilter extends CollectionInputFilter
{
public function __construct()
{
$this->add(array(
'name' => 'fieldOne',
'required' => true,
));
}
}
If i send two fieldOne and one is filled, the another one is validated too!
I think you need to set the input filter for each collection item on to the CollectionInputFilter instead of extending it. Like this:
use Zend\InputFilter\InputFilter;
class ClienteEnderecoFilter extends InputFilter
{
public function __construct()
{
$this->add(array(
'name' => 'fieldOne',
'required' => true,
));
}
}
$collectionInputFilter = new CollectionInputFilter();
$collectionInputFilter->setInputFilter(new ClienteEnderecoFilter());
I have a form using this class type:
class DespesasContainerType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('despesas', 'collection', array(
'type' => new DespesasFamiliasType(),
'by_reference' => false,
))
;
}
// ...
}
This way it shows all items in the despesas attribute of the object.
Is there a way to filter which items to use? Something similar to the query_builder option on the entity field type.
No way from FormTypeInterface, but you can filter this collection before passing it to the Form.
Another tricky tip :
Define a public getter like getFilteredDespeas on your Entity that returns the filtered list of despeas. In your Form, just call the field filteredDespeas instead of despeas. This involves that you specifically manage the form binding, by adding a public setFilteredDespeas to your entity, or any other way...
So, I'm building a small form with a choice. However, I want to pre-select some of those options, as if I was applying selected="selected". I could not find how can this be done in the docs. Help? :D
To set default values for a form those values need to be set in the underlying data class for the form. Assuming the underlying data class is an Entity, the values can be defaulted in that Entity on construction. If you are not using entity annotations and don't want to alter your generated entity classes you can set default values into a new instance of the entity class and use it as the data for your form.
For example, for a User entity which has an array of roles and a method setRoles(array $roles) the roles can be defaulted in the constructor of the User entity like this (hardcoded strings used for clarity):
public function __construct()
{
$this->setRoles(array('ROLE_USER', 'ROLE_READER', 'ROLE_EDITOR');
}
Alternatively, the roles can be defaulted in the controller just before the form is displayed like this (simplistic example with no form class and hardcoded strings):
$allRoles = array('ROLE_USER', 'ROLE_READER', 'ROLE_EDITOR', 'ROLE_ADMIN', 'ROLE_SUPER_ADMIN');
$user = new User();
$user->setRoles(array('ROLE_USER', 'ROLE_READER', 'ROLE_EDITOR');
$form = $this->createFormBuilder($user)
->add('username', 'text')
->add('roles', 'choice', array('choices' => array_combine($allRoles, $allRoles),
'multiple' => true)
->getForm();
return $this->render('AcmeTaskBundle:Default:new.html.twig', array(
'form' => $form->createView(),
));
Ended up being simpler than i thought:
$form['form[selectionMenu]']->select(1);
How can i create a select list with values from a database table in Symfony 2?
I have 2 entities: Student and Classroom with a ManyToOne relationship and i need to create a form with the folowing fields: name, surname, age, classroom(select list from available classes).
In my Student Form i have
$builder
->add('name')
->add('surname')
->add('age')
->add('classroom', new ClassroomType())
;
In my Classroom Form i have this:
$classrooms =$this->getDoctrine()->getRepository('UdoCatalogBundle:Classroom')->findAll();
$builder
->add('clasa','choice',array('choices' => array($classrooms->getId() => $classrooms->getName())));
I get this following error:
Fatal error: Call to undefined method Udo\CatalogBundle\Form\ClassroomType::getDoctrine() in /var/www/html/pos/src/Udo/CatalogBundle/Form/ClassroomType.php on line 13
Kind Regards,
Cearnau Dan
Not sure if you found an answer yet but I just had to do some digging around to figure this out for my own project.
The form class isn't set up to use Doctrine like the controller is so you can't reference the Entity the same way. What you want to do is use the entity Field Type which is a special choice Field Type allowing you to load options from a Doctrine entity as you are trying to do.
Ok so long story short. Instead of doing what you are doing to create the choice field, do this:
->add('category', 'entity', array(
'class' => 'VendorWhateverBundle:Category',
'query_builder' => function($repository) { return $repository->createQueryBuilder('p')->orderBy('p.id', 'ASC'); },
'property' => 'name',
))
I'm not sure if you could place the query_builder function into a repository or what, I'm kind of swinging wildly as I go. Up to this point the documentation I linked to above is pretty clear on what to do. I guess the next step is to read up on Doctrine's QueryBuilder.
While you're in there I think you want to drop the bit where you are embedding the Classroom form,
->add('classroom', new ClassroomType())
You probably don't want people creating their own classrooms. Unless you do, then yeah.
If the entities are mapped, this is a clean solution for Symfony 2.8+ or 3+
<?php
namespace My\AppBundle\Form\Type;
use My\AppBundle\Entity\Student;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class StudentType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('surname')
->add('age')
/*
* It will be resolved to EntityType, which acts depending on your doctrine configuration
*/
->add('classroom');
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(['data_class' => Student::class]);
}
}