Disabling a form at PRE_SET_DATA symfony2 - php

I'm working on a form extension that can disable some fields in form types when the underlying data is not new.I do this so I don't have to create seperate forms or event listeners for update/creation forms.i.e: I create the entity creation form and the extension disables the fields signified by an option if the underlying entity is not new.I bound an event listener to the PRE_SET_DATA event as such:
class RemoveFieldsExtension extends AbstractTypeExtension{
public function buildForm(FormBuilderInterface $builder, array $options)
{
if (!$options['disable_on_update']) {
return;
}
$isNewCallback = $options['disable_on_update_is_new'];
$em = $this->em;
$builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event) use ($em, $isNewCallback, $builder){
if(is_bool($isNewCallback))
return $isNewCallback;
if(is_callable($isNewCallback)){
$isNew = call_user_func($isNewCallback, $event->getData(), $em);
if(!$isNew){//the check for resolving if this is an update form or creation form
// $builder->setDisabled(true);
$form = $event->getForm();
$form->getConfig()->setDisabled(true);
}
}
},
-200
);
}
}
the extension works fine the problem is that the above code will produce 'FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'
it also didn't work when I tried $builder->setDisabled(true);.what can I do to accomplish this?

Related

Error added to Symfony 3 form element is overwriten/deleted

I have Symfony form with PRE_SUBMIT listener.
The listener checks string format for one field.
class TestEntType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('someText', TextType::class, ['error_bubbling' => false]);
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$data = $event->getData();
$form = $event->getForm();
...
if (false === $this->isFormattedCorrectly($data['someText'])) {
$form->get('someText')
->addError(new FormError('Text format should be...'));
}
});
}
...
}
If the listener is written this way - form is valid, even if I force adding error every single time.
It seems that error is somewhere overwritten or deleted.
But if I make small change, and convert $form->get('someText')->addError(..) to $form->addError(..), form isn't valid any more.
If I dump (string)$form->getErrors(true) in the controller, I get ERROR: Text format should be...
So, the error isn't lost in this situation.
But the problem is that this error isn't bounded to someText field!
This is the controller:
class TestEntController extends Controller
{
/**
* #Route("/test-ent", name="test-ent")
* #Method("POST")
*/
public function testAction(Request $request)
{
$testEnt = new TestEnt();
$form = $this->createForm(TestEntType::class, $testEnt);
$data = json_decode($request->getContent(), true);
if ($data === null) {
//...
}
$form->submit($data);
if (!$form->isValid()) {
// ...throw $this->throwApiProblemValidationException($form);
}
$em = $this->getDoctrine()->getManager();
$em->persist($testEnt);
$em->flush();
return new JsonResponse($testEnt);
}
}
Has someone idea where error disappears when I bound it to the specific field?
Is possible to bound error to the specific field in PRE_SUBMIT form listener?
Is there some smarter way to check is string formatted correctly?
I was following directions from this SO question Add error to Symfony 2 form element
EDIT: It seems that everything normally works if I listen for FormEvents:: SUBMIT event. In that case, error isn't lost.
To figure out what is happening, I'll have to in detail investigate how Symfony form works...

How to restrict modification to selected properties of an entity while persisting?

Let's suppose I have an entity class and a generic FormType corresponding to the entity
class Entry {
protected $start_time;
protected $end_time;
protected $confirmed; // Boolean
// ... Other fields and getters and setters
}
On the CRUD of this entity, I don't want to allow any modification on start_time or end_time if the entity has been confirmed or in the above case when $confirmed === true
On the twig file, I disable the fields I want to restrict, like the following:
{% if entity.approved == true %}
{{ form_row(entity.start_time), { 'attr' : { 'disabled' : 'disabled' } }) }}
{% endif %}
{# Sameway for another field #}
Now the problem is that this is a front end resolution which can be tampered very easily using web developer tools in web browsers now. But regardless what I am trying to achieve is not have those two fields changed once the entity in confirmed.
So, one way I tried was after the form was submitted, I check if the entity was confirmed and if it was was, I fetch the earlier state of the entity and set the value of the new one (which is about to be persisted) with the values from old one.
On Controller:
$confirmed = $entity->getConfirmed();
$form->handleRequest($request);
if($form->isSubmitted() && $editForm->isValid()) {
// The form was submitted
if($confirmed === true) { // if the entity was confirmed previously
$oldEntity = $em->getRepository('...')->find($entity->getId());
$entity->setStartTime($oldEntity->getStartTime());
$entity->setEndTime($oldEntity->getEndTime());
}
$em->persist($entity);
$em->flush();
}
The problem here was $oldEntity was exactly same as $entity. My guess is doctrine picked up that it already has the entity that is being asked and just returned me with the same object. Anyways, my attempt to solve this problem failed.
Any idea how to restrict/revert changes on selected properties while allowing changes on rest of the properties of the entity?
Update:
Modifying the form type to disable the field is not an option because I only want them to be read-only/disabled only if entity is confirmed and rest of time I want the form to be as it is.
You must add attribute 'disabled' => true in form builder, not only in twig.
If you don't want a user to modify the value of a field, you can set the disabled option to true. Any submitted value will be ignored.
Reference: http://symfony.com/doc/current/reference/forms/types/form.html#disabled
If you wish modify dynamically, use form events, example:
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$form = $event->getForm();
$entity = $event->getData();
// if confirmed, disable start_time field
if ($entity->getConfirmed()) {
$config = $form->get('start_time')->getConfig();
$options = $config->getOptions();
// set disabled option to true
$options['disabled'] = true;
// replace origin field
$form->add(
'start_time',
$config->getType()->getName(),
$options
);
}
});
I think I understand your problem now #Starx, I didn't read very carefully at first, and your update helped.
Maybe you need to detach your Entity?
Check this link about (Entities in Session)[http://doctrine-orm.readthedocs.io/projects/doctrine-orm/en/latest/cookbook/entities-in-session.html]. Maybe storing the Entity in a session will work. Detaching as a separate Entity might work, and do a comparison on the detached Entity to your updated Entity.
I found two ways to solve this:
You can retrieve original data of the entity. It returns an array with old data of an entity which can be used to reset the data.
if($form->isSubmitted() && $editForm->isValid()) {
// The form was submitted
if($confirmed === true) { // if the entity was confirmed previously
$oldData = $em->getUnitOfWork()->getOriginalEntityData($entity);
$entity->setStartTime($oldData['start_time']);
$entity->setEndTime($oldData['end_time']);
}
$em->persist($entity);
$em->flush();
}
Using Form Events
The following is a Symfony 3 Solution, try maximkou's answer for Symfony 2.
class EntityType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
// ....
// ....
// Your form fields
$builder->addEventListener(FormEvents::POST_SET_DATA, array($this, 'onPreSetData'));
}
public function onPreSetData(FormEvent $event) {
/** #var YourEntity $entity */
$entity = $event->getData();
$form = $event->getForm();
if($entity instanceof YourEntity) {
if ($entity->getTimesheetEntry()->getTimeApproved() === true) {
$config = $form->get('start_time')->getConfig();
$options = $config->getOptions();
$options['disabled'] = true;
$form->add('start_time', get_class($config->getType()->getInnerType()), $options);
$config = $form->get('end_time')->getConfig();
$options = $config->getOptions();
$options['disabled'] = true;
$form->add('end_time', get_class($config->getType()->getInnerType()), $options);
}
}
}
}
Source

Set collection according to property value and update via AJAX

I have an email object which contains a collection textareas and has a property template. The collection textareas contains the textareas in template, which are set in the controller.
public function editAction($id, Request $request)
{
// Get the email
$email = EmailQuery::create()->findPk($id);
if (!$email)
{
throw $this->createNotFoundException('Unknown email ID.');
}
// If a new template file is selected, the client refreshes the form with ajax
// So let's get the new template value
if (isset($request->request->get('email')['template']))
{
if (isset($request->request->get('email')['update_with_ajax']))
$email->setTemplate($request->request->get('email')['template']);
}
// Get textareas in the email's template
$email->setTextareas($this->get('email.analyzer')->getTextAreas($email->getTemplate()));
// Create form
$form = $this->createForm('email', $email);
// Handle form
$form->handleRequest($request);
if ($form->isValid() && $form->get('save')->isClicked())
{
$form->save();
}
}
This is my email type (which is a service):
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class EmailType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
// Read template files in dir and store names in $templates
// $templates = array();
$builder->add('template', 'choice', array('choices' => $templates);
$builder->add('textareas', 'collection');
$builder->add('save', 'submit');
$builder->add('update_with_ajax', 'submit');
}
}
The problem here is that I want to update the textareas collection via AJAX when the user chooses another template. As you can see I add the collection of templates to the email object before creating the form, I think that's where it all goes wrong and triggers the This form should not contain extra fields error. But what is the right approach?

Add a required form field based on submitted data in Symfony2

I have a form with a status select. If a certain status is selected and the form is submitted it should reload and require an additional field.
I have read Dynamic generation for submitted Forms and almost every other post on the internet and about this topic and tried different event combinations (and got different errors) but I still struggle to make this to work correctly.
This is what I have so far:
FormType
private function addProcessAfterField(FormInterface $form)
{
$form->add('processAfterDate', 'date', array('required' => true));
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('status', 'entity', array(
'class' => 'Acme\Bundle\ApplicationBundle\Entity\LeadStatusCode',
'choices' => $this->allowedTypes
));
$builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event){
$form = $event->getForm();
$data = $event->getData();
if ($data->getStatus()->getId() == LeadStatusCode::INTERESTED_LATER) {
$this->addProcessAfterField($form);
}
});
$builder->get('status')->addEventListener(FormEvents::POST_SUBMIT, function(FormEvent $event){
$data = $event->getData();
if ($data == LeadStatusCode::INTERESTED_LATER && !$event->getForm()->getParent()->getData()->getProcessAfterDate()) {
$this->addProcessAfterField($event->getForm()->getParent());
}
});
$builder->add('comment', 'textarea', array('mapped' => false));
$builder->add('Update', 'submit');
}
Error:
ContextErrorException: Catchable Fatal Error: Argument 1 passed to Proxies\__CG__\Acme\Bundle\ApplicationBundle\Entity\Lead::setProcessAfterDate() must be an instance of DateTime, null given, called in /var/www/application.dev/vendor/symfony/symfony/src/Symfony/Component/PropertyAccess/PropertyAccessor.php on line 360 and defined in /var/www/application.dev/app/cache/dev/doctrine/orm/Proxies/__CG__AcmeBundleApplicationBundleEntityLead.php line 447
As already mentioned I tried different event combinations, one was almost working but then the date was never persisted to the entity so I added the \DateTime type-hint to the setProcessAfterDate() method. I am not sure if I don`t understand the event system correctly or if the error lies somewhere else.
Well, it might not be the best way to solve it, but to make long story short:
$form->handleRequest($request);
if($form->isValid()) // check if the basic version of the form is ok
{
$form = $this->createForm(new XXXXForm(), $form->getData()); // you recreate the form with the data that was submitted, so you rebuild the form with new data
if($form->isValid())
{
// ok
}
// not ok
}
Then inside buildForm function, you base the "required" attribute value of fields based on what you want:
'required' => $this->getCheckRequired($options)
private function getCheckRequired($options) // checks whether field should be required based on data bound to the form
{
if($options && isset($options['data'])
{
switch $options['data']->getStatus():
// whatever
;
}
return false;
}
As I said, this is not the best solution, and it doesn't fix your approach, but rather proposes a different one, but it does the job

pre_set_data and post_submit form event

I have two lists in my form : "State" and "Country". State list depends Country list.
I handle this with form events and according to the doc I need two events : pre_set_data and post_submit. So here my code im my Form type :
$formModifier = function(FormInterface $form, Country $country) use ($options) {
// Query to get all states by countryID
}
1/ Initialisation
$builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event) use ($formModifier)
{
$country = ...;
$formModifier($event->getForm, $country);
}
2/ Post submit handler
$builder->get('country')->addEventListener(FormEvents::POST_SUBMIT, function(FormEvent $event) use ($formModifier) {
$formModifier($event->getForm()->getParent(), $event->getForm()->getData());
}
Everything works fine except in case of form submit pre_set_data is called and post_submit too. My query in $formModifier function is called twice.
Is it the normal behaviour ?
Sure it is normal. Both events occur, so both listeners are activated, calling your function $formModifier... so the function is called twice.

Categories