8000 Simplify the form submission · Issue #27638 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content
8000

Simplify the form submission #27638

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
ismail1432 opened this issue Jun 19, 2018 · 26 comments
Closed

Simplify the form submission #27638

ismail1432 opened this issue Jun 19, 2018 · 26 comments
Labels
DX DX = Developer eXperience (anything that improves the experience of using Symfony) Feature Form

Comments

@ismail1432
Copy link
Contributor
ismail1432 commented Jun 19, 2018

Description
At the moment when you want to submit a form in a controller most of the time you should call 3 methods handleRequest($request) , isSubmitted() and isValid() see documentation and if you call isValid() without calling isSubmitted() before it will throw an Exception

I know it's little magic but the simple idea is to aggregate the 3 methods in 1.
the method will handle the request, check if the form isSubmitted and if it's valid.

Example

public function add(Request $request)
{
//create $form
//do some stuff
if($form->isCorrect($request)) {
// Do some stuff with $form
}

The method name is a simple example. I'am ready to work on if you're OK
I think the community have already spoken about that, but I'm not sure, WDYT ?

@BoShurik
Copy link
Contributor

How to separate not submitted forms with not valid in that case?

@ismail1432
Copy link
Contributor Author

We not remove existing method just create a new one « helper »

@nicolas-grekas
Copy link
Member

What about providing a callback to a form, that would be called when it is submitted, valid, etc.?

@xabbuh xabbuh added Form Feature DX DX = Developer eXperience (anything that improves the experience of using Symfony) labels Jun 19, 2018
@HeahDude
Copy link
Contributor

Like a listener to POST_SUBMIT? Not worth it IMHO.

@xabbuh
Copy link
Member
xabbuh commented Jun 19, 2018

Not sure if it would be worth it to add some kind of helper method to the ControllerTrait in the FrameworkBundle (like below), but I don't see it as part of the Form component.

public function handleFormSubmissionRequest(FormInterface $form, Request $request, callable $onSuccess = null, callable $onFailure = null): void
{
    $form->handleRequest($request);

    if ($form->isSubmitted()) {
        if (null !== $onSuccess && $form->isValid()) {
            $onSuccess($form);
        }

        if (null !== $onFailure && !$form->isValid()) {
            $onFailure($form);
        }
    }
}

@javiereguiluz
Copy link
Member

It's always interesting to see how others solved the same problems you have. So here is for example how do they process forms in Django:

def get_name(request):
    # method is POST -> process the submitted form
    if request.method == 'POST':
        form = NameForm(request.POST)

        if form.is_valid():
            # process the data in form.cleaned_data as required...

            return HttpResponseRedirect('/thanks/')

    # method is GET -> display the initial form
    else:
        form = NameForm()

    return render(request, 'name.html', {'form': form})

@HeahDude
Copy link
Contributor

I'm not a fan of using callbacks, we will end up with anonymous functions in controllers instead of an explicit API.

But I do like the idea of using the controller trait to get this helper, what about:

// trait
public function isFormValidForRequest(FromInterface $form, Request $request): bool
{
    return $form->handleRequest($request)->isSubmitted() && $form->isValid();
}

// controller
public function action(Request $request): Response
{
    $form = $this->createForm(MyType::class);

    if ($this-> isFormValidForRequest($form, $request)) {
        // do stuff and redirect
    }
    
    // render
}

@nicolas-grekas
Copy link
Member
nicolas-grekas commented Jun 19, 2018

Something like that @xabbuh yes. By doing this kind of logic in the Cache component, we made it much easier to use, and in fact it became much more than a helper as this is now backed by a first class interface and interesting features have been built on top. Maybe applicable here also?

@ghost
Copy link
ghost commented Jun 19, 2018

Based on the idea of @xabbuh:

public function handleForm(string $formType, callable $preSubmit, callable $onSuccess)
{
    $form = $this->createForm($formType);

    // Try to get the $request automatically (autowiring, container...)
    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {
        return $onSuccess($form);
    }

    return $preSubmit($form);
}

In controller, there is only:

public function myAction()
{
    return $this->handleForm(MyFormType::class, function (FormInterface $form) {
        return $this->render('form.html.twig', ['form' => $form->createView()]);
    }, function (FormInterface $form) {
        return $this->redirectToRoute('success');
    });
}

@javiereguiluz
Copy link
Member

I'm afraid that I agree with @HeahDude ... the proposals don't look much better than the current situation (and considering that it would obsolete all our docs, tutorials, screencasts, etc.) I'm not sure it's worth it.

@Pierstoval
Copy link
Contributor

Why not simply $form->isSubmittedAndValid($request);? Pretty straightforward, and can call the three methods at once (handleRequest($request), isSubmitted() and isValid())?

@jvasseur
Copy link
Contributor
jvasseur commented Jun 20, 2018

The problem I see with isFormValidForRequest is that it's both a mutator (via handleRequest) and an accessor via isSubmitted and isValid. People that doesn't know what is under this method could try to call it multiple times (and get an error because the form is already submitted).

I'm not sure if there is really something to do here but if there was I would be more in favor of combining the createForm and handleRequest into one method (you rarely have something to do between the two calls) and maybe isSubmitted and isValid in another one.

@HeahDude
Copy link
Contributor

Why not simply $form->isSubmittedAndValid($request);?

@Pierstoval because it would make the shortcut be a form interface contract, keeping it in the framework bundle trait is better IMO, since the request object is not tied to the form.

@ismail1432
Copy link
Contributor Author

I am happy to see all proposals, IMO the @HeahDude solution (add a simple method in the ControllerTrait ) is the good one

@Pierstoval
Copy link
Contributor
Pierstoval commented Jun 20, 2018

Then $this->formIsSubmittedAndValid($request) in the ControllerTrait seems to be the best option then 👍

@lyrixx
Copy link
Member
lyrixx commented Jun 20, 2018

yes yes yes, a shortcut in the trait. Thus the PR already exist:
#24576

@ismail1432
Copy link
Contributor Author
ismail1432 commented Jun 20, 2018

Good point @lyrixx !
I missed that PR, I hope that we will merge your work !

@linaori
Copy link
Contributor
linaori commented Jun 20, 2018

A lot of points from this discussion are leading towards the form handler bundle I've worked on, perhaps it's worth checking it out? https://github.com/hostnet/form-handler-bundle

It lets you create thin controllers by extracting any logic to the handler, which can have an "onSuccess" flow and an "onFailure" flow (configurable).

// example taken from the readme
class YourController extends Controller
{
    public function formAction(Request $request, MyEntityUser $user)
    {
        $handler = $this->get('hostnet.form_handler.factory')->create(MyFormHandler::class);

        if (($response = $handler->handle($request, new MyFormData())) instanceof RedirectResponse) {
            return $response;
        }

        // regular or in-valid flow
        return $this->render->renderView('/your/form.html.twig', [
            'form' => $handler->getForm()->createView()
        ]);
    }
}

@lyrixx
Copy link
Member
lyrixx commented Jun 20, 2018

@iltar In the readme, there are no mention of binding a form to an existing object. Is it intended?
To me it's really weird... I except something like

$handler = $this->get('hostnet.form_handler.factory')->create(MyFormHandler::class, $myObject);

@linaori
Copy link
Contributor
linaori commented Jun 20, 2018

@lyrixx your form is bound to a specific object, you configure which form this is in your handler. At the moment of creation, you might not yet (want to) have your data object: https://github.com/hostnet/form-handler-component/blob/87cbc8444ec20a0c66bc8e05e79a102fb7cf4b0d/src/FormHandler/Handler.php#L92-L104

I guess it could be possible that way, but it was written like this because you handle a specific data set rather than just handling a handler.

Edit:
You should always bind to an existing object, but it's possible without. A proper constructor in your DTO is recommended to set default values.

@HeahDude
Copy link
Contributor

@lyrixx I missed that one too, what about reopening? :)

@logarytm
Copy link
logarytm commented Aug 3, 2018

On a related note, how about reporting an error in isSubmitted() if handleRequest() hasn't been called? I don't forget to check isSubmitted() before isValid(), but I frequently find myself not calling handleRequest(), which fails silently. I suspect there are some edge cases lurking here, though, and I am aware that could be a BC break.

@lyrixx
Copy link
Member
lyrixx commented Nov 14, 2018

I rebased #24576 and addressed your comments. No more error prone and super easy.

@ismail1432
Copy link
Contributor Author

The job was done #24576 I close this issue thanks @lyrixx

@ismail1432 ismail1432 reopened this Aug 16, 2019
@ismail1432
Copy link
Contributor Author

I reopen the issue because the code was revert in 2be1987

@xabbuh
Copy link
Member
xabbuh commented Jan 1, 2020

This issue was opened more than a year ago. We had a lot of ideas, but none of them was convincing enough. Therefore I suggest that we close here as we weren't able to agree on a solution. We could still reconsider if someone comes up with a great new idea that receives enough support.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
DX DX = Developer eXperience (anything that improves the experience of using Symfony) Feature Form
Projects
None yet
Development

No branches or pull requests

0