I have few cases where I need to ugly customizing of my FormType classes.
First one is that I need to check if the state of user is active in this case disable possibility to edit username. But just adding disabled atribute is not protecting input to be not submitted. So I decided not to show username input field. I achieved it by passing boolean through options.
Controller:
$userForm = $this->createForm(UserType::class, $user, array(
'is_active' => ($user->getState() == 'active')
));
And then in UserType class:
if ($options['is_active']) {
$builder
->add('username', EmailType::class);
}
$builder
->add('firstName', TextType::class),
...
Second case is that I need to remove NotBlank() constraint and add 'required' => false attribute from FileType field when profile photo is uploaded. I achieved it in similar way by passing boolean through options.
Controller:
$userForm = $this->createForm(UserType::class, $user, array(
'is_uploaded' => !empty($photo)
));
UserType class:
// achieved same way as previous problem
My questions would be:
What recommendations would be dealing with these kind of cases?
Is what I did correct and acceptable?
Is there a documentation or examples dealing with any of these cases?
You can move all this form configuration's logic into the form class.
Since you pass $user entity into the form with:
$userForm = $this->createForm(UserType::class, $user, array( // <- $user is passed
'is_uploaded' => !empty($photo)
));
You can access it in builForm method with:
$user = $builder->getData();
Then you can verify all the condifions inside the form and there's no need for making mess in controller.
Related
I am trying to specify a default value in a form so when creating an entity the form field has a value (not null or empty). However when the entity is being edited it should obviously show the stored value and not the default.
My entity initializes itself as part of the construction - so when an entity is new and not yet persisted these values should be set.
How do I inform the FormType to use the default value over the persisted state? Everything I try seems to suggest it's one or the other not both?
How is this done Symfony 3.2+???
EDIT |
controller:
public function newAction (Request $request)
{
$quoteItem = new QuoteItem();
$form = $this->createForm('UniflyteBundle\Form\QuoteItemType', $quoteItem, ['allow_extra_fields' => true]);
$form->add('QuoteFlight', QuoteFlightType::class);
}
Form type:
public function configureOptions (OptionsResolver $resolver)
{
$resolver->setDefaults([
//'data' => new \UniflyteBundle\Entity\QuoteFlight()
'data_class' => QuoteFlight::class
]);
}
public function buildForm (FormBuilderInterface $builder, array $options)
{
$builder
->add('specMajorSetupCharge', null, [
//'empty_data' => QuoteFlight::SPEC_MAJOR_SETUP_CHARGE,
'data' => QuoteFlight::SPEC_MAJOR_SETUP_CHARGE,
'label' => '* Setups Charge'
])
// ...
}
http://symfony.com/doc/current/components/form.html#setting-default-values
If you need your form to load with some default values (or you're building an "edit" form), simply pass in the default data when creating your form builder.
$quoteItem = new QuoteItem();
$quoteItem->getQuoteFlight()->setSpecMajorSetupCharge(QuoteFlight::SPEC_MAJOR_SETUP_CHARGE).
$form = $this->createForm(QuoteItemType::class, $quoteItem);
// ...
Using data option is no good, because:
http://symfony.com/doc/current/reference/forms/types/form.html#data
The data option always overrides the value taken from the domain data (object) when rendering. This means the object value is also overriden when the form edits an already persisted object, causing it to lose it's persisted value when the form is submitted.
So the recomendation is to set explicitly the data in the underlined object on initialization, either in __constructor() or before bind the object to the form.
To answer my own question and avoid confusion for anyone in the future:
$quoteItem = new QuoteItem();
// THIS LINE WAS MISSING
$quoteItem->setQuoteFlight(new QuoteFlight());
$form = $this->createForm('UniflyteBundle\Form\QuoteItemType', $quoteItem, ['allow_extra_fields' => true]);
$form->add('QuoteFlight', QuoteFlightType::class);
Without the added line the QuoteFlight entity was NULL when the form rendered during create.
I have an entity field type I'm using in an 'edit' form.
The field type lists the correct options and the data persists, but it selects the 'top' result in the list by default, and not the data that is in the DB.
So for example if I have a record with the shelf marked as SH6, and I go to edit, the default shelf selected in the entity field type will be whatever is at the top of the list, ie SH1.
This means that users might go to edit unitsInStock and accidentally change the shelf value, because they didn't realise it was set to the wrong thing. Even more annoying is that even if you know about the problem, you may not remember the value it is supposed to be set to.
This is my controller action.
public function editAction($id, Request $request) {
$em = $this->getDoctrine()->getManager();
$article20000stock = $em->getRepository('RegenerysQMSBundle:Article20000Stock')->find($id);
if (!$article20000stock) {
throw $this->createNotFoundException(
'No id ' . $id
);
}
$form = $this->createFormBuilder($article20000stock)
->add('article20000Information')
->add('unitsInStock')
->add('expiryDate')
->add('shelf', 'entity', array('class' => 'RegenerysQMSBundle:Shelf', 'property' => 'id', ))
->add('submit', 'submit')
->getForm();
$form->handleRequest($request);
if ($form->isValid()) {
$em->flush();
return $this->redirectToRoute('regenerys_qms_article20000stock_individual', array('id' => $id));
}
$build['form'] = $form->createView();
return $this->render('forms/editArticle20000Stock.html.twig', $build);
}
So the Entity was lacking a relationship, which is what was causing the issue.
I have a form that is the bottleneck of my ajax-request.
$order = $this->getDoctrine()
->getRepository('AcmeMyBundle:Order')
->find($id);
$order = $order ? $order : new Order();
$form = $this->createForm(new OrderType(), $order);
$formView = $form->createView();
return $this->render(
'AcmeMyBundle:Ajax:order_edit.html.twig',
array(
'form' => $formView,
)
);
For more cleaner code I deleted stopwatch statements.
My OrderType has next fields:
$builder
->add('status') // enum (string)
->add('paid_status') // enum (string)
->add('purchases_price') // int
->add('discount_price') // int
->add('delivery_price') // int
->add('delivery_real_price', null, array('required' => false)) // int
->add('buyer_name') // string
->add('buyer_phone') // string
->add('buyer_email') // string
->add('buyer_address') // string
->add('comment') // string
->add('manager_comment') // string
->add('delivery_type') // enum (string)
->add('delivery_track_id') // string
->add('payment_method') // enum (string)
->add('payment_id') // string
->add('reward') // int
->add('reward_status') // enum (string)
->add('container') // string
->add('partner') // Entity: User
->add('website', 'website') // Entity: Website
->add('products', 'collection', array( // Entity: Purchase
'type' => 'purchase',
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'property_path' => 'purchases',
'error_bubbling' => false,
));
Purchase type:
$builder
->add('amount')
->add('price')
->add('code', 'variant', array(
'property_path' => 'variantEntity',
'data_class' => '\Acme\MyBundle\Entity\Simpla\Variant'
))
;
Also Purchase type has a listener that is not significant here. It is represented in Symfony profiler below as variant_retrieve, purchase_form_creating. You can see that it takes about 200ms.
Here I put the result of profilers:
As you can see: $this->createForm(...) takes 1011ms, $form->createView(); takes 2876ms and form rendering in twig is also very slow: 4335ms. As stated by blackfire profiler all the deal in ObjectHydrator::gatherRowData() and UnitOfWork::createEntity().
Method createEntity() called 2223 times because there is some field that mapped with Variant entity and has form type Entity. But as you can see from above code there is no entity types for variant. My VariantType is simple extended text form type that has modelTransformer. To not mess up everything you can see code for similar Type class at docs.
I found with XDebug that buildView for VariantType has been called in Purchase's buildView with text form type. But after that from somewhere buildView for VariantType was called again and in this case it has entity form type. How can it be possible? I tried to define empty array in choices and preferred_choices on every my form type but it didn't change anything. What I need to do to prevent EntityChoiceList to be loaded for my form?
The described behavior looks as the work of the guesser. I have the feeling that there is need to show an some additional code (listeners, VariantType, WebsiteType, PartnerType).
Let's assume a some class has association variant to Variant and FormType for this class has code ->add('variant') without explicit specifying type (as I see there is a lot of places where the type is not specified). Then DoctrineOrmTypeGuesser comes in the game.
https://github.com/symfony/symfony/blob/2.7/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php#L46
This code assign the entity type (!) to this child. The EntityRepository::findAll() is called and all variants from DB are hydrated.
As for another form optimization ways:
Try to specify type in all possible cases to prevent a type guessing;
Use SELECT with JOINs to get an order as new sub-requests to DB are sent to set an underlying data for an every form maps relation;
Preserve keys for collection elements on a submission as a removing of a single element without a keys preserving will trigger unnecessary updates.
I also had the same problem with the entity type, I needed to list cities, there were like mire then 4000, what I did basically is to inject the choices into the form. In your controller you ask the Variants from the database, in a repository call, hydrate them as array, and you select only the id and the name, or title, and then you pass into the form, as options value. With this the database part will be much quicker.
I couldn't find a solution for my current problem in the web.
I got a formular and an entity.
If the formular is not valid ($form->isValid() returns false), the entity is saved with the invalid data anyway. I see the form errors and that's correct. But my entity should not be updated if the form is not valid. What is going wrong here?
My entity got a hand full fields and a many-to-many relation with extra fields too.
I can show you some code parts:
$entry = $em->getRepository('MyAppBundle:Entry')->find($id);
// ...
$form = $this->createForm(new EntryType($this->getUser(), $entry), $entry);
$request = $this->getRequest();
if ($request->getMethod() == 'POST') {
$form->bind($request);
// ...
// i set custom errors here by myself!
if ($entry->getDeadlineEnable() && NULL === $entry->getDeadlineAt()) {
$form->get('deadline_at')->addError(new FormError('Please enter a date.'));
}
// ...
if ($form->isValid()) {
$em->merge($entry);
$em->flush();
// ...
return $this->redirect($this->generateUrl('MyAppBundle_homepage'));
As you can see, I check the form with isValid, which works correct. It returns false if there is a form error, e.g.
$form->get('deadline_at')->addError(new FormError('Please enter a date.'));
But the entitiy gets updated exactly here in this line anyway:
if ($form->isValid()) {
Why is this?
This is a really bad problem here. I don't know why this happens.
Thanks for any advice.
EDIT:
More information:
I use validators too for all fields wich has simple conditions.
I do this in the EntryType.php # public function buildForm(FormBuilderInterface $builder, array $options), e.g.:
$builder->add('min_commitments', null, array(
'label' => 'Zusagen mindestens',
'attr' => array(
'class' => 'form-control',
'placeholder' => 'Ab wievielen findet\'s statt?',
'min' => 0,
'max' => 100,
),
'required' => false,
'invalid_message' => 'Das ist keine gütige Angabe.',
'constraints' => array(
new Range(array(
'minMessage' => 'Mindestens 1.',
'maxMessage' => 'Maximal 100.',
'min' => 1,
'max' => 100,
)),
),
));
This is a number field to type in a number between 1 and 100.
But for some fileds i use my own validation direclty in the controller action method because the fields (most of them are a combination of a checkbox field and an text field belonging to to this checkbox) because many fields are interdependent.
I still don't know whats wrong with isValid and why the entity behind this form gets saved even the form is not completely valid. isValid returns false there if an error exists (correct).
Could this maybe be caused by the many to many relation ship?
I implementet such a relationship to allow the user to select many checkboxes, where each checkbox represents an user. The relation rows are saved into the relation table EntryUser.
Here is the many to many field in the form definition (Entry.php):
/**
* #ORM\OneToMany(targetEntity="EntryUser", mappedBy="entry", cascade={"all"}, orphanRemoval=true)
*/
protected $entry_users; // One more thing: this is a one to many but it is a many to many relationship at all because i created a relationship table with extra fields so it bacame a own entity. on the other side, the file User.php got the oppsite part of the many-to-many relationship with:
User.php:
/**
* #ORM\OneToMany(targetEntity="EntryUser" , mappedBy="user" , cascade={"all"} , orphanRemoval=true)
*/
protected $entry_users;
The form->bind is what is actually updating the entity with posted
data regardless of the data's validity. You must have another flush
getting kicked off somewhere. Perhaps a listener? Normal workflow for
$form->isValid() === false is to redisplay the form with the invalid
data and error messages. Once again, it would appear that you have
something calling $em->flush.-
Credit to #Cerad
Have you ever tried validators? http://symfony.com/doc/current/book/validation.html
If your attached code is from your controller - the problem is that your condition is wrong.
if ($entry->getDeadlineEnable() && NULL === $entry->getDeadlineAt()) {
$form->get('deadline_at')->addError(new FormError('Please enter a date.'));
}
Controller isn't the best place to valide. How are you going to reuse the validator for the form?
I'm starting developing with Symfony2 and looks like I need help. I have Product entity related with SynchronizationSetting entity. I can edit product data by form maped with his entity. But I also need to modify some data related to product in SynchronizationSetting. To do that I've modified the form so it look like that (Vendor\ProductBundle\Form\ProductType.php):
...
->add('synchronization_setting', 'choice', array(
'choices' => array('daily' => 'Daily', 'weekly' => 'Weekly', 'never' => 'Never'))
After form is submitted selected checkbox values are passed to setSynchronizationSetting method in Product Entity. Then I do that (Vendor\ProductBundle\Entity\SynchronizationSetting.php):
public function setSynchronizationSetting($data)
{
$synchronizationSetting = new SynchronizationSetting();
$synchronizationSetting->setDaily(in_array('daily', $data) ? '1' : '0');
...
}
And now I need to somehow save those SynchronizationSetting entity into database. I read that calling entity manager from here is very bad practice so... how should I save this?
One possible way (I'm not sure if it's good practice)
public function setSynchronizationSetting($data)
{
$synchronizationSetting = new SynchronizationSetting();
$synchronizationSetting->setDaily(in_array('daily', $data) ? '1' : '0');
}
public function retSynchronizationSetting()
{
return $this->synchronizationSetting;
}
Then in your controller in place where you handle form data you call retSynchronizationSetting() and save entity using EntityManager.