This repository has been archived on 2023-08-20. You can view files and clone it, but cannot push or open issues or pull requests.
Go to file
Nicolas Grekas 549c097251 feature #40799 [FrameworkBundle] Add AbstractController::handleForm() helper (dunglas)
This PR was squashed before being merged into the 5.3-dev branch.

Discussion
----------

[FrameworkBundle] Add AbstractController::handleForm() helper

| Q             | A
| ------------- | ---
| Branch?       | 5.x
| Bug fix?      | no
| New feature?  | yes <!-- please update src/**/CHANGELOG.md files -->
| Deprecations? | no <!-- please update UPGRADE-*.md and src/**/CHANGELOG.md files -->
| Tickets       | n/a
| License       | MIT
| Doc PR        | https://github.com/symfony/symfony-docs/pull/15217

Some libraries such as Turbo require to strictly follow the HTTP specification (and especially to use proper status codes) to deal with forms.

In https://github.com/symfony/symfony/pull/39843, I introduced a new `renderForm()` helper for this purpose. But I'm not very satisfied by it. The current approach has several problems:

1. It calls `$form->isValid()` two times, which may hurt performance
2. It sets the proper status code in case of validation error (422), but not for the redirection when the entity is created or updated (306). The user must do this manually (and so be aware of these HTTP subtleties).
3. It hides the verbosity of the Form component a bit, but I'm a sure that we can reduce it more

This PR proposes an alternative helper, `handleForm()`, which handles automatically 80% of the use cases, provide an extension point for the other 20%, and can also serve as a quick example for users to handle form in a custom way (by control-clicking on the function to see the code and copy/paste/adapt it).

* if the form is not submitted, the Twig template passed in $view is rendered and a 200 HTTP status code is set
* if the form is submitted but invalid, the Twig template passed in $view is rendered and 422 HTTP status code is set
* if the form is submitted and valid, the entity is saved (only if it is managed by Doctrine ORM), a 306 HTTP status code is set and the Location HTTP header is set to the value of $redirectUrl

Before (standard case):

```php
    #[Route('/{id}/edit', name: 'conference_edit', methods: ['GET', 'POST'])]
    public function edit(Request $request, Conference $conference): Response
    {
        $form = $this->createForm(ConferenceType::class, $conference);
        $form->handleRequest($request);

        $submitted = $form->isSubmitted();
        $valid = $submitted && $form->isValid();

        if ($valid) {
            $this->getDoctrine()->getManager()->flush();

            return $this->redirectToRoute('conference_index', [], Response::HTTP_SEE_OTHER);
        }

        $response = $this->render('conference/edit.html.twig', [
            'conference' => $conference,
            'form' => $form->createView(),
        ]);
        if ($submitted && !$valid) {
            $response->setStatusCode(Response::HTTP_UNPROCESSABLE_ENTITY);
        }

        return $response;
    }
```

With the new helper:

```php
    #[Route('/{id}/edit', name: 'conference_edit', methods: ['GET', 'POST'])]
    public function edit(Request $request, Conference $conference): Response
    {
        $form = $this->createForm(ConferenceType::class, $conference);
        return $this->handleForm(
            $request,
            $form,
            view: 'conference/edit.html.twig',
            redirectUrl: $this->generateUrl('conference_index')
        );
    }
```

Before (more advanced use case):

```php
    #[Route('/{id}/edit', name: 'conference_edit', methods: ['GET', 'POST'])]
    public function edit(Request $request, Conference $conference, HubInterface $hub): Response
    {
        $form = $this->createForm(ConferenceType::class, $conference);
        $form->handleRequest($request);

        $submitted = $form->isSubmitted();
        $valid = $submitted && $form->isValid();

        if ($valid) {
            $this->getDoctrine()->getManager()->flush();

            $hub->publish(
                new Update(
                    'conference:'.$conference->getId(),
                    $this->renderView('conference/edit.stream.html.twig', ['conference' => $conference])
                )
            );

            return $this->redirectToRoute('conference_index', [], Response::HTTP_SEE_OTHER);
        }

        $response = $this->render('conference/edit.html.twig', [
            'conference' => $conference,
            'form' => $form->createView(),
        ]);
        if ($submitted && !$valid) {
            $response->setStatusCode(Response::HTTP_UNPROCESSABLE_ENTITY);
        }

        return $response;
    }
```

With the new helper (more advanced case):

```php
    #[Route('/{id}/edit', name: 'conference_edit', methods: ['GET', 'POST'])]
    public function edit(Request $request, Conference $conference, HubInterface $hub): Response
    {
        $form = $this->createForm(ConferenceType::class, $conference);
        $response = $this->handleForm(
            $request,
            $form,
            view: 'conference/edit.html.twig',
            redirectUrl: $this->generateUrl('conference_index')
        );

        if ($response->isRedirection()) {
            $hub->publish(
                new Update(
                    'conference:' . $conference->getId(),
                    $this->renderView('conference/edit.stream.html.twig', ['conference' => $conference])
                )
            );
        }

        return $response;
    }
```

This also works without named parameters. I also considered passing a callback to be executed on success, but I'm happier with the current solution.

WDYT?

TODO:

* [x] update tests

Commits
-------

5228546066 [FrameworkBundle] Add AbstractController::handleForm() helper
2021-04-16 18:31:06 +02:00
.github [Intl] Switch from json to php resources 2021-04-13 21:38:10 +02:00
src/Symfony feature #40799 [FrameworkBundle] Add AbstractController::handleForm() helper (dunglas) 2021-04-16 18:31:06 +02:00
.appveyor.yml Merge branch '4.4' into 5.2 2021-02-16 11:13:48 +01:00
.editorconfig Update .editorconfig 2018-09-06 16:22:56 +02:00
.gitattributes [Runtime] a new component to decouple applications from global state 2021-03-09 21:44:54 +01:00
.gitignore Run the phpunit-bridge from a PR 2019-08-02 17:46:19 +02:00
.php_cs.dist Merge branch '5.1' into 5.2 2021-01-19 22:00:40 +01:00
.travis.yml Merge branch '5.2' into 5.x 2021-03-23 22:05:31 +01:00
CHANGELOG-5.0.md Merge branch '5.0' into 5.1 2020-06-15 13:50:15 +02:00
CHANGELOG-5.1.md Update CHANGELOG for 5.1.10 2020-12-18 14:43:18 +01:00
CHANGELOG-5.2.md Update CHANGELOG for 5.2.6 2021-03-29 07:16:50 +02:00
CODE_OF_CONDUCT.md Added the Code of Conduct file 2018-10-10 03:13:30 -07:00
composer.json Drop support of mercure:^0.4 2021-04-06 11:32:33 +02:00
CONTRIBUTING.md Mention the community review guide 2016-12-18 22:02:35 +01:00
CONTRIBUTORS.md Update CONTRIBUTORS for 4.4.21 2021-03-29 07:10:41 +02:00
LICENSE Bump license year 2021-01-01 10:24:35 +01:00
link Merge branch '5.1' into 5.2 2020-12-10 20:16:15 +01:00
phpunit Merge branch '4.4' into 5.2 2021-02-11 09:21:20 +01:00
phpunit.xml.dist Merge branch '4.4' into 5.1 2020-11-16 16:58:32 +01:00
psalm.xml Adding a Github action to run Psalm 2021-02-25 17:18:18 +01:00
README.md Update README.md 2021-04-04 16:42:37 +02:00
UPGRADE-5.0.md Merge branch '4.4' into 5.1 2020-12-10 18:44:54 +01:00
UPGRADE-5.1.md Update UPGRADE-5.1.md 2020-09-07 01:58:27 +02:00
UPGRADE-5.2.md [HttpFoundation] Deprecate BinaryFileResponse::create(). 2020-11-20 16:47:02 +01:00
UPGRADE-5.3.md [Security] Fix UsageTrackingTokenStorage outside the request cycle 2021-04-13 08:27:14 +02:00
UPGRADE-6.0.md [Console] Deprecate Helper::strlen() for width() and length() 2021-04-09 11:54:15 +02:00

Symfony is a PHP framework for web and console applications and a set of reusable PHP components. Symfony is used by thousands of web applications (including BlaBlaCar.com and Spotify.com) and most of the popular PHP projects (including Drupal and Magento).

Installation

Documentation

Community

Contributing

Symfony is an Open Source, community-driven project with thousands of contributors. Join them contributing code or contributing documentation.

Security Issues

If you discover a security vulnerability within Symfony, please follow our disclosure procedure.

About Us

Symfony development is sponsored by SensioLabs, led by the Symfony Core Team and supported by Symfony contributors.