How to pass custom options to a Symfony form - php

I've to pass a custom option to a symfony form. I followed the documentation setp by step but my options will not pass.
The FormType
class AdvertType extends AbstractType
{
/**
* {#inheritDoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$entityManager = $options['em'];
}
/**
* {#inheritDoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setRequired(array(
'em',
));
$resolver->setAllowedTypes(array(
'em' => 'Doctrine\Common\Persistence\ObjectManager',
));
}
/**
* {#inheritDoc}
*/
public function getName()
{
return 'advert';
}
}
Here is the Code from the controller.
$form = $this->createForm(new AdvertType(), new Advert(), array(
'em' => $this->getDoctrine()->getManager(),
));
Symfony will throw an exception that my option em is missing.
Exception: The required option "em" is missing.
I followed the tutorial to add a data transformers: http://symfony.com/doc/current/cookbook/form/data_transformers.html
I cleared my cache and restarted my web server but nothing will work. What did I wrong? Did I missed a configuration to pass my $options? It looks like the $options array from the controller I passed will never reach the buildForm method.
I'm using Symfony v2.3.5. For testing I updated to the latest (2.3.6) but the problem still exists.
Cheers.

I've copied your code and used it successfully (Symfony 2.3.6). It worked! You shouldn't have to clear your cache. So I'm not sure what's wrong. You should also consider adding the data_class option in your resolver if you want to constrain the form to your Advert object, e.g.
$resolver
->setDefaults(array(
'data_class' => 'Your\Bundle\Entity\Advert',
))
;

I used my form twice (in different ways) so the error results from the wrong usage. I looked on the wrong place for the error. The code in general is correct. Sorry for that.

Related

set valid constraint for embedded entity only if flag in embedded object property is set

I am using symfony3.4.
Now i have a form presented by a data-class which uses several child entities as property:
contact {
protected $address;
protected $user;
protected $message;
}
The address entity includes a field "address->validate" which can be set by the user in the frontend. The requirement now is, that address only should be validated, if the user checks the field "address->validatable".
address {
private $street;
//[...]
private $validatable; //bool
public function isValidatable(){...}
}
Normally, I would use a callback-constraint, to check the field. But since the callback function is static, all the examples only show callbacks with one single field-property and ExecutionContextInterface does not allow me, to add constraints afterwards, I don't know how to implement it.
Can anybody tell me how to solve this case?
EDIT:
Thanks to Marc I solved it this way:
I have a form-type-class called "ContactGeneralType". In this class I add the group like described on https://symfony.com/doc/3.4/form/data_based_validation.html:
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults($this->addDefaults([
'data_class' => ContactGeneral::class,
'cascade_validation' => true,
]));
$resolver->setDefaults([
'validation_groups' => function (FormInterface $form) {
$data = $form->getData();
if ($data->getAddress()->isValidatable()) {
return ['User', 'Address'];
} else {
return ['User'];
}
},
]);
}
That does all the trick. If you have more address-validation-groups (e.g. if you validate different countries), you already can inject your service in the form-type-class and set the group-name dynamically, too:
if ($data->getAddress()->isValidatable()) {
return ['User', $this->locationManager>getGroupName()];
}
Instead of using callback, try creating custom validator that is explained here: https://symfony.com/doc/current/validation/custom_constraint.html. You get your Object inside of validator and you can check if field is checked or w/e and validate based on that.
EDIT:
Maybe changing the validation group on form submit is better solution as described here https://symfony.com/doc/current/form/data_based_validation.html

How to POST nested entities with FOSRest and Symfony Form

Using Symfony 3.2 and FOS REST Bundle I have created some REST endpoints for a resource and use a Symfony Form to define the fields for API DOC. Everything is working fine to this point.
Now I'm trying to improve my schema and added a sub entity (one-to-one) to my resource. I want the main resource to save the sub entity - there is no dedicated endpoint for the sub entity.
I followed the instruction on the Symfony documentation and removed all other fields to isolate any issues.
This is how my form type looks now:
<?php
namespace VendorName\MyBundle\Form;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class CountryType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('mySubEntity', EntityType::class, array(
'multiple' => false,
'expanded' => false,
'property' => 'name',
'class' => 'MyBundle\Entity\mySubEntity'));
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'VendorName\MyBundle\Entity\Country',
'csrf_protection' => false
));
}
}
Now when I load my api-docs, I receive the error message The option "property" does not exist. Defined options are: "action", "allow_extra_fields", [...].
To be honest, I don't even know if adding the Entity to the form is the right approach to make it show up in the API Docs. Any help in resolving the above issue and / or best practices to achieve this would be appreciated.
EDIT: Thanks to #miikes this error is now resolved and I can see the api doc showing up correctly with the fields of the nested form. However, now my issue is that the form does not populate the sub entity on the parent entity. This seems to be related to the way I modelled the parent-child relationship and I have posted a new question for this issue.
To resolve your error try to use choice_label, instead of property option.
'choice_label' => 'name'
But referring to the documentation, EntityType is a kind of ChoiceType, so using this type, you can only select existing entity, not persist new one.
The easiest and most clear way for creating new entity instance is creating another type class, designed for your entity, and adding the type as the field to your CountryType.
class MyEntityType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('some-field', TextType::class)
->add('another-field', TextType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => MyEntity::class
]);
}
}
class CountryType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('mySubEntity', MyEntityType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => Country::class,
'csrf_protection' => false
));
}
}
So you should pass form data as
['mySubEntity']['some-field'] = 'foo'
['mySubEntity']['another-field'] = 'bar'
Another tip is to use Country::class instead of string 'VendorName\MyBundle\Entity\Country', because in case of renaming class, IDE refactoring should affect on your type.

Symfony : Can I return null from Type / Form?

I have an entity form with Symfony :
class MyType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
...
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'LogicielBundle\Entity\FichierGroup',
'intention' => $this->getName() . '_token'
));
}
But in POST_SUBMIT event, I want to return null (no entity).
I tested this but not working :
$builder->addEventListener(FormEvents::POST_SUBMIT, function(FormEvent $event) {
.... my condition ...
$event->setData(null);
});
Can you help me ? Thanks :)
Could you please post you controller code?
Do you pass an object reference to createForm, or do you use $form->getData() ? In your case, you should stick to the second.
Try using SUBMIT event instead of POST_SUBMIT. As Symfony doc states it, "It can be used to change data from the normalized representation of the data.".
To my knowledge you can not change a submit form. (I may be wrong)
But if you want reset a form after submit, you can do it in your controller:
For example:
public function fooAction()
{
...
$form = $this->getEntityForm($entity);
...
$form->handleRequest($request);
if ($form->isSubmitted()) {
// Do something
...
// Reset your form with null as entity
$form = $this->getEntityForm(null);
}
...
return $this->render(
'yourfootwig.html.twig',
[
'form' => $form->createView(),
...
]
);
}
protected function getEntityForm($entity = null)
{
return $this->createForm(
'Foo:MyType',
$entity,
[...]);
}
Of course, you must adapt it with your own code.
Actually, if you want change data passed to form entity, the event should be listened on is FormEvents::PRE_SUBMIT. As described in the official doc.

Extra field in form when changing HTTP method in Symfony / FOSRestBundle

I am building a REST API in Symfony using FOSRestBundle and I've stumbled upon a problem which seems to be trivial, but I can't find a nice solution.
I want to create a controller method to handle PATCH requests:
/**
* #param Article $article
* #param Request $request
*
* #Patch("/articles/{slug}")
* #ParamConverter("article", converter="doctrine.orm")
*
* #return Response
*/
public function patchAction(Article $article, Request $request)
{
$form = $this->createForm(new ArticleType(), $article);
$form->handleRequest($request);
if ($form->isValid()) {
$entityManager = $this->get('doctrine.orm.entity_manager');
$entityManager->merge($article);
$entityManager->flush();
$view = $this->view($article)
->setTemplate('MyBundle::articleSubmit.html.twig')
->setTemplateVar('article')
;
return $this->handleView($view);
}
$view = $this->view($form)
->setTemplate('MyBundle::articleForm.html.twig')
->setTemplateVar('form')
->setTemplateData(array('article' => $article))
;
return $this->handleView($view);
}
I've configured form type like this, setting method option to PATCH:
namespace MyBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ArticleType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('content', 'textarea');
}
public function getName()
{
return '';
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'MyBundle\Entity\Article',
'method' => 'PATCH',
));
}
}
When I want to see this working, everything goes fine as long as I make a real PATCH request (for example using Open HttpRequester). But as most browsers support only GET and POST method, Symfony makes a trick adding _method parameter to regular POST request, and that's where my problem begins, because when testing this in browser I get the message:
This form should not contain extra fields.
This is obviously because ArticleType::getName() returns empty string, but I would like my request parameters look more like content=foo than like article[content]=foo, as it's supposed in REST API. That's why I want this method to return blank value.
This works normally either when I change the action to POST, or when I set the form's name, or when I make a real PATCH request, but not when I'm sending a POST request with _method parameter.
I've managed to solve this problem creating a body listener that removes fields having names prefixed by _, but that's more a dirty hack than a nice and clean solution.
I'm sure there must be some better workaround, thank you in advance for any help.

Symfony2 create a form type to add multiple records of the same type

I need to create a form type that will create multiple records of a certain Entity.
I figured out to create a parent Form Type and then add a collection inside. This parent Type isn't bind to any entity, but child Type is. The problem is, that when I process the request, I get an empty data object.
Here is my parent Type:
class ChallengeCollectionType extends AbstractType {
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('email_invitations', CollectionType::class, [
'entry_type' => EmailInvitationType::class
]);
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'csrf_protection' => false
));
}
}
My Child Type:
class EmailInvitationType extends AbstractType {
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('email', EmailType::class);
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => Challenge::class,
'csrf_protection' => false
));
}
}
Here is how I process my data in Controller:
public function inviteEmailAction(Request $request){
$form = $this->createForm(ChallengeCollectionType::class);
$form->handleRequest($request);
dump($form->getData());
dump($form->get('email_invitations')->getData());
die();
}
I submit data in a POST request (this is API and there's no HTML form) like this:
challenge_collection[email_invitations][0][email] = "email#address.com"
challenge_collection[email_invitations][1][email] = "address#email.com"
And Controller returns empty [] for all dumps.
I've actually found a workaround, but it looks bad:
$data = ['email_invitations' => [new Challenge(), new Challenge()]];
$form = $this->createForm(ChallengeCollectionType::class, $data);
$form->handleRequest($request);
And this will work, so I have to send an array of empty objects of the type of the Entity to get request processed.
Or even this way:
$data = ['email_invitations' => []];
if(!empty($request->request->get('challenge_collection'))){
if(!empty($request->request->get('challenge_collection')['email_invitations'])){
for($i = 0; $i < count($request->request->get('challenge_collection')['email_invitations']); $i ++){
$data['email_invitations'][] = new Challenge();
}
}
}
There should be a better way to do it, please help me to find it.
You should try using allow_add and allow_delete options in collection field type. See the Collection field type documentation
Also, this is important for parent entity to have an mapped-by association with child entity, which would get you child objects in an ArrayCollection object in controller.
Further, if you want to save/remove them automatically as per the request, you need to implement a 'by_referencefalse and set association withcascade-persistandcascade-remove`.
I have used collection field type with API numerous times, and it worked like charm. Let me know if you need further explanation.
Maybe you should try in your controller something like :
$invitations = $this->getDoctrine()->getRepository('yourInvitationEntityClass')->findAll());
$form = $this->createForm(ChallengeCollectionType::class, $invitations );
You could find explanations here :
http://symfony.com/doc/current/cookbook/form/form_collections.html
One form with all row of one entity

Categories