Symfony2 + Doctrine2 / building a form from 2 joined entity objects - symfony

Is it possible to build a form from 2 joined entity objects?
I have two entities property & propertylanguage which are joined on onetomany relation.
(One property can have many languages)
Language has a title and description colomns.
So one property can have a english, french, german title.
I am trying to build a form out of that.
See below.
Controller: addProperty.php
class AddPropertyController extends Controller
{
// ....
public function indexAction(Request $request)
{
$property = new property;
$language = new propertyLanguage;
$property ->addpropertylanguage($language);
$form = $this->createForm(new propertyType($this->getDoctrine()),$property);
// .....
}
Form type: propertType.php
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('title', 'text');
// other ->add() below.
}
It returns the following error:
Neither property "title" nor method "getTitle()" nor method
"isTitle()" exists in class "\defaultBundle\Entity\property"
Of course there is no property Title in property, but there is one in propertylanguage..
Even if I try:
->add('title', 'entity', array('class'=>defaultBundle:propertylanguage));
it doesn't work.
Thanks if you have time to help me.
Best,
Pierre.

What you will want to do is make a PropertyLanguageType class as well as a PropertyType.
Then, in your PropertyType you will embed the PropertyLanguageType:
public function buildForm(FormBuilder $builder, array $options)
{
// $builder->add('propertyLanguage', new PropertyLanguageType());
// Since we have a 1 to many relation, then a collection is needed
$builder->add('propertyLanguage', 'collection', array('type' => new PropertyLanguageType()));
The PropertyLanguageType is where you add the title.
It's all in the forms section in the manual but it might take several readings.
A second approach is to add a getTitle to your Property entity which would return the title from the PropertyLanguage entity. By doing this, your original form will work. But it can be a bit of a mess when you start to have multiple associations with multiple attributes. Best to just define a type for each entity.

You could use a query_builder when defining the form. Here's how your form class might look like. Of course it will certainly not be exactly as so but that will give you a good start ;)
public function __construct($id)
{
$this->propertylanguageId = $id;
}
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('propertylanguage', 'entity', array(
'label' => 'Property Language',
'class' => 'YourAdressToBundle:Propertylanguage',
'query_builder' => function(EntityRepository $er) use ($propertylanguageId) {
return $er->createQueryBuilder('p')
->join('p.property', 'prop', Expr\Join::WITH, 'prop.id = :propertylanguageId')
->setParameter('propertylanguageId', $propertylanguageId);
},
));
}
Hope that'll be helpful

Related

How to show on the select only specific records when it's generated by buildForm for ManyToOne association

I have Entity like this
class User implements UserInterface
{
// ...
/**
* Many Departments have One Central Department (User)
* #ORM\ManyToOne(targetEntity="User")
*/
private $centralDepartment;
// ...
}
with self-referencing association. In related buildForm I use
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// ...
->add('centralDepartment');
// ...
}
}
and it creates in my view select list with list of Users. It's correct.
But the goal is to show on the list only Users with specific Role. If there is possibility I want also to validate before saving in database if selected User has specific Role.
Should I use option choice_loader from https://symfony.com/doc/3.4/reference/forms/types/choice.html or is there some better option in Symfony? I tried to change at first the label using
->add('centralDepartment', ChoiceType::class, array('label' => 'Choose Central Department'));
But my select list is empty now.
First try using EntityType instead of ChoiceType which is a more specialized ChoiceType for entity relations.
With EntityType you have a query_builder option to select the wanted choices.
See: https://symfony.com/doc/current/reference/forms/types/entity.html#query-builder
This could also be achieved through the choice_loader option with a ChoiceType but requires more work.
Going by #Joe suggestion I used EntityType
->add('centralDepartment', EntityType::class, array(
'class' => 'AppBundle:User',
'query_builder' => function(UserRepository $er) {
return $er->findAllBC(true);
}
));
where findAllBC(true) is my method in UserRepository to return Query Builder object which included specific Users for my use:
public function findAllBC($returnQB = false)
{
$qb = $this->createQueryBuilder('u')
->andWhere('u.roles LIKE :role')
->setParameter('role', '%BC%');
if(!$returnQB) return $qb->getQuery()->execute();
else return $qb;
}

Symfony Form OneToMany Entity ManyToOne Relationship

Three entities exists; Topic, Meta and User.
Topic OneToMany Meta ManyToOne User
How can this be created using Symfony Forms?
Originally this was a ManyToMany relationship between Topic and User but this has been altered to allow additional information about the relationship to be stored in Meta.
Original form:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title')
->add('users')
;
}
Modified form:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title')
->add('users', EntityType::class, [
'multiple' => true,
'class' => 'FooBarBundle:User',
'choice_label' => 'username',
])
;
}
This renders a form with Title and a multi select for User. The problem is User doesn't map to Meta and so the User data won't be persisted. Is mapped=false and using the controller to attach the Meta entity to Thread the correct way to handle this relationship? Or is there a way to use a DataTransformer to convert the Users to Metas?
--- UPDATE ---
public function setUsers($users) {
foreach($users AS $user) {
$meta = new Meta();
$meta
->setUser($user)
->setThread($this);
$this->addMeta($meta);
}
return $this;
}
Or is using the call from within Form::handleRequest to Thread::setUsers the right place to convert a User to a Meta?

symfony filter collection field type like entity field type

I have a form with a collection field type.
I'd like to filter it as we can do for entity field types but I'm not finding the solution.
i found other similar questions but no satisfiable answer so far. Can we do something like :
$builder
->add('userIngredients', 'collection', array(
'type' => new UserImportedIngredientType($this->userIngredients),
'query_builder'=>$this->queryBuilder,
))
;
If not, can we use form listener event to exclude some elements based on the object property ? How ?
This collection represents userIngredients that I want the user to be able to change if they have their property isImported set to true, hence the search for the query_builder solution.
Well, I figured I could do something as simple as build a regular form not attached to a parent entity.
In case this might help someone :
class UserImportedIngredientType extends AbstractType
{
protected $userIngredients;
protected $userImportedIngredients;
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
foreach ($this->userImportedIngredients as $userImportedIngredient)
{
/**
* #var $userImportedIngredient UserIngredient
*/
$builder
->add($userImportedIngredient->getId(), 'genemu_jqueryselect2_entity', array(
'query_builder'=>$this->userIngredients,
'class' => 'AppBundle:FoodAnalytics\UserIngredient',
'multiple' => false,
'label' => $userImportedIngredient->getName(),
'required'=>false,
'mapped' => false,
'data' => $userImportedIngredient
))
;
}
}
/**
* #return string
*/
public function getName()
{
return 'appbundle_foodanalytics_user_imported_ingredient';
}
public function __construct($userIngredients, $userImportedIngredients)
{
$this->userIngredients=$userIngredients;
$this->userImportedIngredients=$userImportedIngredients;
}
}
I solved it like this in my case with additionally Sonata on top of Symfony. What I did was feeding the 'data' parameter the specifically doctrine queried array of entities result. (In Repository: return $queryBuilder->getQuery()->getResult();)
/** #var MyEntityRepository $myEntityRepository */
$myEntityRepository = $this->getEntityManager()->getRepository(MyEntity::class);
/** #var MyEntity[] $myEntities */
$myEntities = $myEntityRepository->findBySomeCriteriaFilter(
$parameter, Constants::specificConstant
);
$formMapper->add(
// html name=
'myEntityProperty',
\Symfony\Component\Form\Extension\Core\Type\CollectionType::class,
[
// specific form for MyEntity
'entry_type' => new Form\MyEntity\MyEntityType(),
'allow_add' => true,
'label' => false,
'entry_options' => [
'label' => false
],
// the filtered array of entities from doctrine repository query above
'data' => $myEntities,
]
);
Use this instead of sonata_type_model_list, I guess.
Also if you want to filter sonata_type_model, use EntityType instead and use the 'query_builder' option with a Closure and returning the queryBuilder instead of the array of entities. This is all very inconsistent, best don't use symfony and sonata at all.
As far as I know Collection does not have the query_builder option
So you cannot go this way.
Is hard to decipher what you are trying to do with 4 lines of formType.
Your code looks alright, except the unsuppported query_builder, and you are already passing the userIngredients to the constructor.
My though is that if you need to filter this, then you shouldn't do it in the collection Type, but in other place.
EDITED:
On a second though, you are trying to filter the collection in the wrong place. Is not on the base collection , but in :
class UserImportedIngredientType extends AbstractType {
function __construct( $userIngredients ) {
// Do your stuff
}
function buildForm( FormBuilderInterface $builder, array $options) {
// Add here your entity with the query_filter option :)
}
}
Check the other entries in SO over Collections, there are many of them

Symfony Forms - dynamic number of items

I'm trying to build a product page that will contain a form with a dynamic number of "options" (either select boxes or input fields) depending on the product. After reading the documentation, I can't see how to create a form entity that would work when building this form. I feel like I'm missing something obvious.
What you need to do is basically create form field of collection type, which will be your collection of select boxes, input fields, whatever.
Check documentation and read about embeding forms, it is described pretty well ther
Your parent form:
class ParentType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('id', 'hidden')
->add('name')
->add('yourCollection', 'collection', array(
'type' => new ChildType(),
'label' => 'Label for your child form',
));
}
}
Your child form:
class ChildType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('id', 'hidden')
->add('category', 'choice')
;
}
}

Symfony2 Forms - How to use parametrized constructors in form builders

I am learning to use Symfony2 and in the documentation I have read, all entities being used with Symfony forms have empty constructors, or none at all. (examples)
http://symfony.com/doc/current/book/index.html Chapter 12
http://symfony.com/doc/current/cookbook/doctrine/registration_form.html
I have parametrized constructors in order to require certain information at time of creation. It seems that Symfony's approach is to leave that enforcement to the validation process, essentially relying on metadata assertions and database constraints to ensure that the object is properly initialized, forgoing constructor constraints to ensure state.
Consider:
Class Employee {
private $id;
private $first;
private $last;
public function __construct($first, $last)
{ .... }
}
...
class DefaultController extends Controller
{
public function newAction(Request $request)
{
$employee = new Employee(); // Obviously not going to work, KABOOM!
$form = $this->createFormBuilder($employee)
->add('last', 'text')
->add('first', 'text')
->add('save', 'submit')
->getForm();
return $this->render('AcmeTaskBundle:Default:new.html.twig', array(
'form' => $form->createView(),
));
}
}
Should I not be using constructor arguments to do this?
Thanks
EDIT : Answered Below
Found a solution:
Looking into the API for the Controllers "createForm()" method I found something that is not obvious from the examples. It seems that the second argument is not necessarily an object:
**Parameters**
string|FormTypeInterface $type The built type of the form
mixed $data The initial data for the form
array $options Options for the form
So rather than pass in an instance of the Entity, you can simply pass in an Array with the appropriate field values:
$data = array(
'first' => 'John',
'last' => 'Doe',
);
$form = $this->createFormBuilder($data)
->add('first','text')
->add('last', 'text')
->getForm();
Another option (which may be better), is to create an empty data set as a default option in your Form Class.
Explanations here and here
class EmployeeType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('first');
$builder->add('last');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'empty_data' => new Employee('John', 'Doe'),
));
}
//......
}
class EmployeeFormController extends Controller
{
public function newAction(Request $request)
{
$form = $this->createForm(new EmployeeType());
}
//.........
}
Hope this saves others the head scratching.

Resources