I am building a form that is rendered from two different Type classes in Symfony2 (using the collection type for the second Type) and I am having trouble accessing data from the collection field in the controller. Here is the code for the outer formBuilders method:
// ...
class EmployeeCreateType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// ...
->add('positions', 'collection', array(
'type' => new PositionCreateType(),
'label' => ' ',
'allow_add' => false,
'prototype' => false,
));
}
// ...
and here is the code for the inner buildForm method from PositionCreateType:
// ...
class PositionCreateType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', 'choice', array(
'label' => 'Title: ',
'choices' => array(
'Senior Engineer',
'Staff',
'Engineer',
'Senior Staff',
'Assistant Engineer',
'Technique Leader',
),
'expanded' => true,
'multiple' => false,
))
->add('department', 'choice', array(
'label' => 'Department: ',
'choices' => array(
'd001' => 'Marketing',
'd002' => 'Finance',
'd003' => 'Human Resources',
'd004' => 'Production',
'd005' => 'Development',
'd006' => 'Quality Management',
'd007' => 'Sales',
'd008' => 'Research',
'd009' => 'Customer Service',
),
'expanded' => true,
'multiple' => false,
));
}
// ...
I would like to access the department field from my controller, but I can't figure out how to do it. I've tried doing something like
$form->get('positions')->get('department')->getData();
but it is not working.
I figured out the solution. Because a collection is an ArrayCollection, you have to access the element of the collection that corresponds to the object you want to access by providing the correct index. Because there was only one item in this collection (a separate form type), the following statement did the trick:
$form->get('positions')->getData()->get('0')->getDepartment();
In other words,
$form->get('positions')->getData()->get('0')
returns the entity (Position) corresponding to my separate form type, PositionCreateType().
Related
i want to store some data
class Offer
{
/**
* #ORM\Column(type="array")
*/
private $meterPoints;
public function __construct()
{
$this->meterPoints = new ArrayCollection();
}
}
for an offer as a CollectionType.
class OfferType extends AbstractType
{
$builder
->add('meterPoints', CollectionType::class, array(
'entry_type' => OfferMeterPointType::class,
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'prototype_name' => '__mp_name__',
))
}
In the OfferMeterPointType I also have an EntityType
class OfferMeterPointType extends AbstractType
{
$builder
->add('meterPoint', EntityType::class, array(
'attr' => array(
'class' => 'chosen-select'
),
'class' => 'AppBundle:MeterPoint',
'choice_label' => 'meterPoint',
))
->add('billData', CollectionType::class, array(
'label' => false,
'entry_type' => OfferBillType::class,
'allow_add' => true,
'allow_delete' => true,
'entry_options' => array(
'label' => false
)
));
}
Now when I persist that entity the whole AppBundle:MeterPoint object get serialized and not just the id. I kind of understand why doctrine does that but can I change it such that just the id will be stored?
Also when I want to edit an Offer
$offer = $em->getRepository('AppBundle:Offer')->findOneById(2);
$form = $this->createForm(OfferType::class, $offer);
i get an Exception
Entities passed to the choice field must be managed. Maybe persist them in the entity manager?
I guess a solution would be to create an Entity for the OfferMeterPointType but I don't really want to do that. Because I allmost never need that data.
Update
I tried like martin suggested. now the exception is gone but it still saves the complete object
$meterPoints = $this->em->getRepository('AppBundle:MeterPoint')->findAll();
//dump($meterPoints);
$builder
->add('meterPoint', ChoiceType::class, array(
'label' => 'offerMeterPoint.meterPoint',
'attr' => array(
'class' => 'chosen-selesct',
'placeholder' => 'ob.to'
),
'choices' => $meterPoints,
'choice_label' => function($meterPoint) {
return $meterPoint->getMeterPoint();
},
'choice_value' => function($meterPoint) {
if($meterPoint === null){
return null;
}
dump($meterPoint);
return $meterPoint->getId();
},
'placeholder' => 'global.plz_select'
))
Update 2
Got it working
changed the ChoiceType
$meterPoints = $this->em->getRepository('AppBundle:MeterPoint')->findAll();
$mps = array();
foreach($meterPoints as $mp){
$mps [$mp->getMeterPoint()] = $mp->getId();
}
//dump($meterPoints);
$builder
->add('meterPoint', ChoiceType::class, array(
'label' => 'offerMeterPoint.meterPoint',
'attr' => array(
'class' => 'chosen-selesct',
'placeholder' => 'ob.to'
),
'choices' => $mps,
'placeholder' => 'global.plz_select'
))
You get a object serialized because your column type is array and EntityType automatically replaces choice values with objects.
There's however choice_value that accepts also a callable so I'd try fiddling with it and maybe you can get it to return just the id (maybe force string type?).
If this doesn't help then probably use just ChoiceType and handle the logic yourself.
This happens because Doctrine automatically deserializes the objects from $meterPoints that you try to use as entities with EntityType. These objects are obviously not managed by the Doctrine Entity manager. Thus the error.
I think you'll have to convert the $meterPoints to database entities before using $this->createForm(...) yourselves if you want to avoid creating more relations. Eventually and maybe even easier approach would be writing a custom Doctrine type that could do this for you:
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/cookbook/custom-mapping-types.html
I have the following form:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('type', ChoiceType::class, array(
'expanded' => true,
'multiple' => false,
'choices' => array(
'Friend' => 'friend',
'Guide' => 'guide'
)
));
}
How can I make 'Friend' checkbox to be checked by default when the form is rendered ?
I think you should try with data option, but it's just in the case where you don't even have a data saved inside your object, because it will override it else.
Important : It's good for create action, but not for edit action.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('type', ChoiceType::class, array(
'expanded' => true,
'multiple' => false,
'choices' => array(
'Friend' => 'friend',
'Guide' => 'guide'
),
'data' => 'friend'
));
}
Official link
Extract :
When you create a form, each field initially displays the value of the
corresponding property of the form's domain object (if an object is
bound to the form). If you want to override the initial value for the
form or just an individual field, you can set it in the data option
UPDATE If YOU NEED EMPTY VALUE:
As the answer below, replace data with empty_data if you need in any case to update default value
Use the empty_data form field option. (not data because it will override any posted data unless you set it dynamically).
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('type', ChoiceType::class, array(
'expanded' => true,
'multiple' => false,
'choices' => array(
'Friend' => 'friend',
'Guide' => 'guide'
),
'empty_data' => 'friend'
));
}
Another option for complex cases is to use Sf Dynamic Form Events.
If you don't want to override value for an edition you can do this :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$form = $event->getForm();
$form->add(
'type',
ChoiceType::class,
[
'expanded' => true,
'multiple' => false,
'choices' => [
'Friend' => 'friend',
'Guide' => 'guide'
],
'data' => $event->getData() ?: 'friend'
]);
});
}
I think it would be better to set initial values in the Entity constructor:
public function __construct()
{
$this->exercises = new ArrayCollection();
$this->setTitle("WOCHE#") ;
$this->setYear(date('Y'));
$this->setWeekInCalendar(Week::getCurrentWeekInCalendar());
}
An other solution would be to set placeholder as false.
This would set the first value as default and minimize setup effort.
If the field needs to be nullable you could add one more choice i.e. 'empty' => null
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('type', ChoiceType::class, array(
'expanded' => true,
'multiple' => false,
'choices' => array(
'Friend' => 'friend',
'Guide' => 'guide'
),
'placeholder' => false
));
}
I would like to have a collection of forms. My brick form is defined like:
class ImportPaymentsType extends AbstractType
{
public function getName()
{
return 'import_payments_type';
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('caisse', 'entity', array(
'label' => null,
'class' => 'ACME\AppBundle\Entity\Caisse',
'property' => 'name',
'empty_value' => 'Choose an option',
))
->add('label', 'text', array(
'label' => null,
))
->add('credit', 'number', array(
'label' => null,
));
}
}
In controller I construct this form like:
$data = array(
'imports' =>
array(
'caisse' => $em->getRepository('ACMEAppBundle:Caisse')->findOneByCode('P6015C'),
'label' => null,
'credit' => null,
)
);
$importForm = $this->createFormBuilder($data)
->add('imports', 'collection', array(
'type' => new ImportPaymentsType(),
))->getForm();
The error I get is:
The form's view data is expected to be of type scalar, array or an
instance of \ArrayAccess, but is an instance of class
ACME\AppBundle\Entity\Caisse. You can avoid this error by
setting the "data_class" option to
"ACME\AppBundleBundle\Entity\Caisse" or by adding a view
transformer that transforms an instance of class
ACME\AppBundleBundle\Entity\Caisse to scalar, array or an instance
of \ArrayAccess.
I tried to change ImportPaymentsType but it didn't work:
->add('caisse', 'entity', array(
'label' => null,
'class' => 'ACME\AppBundle\Entity\Caisse',
'property' => 'description',
'empty_value' => 'Choose an option',
'data_class' => 'ACME\AppBundle\Entity\Caisse'
))
What should I do? Please help.
'imports' should be an array of array.
Try with:
$data = array(
'imports' =>
array(
array(
'caisse' => $em->getRepository('ACMEAppBundle:Caisse')->findOneByCode('P6015C'),
'label' => null,
'credit' => null,
)
)
);
I have one superclass which gets extended by two entities. The superclass has a N:M relation with another entity. In the formbuilder of the latter, I need to separate the two subtypes into excluding separate fields.
This is its FormType::buildForm method:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('options_subtype_1', 'entity', [
'class' => 'AcmeExampleBundle:Options',
'expanded' => true,
'multiple' => true,
'property_path'=> 'options',
'query_builder' => /* specific filter 1 */
])
->add('options_subtype_2', 'entity', [
'class' => 'AcmeExampleBundle:Options',
'expanded' => true,
'multiple' => true,
'property_path'=> 'options',
'query_builder' => /* specific filter 2 */
])
;
}
However, no options are saved on form submission, except when I comment one of the two fields above (the one left is saved). I imagine setting property_path to the same property from two fields on the same form does not work.
How can I achieve this?
I'm still searching for the perfect solution by myself. For now I handle this kind of issues with a mapped hidden field and a PRE_SUBMIT Form Event. This could look like
FormType::buildForm
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('options_subtype_1', 'entity', [
'class' => 'AcmeExampleBundle:Options',
'expanded' => true,
'multiple' => true,
'mapped'=> false,
'data' => $builder->getData()->getOptions(),
'query_builder' => /* specific filter 1 */
])
->add('options_subtype_2', 'entity', [
'class' => 'AcmeExampleBundle:Options',
'expanded' => true,
'multiple' => true,
'mapped' => false,
'data' => $builder->getData()->getOptions(),
'query_builder' => /* specific filter 2 */
])
->add('options', 'entity', [
'class' => 'AcmeExampleBundle:Options',
'required' => false,
'multiple' => true,
'attr' => ['style' => 'visibility: hidden;'],
'label' => false
]);
$builder->addEventListener(
FormEvents::PRE_SUBMIT,
function (FormEvent $event) {
$data = $event->getData();
unset($data['options']);
$data['options'] = array_merge($data['options_subtype_1'], $data['options_subtype_2']);
$event->setData($data);
}
);
}
I have this Symfony form, it displays the values of the entity 'Manifestation' that has a many to many relationship.
So I do: ->add('manifestations').
But when I try to add an empty_value 'All' in the select list, it doesn't work!
Can any one help me ? Thanks
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('manifestations', 'entity', array(
'class' => 'PrifProtocoleBundle:Manifestation',
'multiple' => true,
'property' => 'name',
'empty_value' => 'All',
'required' => false,));
}
empty values New in Symfony version 2.3 .
empty values are also supported if the expanded option is set to true like Symfony 2.3 Documentation
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('manifestations', 'entity', array(
'class' => 'PrifProtocoleBundle:Manifestation',
'multiple' => true,
'expanded' => true,
'property' => 'name',
'empty_value' => 'All',
'required' => false,));
}