Symfony form collection - php

I'm trying to add a formType in a other form type.
CustomerType:
class CustomerType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('firstname', 'text', array(
'required' => 'required'
))
->add('middlename')
->add('lastname')
->add('email', 'email')
->add('groups', 'entity', array(
'class' => 'MV\CMSBundle\Entity\Group',
'property' => 'name',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('g')
->orderBy('g.name', 'ASC');
}
))
->add('profile', 'collection', array(
'type' => new ProfileType()
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'MV\CMSBundle\Entity\User',
));
}
public function getName()
{
return 'customer';
}
}
ProfileType:
class ProfileType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('isActive', 'checkbox', array(
'required' => false
))
->add('phone')
->add('address')
->add('city')
->add('zipcode')
->add('country')
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'MV\NameBundle\Entity\Profile',
));
}
public function getName()
{
return 'profile';
}
}
Then I get this message:
Expected argument of type "array or (\Traversable and \ArrayAccess)", "MV\NameBundle\Entity\Profile" given.
If I comment that line i get: (just to check what will goes wrong ;) )
The form's view data is expected to be of type scalar, array or an instance of \ArrayAccess, but is an instance of class MV\NameBundle\Entity\Profile. You can avoid this error by setting the "data_class" option to "MV\NameBundle\Entity\Profile" or by adding a view transformer that transforms an instance of class MV\NameBundle\Entity\Profile to scalar, array or an instance of \ArrayAccess.
What do I do wrong?
Symfony2: 2.1.8-DEV

It's not clear what your mapping is between your Customer entity and your Profile entity, but I guess there are 2 options:
Option 1: You have a OneToOne relationship (One customer can only have One profile). Therefore, you don't need the collection at all but simply need to embed a single object.
->add('profile', new ProfileType());
Option 2: You want a ManyToOne relationship (One customer can have many profiles). In that case, you need to use to embed a collection of forms. If this is what you want, rename $profile to $profiles in your Customer entity, and do the following:
//MV\CMSBundle\Entity\Customer
use Doctrine\Common\Collections\ArrayCollection;
/**
* Your mapping here.
*
* #ORM\ManyToOne(targetEntity="MV\NameBundle\Entity\Profile")
*/
protected $profiles;
public function __construct()
{
//This is why you had an error, this is missing from your entity
//An object was sent instead of a collection.
$this->profiles = new ArrayCollection();
}
Finally, update your database.

Related

use the EntityType with text input

I have created an EntityType form where you can add to your participation to an event the field and your position. Unfortunately the number and name of the fields is updated dynamically on the page so when creating the form, I don't have all the fields available (or worse some that doesn't exist anymore)
For simplicity I would like to use a simple textType, where my javascripts could enter an Id number and link the corresponding element from the db.
the code is very simple
class FieldPositionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('position', EntityType::class, [
'class' => Position::class,
'choice_label' => 'name'
])
->add('field', EntityType::class, [
'class' => Field::class,
'choice_label' => 'id'
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Participation::class,
]);
}
}
but instead of EntityType, I would prefer TextType.
I imagine that I need to perform some modification on my setter but I have no clue how to transform an Id into a Entity as the EntityType does.
As suggested by Cerad, the dataTransformer was the solution.
So just for the position, I created a dataTransformer
class PositionToIdTransformer implements DataTransformerInterface
{
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
public function transform($position)
{
if (null === $position) {
return '';
}
return $position->getId();
}
public function reverseTransform($id)
{
if (!$id){
return;
}
$position = $this->em->getRepository(Position::class)->find($id);
if (null === $position){
throw new TransformationFailedException(sprintf("the position '%s' does not exist!", $id));
}
return $position;
}
}
that I use in my formBuilder:
class FieldPositionType extends AbstractType
{
private $pt; //PositionToIdTransformer
public function __construct(PositionToIdTransformer $pt)
{
$this->pt = $pt;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('position', TextType::class)
;
$builder->get('position')->addModelTransformer($this->pt);
}
}
and it work like a charm!
Link for query builder: https://symfony.com/doc/current/reference/forms/types/entity.html#ref-form-entity-query-builder
As told in comment try using it. I can not halp you further because I do not see rest of your logic but to limit entries which are shown in entity field it looks like this:
$builder->add('users', EntityType::class, [
'class' => User::class,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('u')
->orderBy('u.username', 'ASC');
},
'choice_label' => 'username',
]);

Passing data to buildForm() in Symfony 2.8, 3.0 and above

My application currently passes data to my form type using the constructor, as recommended in this answer. However the Symfony 2.8 upgrade guide advises that passing a type instance to the createForm function is deprecated:
Passing type instances to Form::add(), FormBuilder::add() and the
FormFactory::create*() methods is deprecated and will not be supported
anymore in Symfony 3.0. Pass the fully-qualified class name of the
type instead.
Before:
$form = $this->createForm(new MyType());
After:
$form = $this->createForm(MyType::class);
Seeing as I can't pass data through with the fully-qualified class name, is there an alternative?
This broke some of our forms as well. I fixed it by passing the custom data through the options resolver.
In your form type:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->traitChoices = $options['trait_choices'];
$builder
...
->add('figure_type', ChoiceType::class, [
'choices' => $this->traitChoices,
])
...
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'trait_choices' => null,
]);
}
Then when you create the form in your controller, pass it in as an option instead of in the constructor:
$form = $this->createForm(ProfileEditType::class, $profile, [
'trait_choices' => $traitChoices,
]);
Here's how to pass the data to an embedded form for anyone using Symfony 3. First do exactly what #sekl outlined above and then do the following:
In your primary FormType
Pass the var to the embedded form using 'entry_options'
->add('your_embedded_field', CollectionType::class, array(
'entry_type' => YourEntityType::class,
'entry_options' => array(
'var' => $this->var
)))
In your Embedded FormType
Add the option to the optionsResolver
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Yourbundle\Entity\YourEntity',
'var' => null
));
}
Access the variable in your buildForm function. Remember to set this variable before the builder function. In my case I needed to filter options based on a specific ID.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->var = $options['var'];
$builder
->add('your_field', EntityType::class, array(
'class' => 'YourBundle:YourClass',
'query_builder' => function ($er) {
return $er->createQueryBuilder('u')
->join('u.entity', 'up')
->where('up.id = :var')
->setParameter("var", $this->var);
}))
;
}
Here can be used another approach - inject service for retrieve data.
Describe your form as service (cookbook)
Add protected field and constructor to form class
Use injected object for get any data you need
Example:
services:
app.any.manager:
class: AppBundle\Service\AnyManager
form.my.type:
class: AppBundle\Form\MyType
arguments: ["#app.any.manager"]
tags: [ name: form.type ]
<?php
namespace AppBundle\Form;
use AppBundle\Service\AnyManager;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class MyType extends AbstractType {
/**
* #var AnyManager
*/
protected $manager;
/**
* MyType constructor.
* #param AnyManager $manager
*/
public function __construct(AnyManager $manager) {
$this->manager = $manager;
}
public function buildForm(FormBuilderInterface $builder, array $options) {
$choices = $this->manager->getSomeData();
$builder
->add('type', ChoiceType::class, [
'choices' => $choices
])
;
}
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults([
'data_class' => 'AppBundle\Entity\MyData'
]);
}
}
In case anyone is using a 'createNamedBuilder' or 'createNamed' functions from form.factory service here's the snippet on how to set and save the data using it. You cannot use the 'data' field (leave that null) and you have to set the passed data/entities as $options value.
I also incorporated #sarahg instructions about using setAllowedTypes() and setRequired() options and it seems to work fine but you first need to define field with setDefined()
Also inside the form if you need the data to be set remember to add it to 'data' field.
In Controller I am using getBlockPrefix as getName was deprecated in 2.8/3.0
Controller:
/*
* #var $builder Symfony\Component\Form\FormBuilderInterface
*/
$formTicket = $this->get('form.factory')->createNamed($tasksPerformedForm->getBlockPrefix(), TaskAddToTicket::class, null, array('ticket'=>$ticket) );
Form:
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefined('ticket');
$resolver->setRequired('ticket');
$resolver->addAllowedTypes('ticket', Ticket::class);
$resolver->setDefaults(array(
'translation_domain'=>'AcmeForm',
'validation_groups'=>array('validation_group_001'),
'tasks' => null,
'ticket' => null,
));
}
public function buildForm(FormBuilderInterface $builder, array $options) {
$this->setTicket($options['ticket']);
//This is required to set data inside the form!
$options['data']['ticket']=$options['ticket'];
$builder
->add('ticket', HiddenType::class, array(
'data_class'=>'acme\TicketBundle\Entity\Ticket',
)
)
...
}

symfony fosrestbundle - How to post a record with associated entities (many-to-many)

I am trying to build a RESTful API with Symfony2, FosRestBundle, and JMSSerializer and everything works perfect until I try to persist an entity with any kind of association. This works out-of-the-box with the standard symfony edition, it's enough to set the form field as "entity" type and the framework updated the cross table. However when I try to do the same on a REST setup, I get this error "Notice: Array to string conversion" related to the "categories" form field and a 500 error.
How I am supposed to persist related entities with an API?
Entity Serie.php
/**
* #ORM\ManyToMany(targetEntity="Category", inversedBy="series")
* #ORM\JoinTable(name="categories_series")
*/
private $categories;
a form SerieType.php
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('categories', 'entity', array(
'multiple' => true,
'expanded' => false,
'property' => 'name',
'class' => 'My\Bundle\CoreBundle\Entity\Category'
))
->add('name')
->add('description')
->add('status', 'checkbox', array('required' => false))
->add('save', 'submit')
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'My\Bundle\CoreBundle\Entity\Serie',
'csrf_protection' => false
));
}
public function getName()
{
return 'serie';
}
and a SerieController.php
/**
* #POST("", name="serie_new")
*/
public function newAction(Request $request){
$serie = new Serie();
$form = $this->createForm(new SerieType(), $serie);
$form->submit($request->request->get($form->getName()));
if($form->isValid()){
$em = $this->getDoctrine()->getManager();
$em->persist($serie);
$em->flush();
$view = $this->routeRedirectView('serie_index');
return $this->handleView($view);
}
and a json POST request:
{
"serie": {
"categories":[
{"id":1,"name":"Category 1"},
{"id":2,"name":"Category 2"}
],
"name":"asasdasd",
"description":"sadsads",
"status":true
}
You error is probably generated when trying to display the entity. Try adding __toString() method to your Category Entity.
And you should use a DataTransformer in order to be able to identify the category when trying to submit a POST request.

symfony 2 how to pass array collection to input select

Hi i am tying pass array collection (method getProjects() returns it) to form (select input) and fail. This code returns exception - A "__toString()" method was not found on the objects of type "Tasker\WebBundle\Entity\Project" passed to the choice field.
Can anybody help? Is needed transformer? Or what is right way?
Controller:
/**
* #Route("/pridaj", name="web.task.add")
* #Template()
*/
public function addAction(Request $request)
{
$task = new Task;
/** #var User $loggedUser */
$loggedUser = $this->get('security.token_storage')->getToken()->getUser();
$form = $this->createForm(new AddTaskType(), $task, ['user' => $loggedUser]);
if ($form->handleRequest($request) && $form->isValid()) {
// some stuff
}
return [
'form' => $form->createView()
];
}
Form:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('project', 'entity', [
'label' => 'Projekt:',
'class' => 'TaskerWebBundle:Project',
'choices' => $options['user']->getProjects(),
'placeholder' => 'Označte projekt',
])
// ....
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setRequired(array(
'user',
));
$resolver->setDefaults(array(
'user' => null,
));
}
just add __ToString() to your Project class
Tasker\WebBundle\Entity\Project
class Project
{
....
function __toString() {
return $this->getName(); //or whatever string you have
}
}
I wanted to add another answer, because you do not have to add __toString() to your Project class. The Symfony entity field type allows you to specify which property/field to use for displaying. So instead of __toString() you could specify the property in the form configuration like so:
$builder
->add('project', 'entity', [
'label' => 'Projekt:',
'class' => 'TaskerWebBundle:Project',
'choices' => $options['user']->getProjects(),
'placeholder' => 'Označte projekt',
'property' => 'name'
])
If you check this part of the Symfony documentation you will see that __toString() is automatically called only if you do not specify the property.

Symfony form many-to-many without collection

I have 2 entities, User and Account. An Account can be own by multiple users and a User can own multiple Account. So I have this entity UserHasAccount that is handling this relation, plus some few fields that have personalised values for each User - Account relation, such as position, showInMenu, showInSideBar.
In the settings of the account, when a user is logged in I want to display those options but only for the current logged user.
So I end up with 2 form type:
AccountEditType:
class AccountEditType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', null, array(
'label' => 'account.form.name',
'translation_domain' => 'AcmeAccountBundle',
))
->add('timezone', 'genemu_jqueryselect2_timezone', array(
'label' => 'account.form.timezone',
'translation_domain' => 'AcmeAccountBundle',
))
->add('accountUser', new UserAccountEditType($options['user']))
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\AccountBundle\Entity\Account',
'user' => null,
'validation_groups' => array('AcmeUpdateAccount'),
'cascade_validation' => true,
));
}
public function getName()
{
return 'acme_account_edit_settings';
}
}
UserAccountEditType:
class UserAccountEditType extends AbstractType
{
protected $user;
public function __construct($user)
{
$this->user = $user;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('showInMenu', null, array(
'label' => 'account.user_account.form.show_in_menu',
'translation_domain' => 'AcmeUserBundle',
'required' => false,
))
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\UserBundle\Entity\UserHasAccount',
));
}
public function getName()
{
return 'acme_general_account_user';
}
}
The problem is that Account.accountUser is a collection of user and so I cannot use UserAccountEditType which is mapping only one relation (not the collection). If I leave it like this I have the following error:
The form's view data is expected to be an instance of class Acme\UserBundle\Entity\UserHasAccount, but is an instance of class Doctrine\ORM\PersistentCollection
How can I possibly select one of the relation in the form type?
Cheers,
Maxime

Categories