Symfony2 form - Many to Many as text causing errors - php

I have tried looking around for a possible solution to this but with no luck.
What I have is a Many to many relationship between properties and postcodes, I can't display the postcodes in a select for example due to the amount of possible entries.
My solution was to have it as a text field in the form and then catch it on PrePersist to search for the matching record and then apply this to the entity before persisting to the db.
The problem is when the form is validating it is still trying to pass the string value to the setter which is expecting an entity object.
Is there anyway to prevent this from causing an error?
I have attached my form code for you.
Thanks,
Harry
$propertyData = new PropertyData();
$builder
->add('reference')
->add('listing_type', 'choice', array('choices' => $propertyData->getListingTypes()))
->add('listing_status', 'choice', array('choices' => $propertyData->getStatusList()))
->add('title')
->add('house_number')
->add('address_line_1')
->add('address_line_2')
->add('town', 'text', array('data_class'=> 'Minos\Bundle\PropertyBundle\Entity\UtilTown'))
->add('county')
->add('country')
->add('council')
->add('region')
->add('postcode', 'text', array('data_class'=> 'Minos\Bundle\PropertyBundle\Entity\UtilPostcode'))
->add('short_description')
->add('long_description')
->add('size_sq_ft')
->add('floor_level')
->add('property_age')
->add('tenure_type', 'choice', array('choices' => $propertyData->getTenureTypes()))
->add('garage')
->add('num_living_rooms')
->add('num_bathrooms')
->add('num_bedrooms')
->add('num_floors')
->add('num_receptions')
->add('property_type')
//->add('prices')
;

You need a data transformer to convert your string input to an entity before processing the form.
$builder
// ...
->add('postcode', 'text', array(
'data_class'=> 'Minos\Bundle\PropertyBundle\Entity\UtilPostcode'
))
// ...
;
$builder->get('postcode')->addModelTransformer(new CallbackTransformer(
//Render an entity to a string to display in the text input
function($originalInput){
$string = $originalInput->getPostcode();
return $string;
},
//Take the form submitted value and convert it before processing.
//$submittedValue will be the string because you defined
// it in the builder that way
function($submittedValue){
//Do whatever to fetch the postcodes entity:
$postcodeEntity = $entityManager->find('AppBundle\postcodes', $submittedValue);
return $postcodeEntity;
}
));
This is just an example (I haven't tested it), you will need to change some stuff to match how your entities look.

Related

Symfony 3 forms: how to set default value for a textarea widget

I want to set the value on a textarea widget.
How i can set default value for textarea in Symfony 3
for TextType(input type="text") i can use value parameter but for textarea i can not!!!how i can set default value for textarea.
this is my buildForm
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('linkdin', TextType::class, array('attr' => array('placeholder' =>
'linkdin','class' => 'form-control width100','value' =>
MainPageType::$content1[0]['linkdin'])))
->add('addres', CKEditorType::class, array('attr' => array('required' =>
'false','name'=>'editor1' ,'id' => 'editor1','class' => 'ckeditor','empty_data'
=> MainPageType::$content1[0]['addres'])))
.
.
Assuming you are using Symfony 3.4, there's quite a good documentation for that.
Long story short, you should use data:
$builder->add('token', TextareaType::class, array(
'data' => 'abcdef',
));
As the docs say:
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 its persisted value when the form is submitted.
You can transfer the variable with data to the formType in controller like that
$form = $this->createForm(Form::class,$YourData);
If you’re using the form for both saving a new record and editing an existing one, chances are that you’ve found the ‘data’ option (Alex’s solution) to be limited, because the fields are overridden with the default data while editing an existing record.
One of the solutions is to set the default data manually in the new() action in your controller, but only on the GET call, not POST.
$form = $this->createForm(MyType::class, $dto);
$form->handleRequest($request);
if($form->isSubmitted()) {
if($form->isValid()) {
// Save data
}
} else {
// Set default value
$form->get('date')->setData(
new\DateTime(’now’)
);
}

Symfony2 having issues with multiple property in form types

I'm pretty deep into a complex Symfony2 project, with many entities and join tables in forms etc. but I'm having a strange issue with the "multiple" attribute within the form builder.
Basically I have a form where a user can add an illness to the CRM, and each illness can be attached to a specific store (depending on the language used). There is a list of stores that can be chosen by the user within this form, and the values are stored in a join table. However I only want one store to be chosen (i.e. a select drop down) rather than a multiple select list but using the false value for the multiple attribute throws an error which I will outline later.
Firstly, here is the buildForm() code in my Type.php file:
$builder->add('name' , 'text');
$builder->add('description' , 'textarea');
$builder->add('store', 'entity',
array(
'class' => 'AppBundle:Store',
'empty_value' => 'Choose store',
'property' => 'name',
'multiple' => false,
));
$builder->add('save', 'submit', array(
'attr' => array(
'class' => 'btn btn-primary'
),
));
And the entry for the store field in my Entity:
/**
* #var \Doctrine\Common\Collections\Collection
*
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Store", inversedBy="illness", cascade={"persist"})
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="store_id", referencedColumnName="id")
* })
*/
public $store;
However, if I used the false declaration for the multiple attribute in the form Type, when the form is submitted I receive the following error:
Warning: spl_object_hash() expects parameter 1 to be object, string given
because it looks like it's passing the text value of the select box, rather than the relevant Entity. When set as a multiple select box (i.e. set to true) then it works fine and persists as it should.
My controller code:
$addIllness = new Illness();
$form = $this->createForm(new IllnessType($em), $addIllness);
$form->handleRequest($request);
if ($form->isValid()) {
$em->persist($addIllness);
$em->flush();
return $this->redirect($this->generateUrl('app_illness_table'));
}
Having it as a multiple select box is not the end of the world, though I'd rather have it as a drop down select so the user cannot select more than one store - rather than me having to add an error message or note to tell them otherwise.
If anyone has an ideas as to why this may be happening, or has encountered it before please let me know, I would be very grateful!
Thank you
Michael

How-to: Optimize Symfony's forms' performance?

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.

Symfony2 form is not valid but entity gets saved anyway?

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?

How to build the generic form which is not linked to any entity in symfony2

So far , i have only built form which are related to entities using formbuilder
i normally do this
$builder->add("tasks")
Then i do this
$Form = $this->createForm(new TaskType(), $entity);
Now i just want a generic form where i have the select boxes in which i can load entities from database.
I am not persisting or binding that form i just want that when user select user and hit submit then i go to that user page
IN the above form i used to have tasks as property in user entity so i used add.
But i just want want to display tasks , whichis not linked to user can i do that
$form = $this->createFormBuilder($task)
->add('task', 'text')
->add('dueDate', 'date')
->getForm();
Example i above code can i do that
$form = $this->createFormBuilder()
->add('task', 'text')
->add('dueDate', 'date')
->getForm();
Before reading, why would you show a <select> for doing nothing? Maybe there is a better solution to achieve what you want :)
Anyway, if your form has some field not related to an entity property, just set the option property_path to false. Assuming you want to display a <select> ot tasks i would do:
$this->createFormBuilder()
->add('task', 'entity', array(
'property_path' => false,
'class' => 'Acme\HelloBundle\Entity\Task', // Full/short FQCN
'property' => 'name', // Assuming your task as a property "name",
))
;
And if for some reason you need selected value (mean selected Task object) you can get with $form->get('task')->getData(), after binding it with the request.

Categories