Symfony keep form values
last modified July 5, 2020
Symfony keep form values tutorial shows how to keep form values after form submission after the submission of the form fails. In this tutorial, we do the traditional form submission; we do not use form builders.
Symfony
Symfony is a set of reusable PHP components and a PHP framework for web projects. Symfony was published as free software in 2005. The original author of Symfony is Fabien Potencier. Symfony was heavily inspired by the Spring Framework.
Keeping form values
When the form is submitted by the user, it is validated by the application. When the validation fails, the application redirects the user back to the form, displaying the validation errors. It is a good practice to keep the already entered values in the form.
Symfony keep form values example
In the example, we have a simple form with two fields: name and email.
After the form is submitted, we check for CSRF protection and validate
the input values with Symfony's Validator
. We store the entered
values into the session to retrieve them back when the submission fails.
Setting up the application
We start with setting up the application with composer.
$ composer create-project symfony\skeleton formkeepvals $ cd formkeepvals
We create a new Symfony skeleton project and go the the newly created project directory.
$ composer require twig annot validator
We install three basic Symfony packages: twig
, annot
, and
validator
. The packages may have aliases. For instance, the
symfony/validator
has two aliases: validator
and validation
.
Check the Symfony Recipes Server for more details.
$ composer require symfony/security-csrf $ composer require symfony/monolog-bundle
The security-csrf
package is needed against cross-site request
forgeries and monolog-bundle
for logging.
$ composer require symfony/property-access
We install the PropertyAccess
component, which is used for
convenient reading and writing of properties/keys of objects and arrays.
$ composer require maker server --dev
We install the maker component and the development server.
$ php bin/console make:controller HomeController
We create a HomeController
. The controller sends a form
to the client.
<?php namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Routing\Annotation\Route; class HomeController extends AbstractController { /** * @Route("/home", name="home") */ public function index() { return $this->render('home/index.html.twig'); } }
This is a simple controller that sends a view containing the web form to the user.
$ php bin/console make:controller MessageController
We create a MessageController
that responds to the form submission.
<?php namespace App\Controller; use App\Service\ValidationService; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; class MessageController extends AbstractController { /** * @Route("/message", name="message") */ public function index(Request $request, ValidationService $validator) { $token = $request->get("token"); $valid = $validator->validateToken($token); if (!$valid) { return new Response("Operation not allowed", Response::HTTP_BAD_REQUEST, ['content-type' => 'text/plain']); } $name = $request->request->get("name"); $email = $request->request->get("email"); $input = ['name' => $name, 'email' => $email]; $errorMessages = $validator->validateInput($input); if (count($errorMessages) > 0) { $session = $request->getSession(); $session->set('name', $name); $session->set('email', $email); foreach ($errorMessages as $key => $val) { $this->addFlash($key, $val); } return $this->redirectToRoute('home'); } else { return new Response("User saved", Response::HTTP_OK, ['content-type' => 'text/plain']); } } }
In the MessageController
, we check the CSRF token, validate the form
input values, and send a response back to the client.
public function index(Request $request, ValidationService $validator) {
The validation is delegated to the ValidationService
, which is injected
into the method.
$token = $request->get("token"); $valid = $validator->validateToken($token); if (!$valid) { return new Response("Operation not allowed", Response::HTTP_BAD_REQUEST, ['content-type' => 'text/plain']); }
We get the CSRF token and validate it. If the validation fails, we send a response with the error message back to the client.
$name = $request->request->get("name"); $email = $request->request->get("email"); $input = ['name' => $name, 'email' => $email]; $errorMessages = $validator->validateInput($input);
We retrieve the form input values and validate them with the validation service. The validation service returns error messages if it fails.
if (count($errorMessages) > 0) { $session = $request->getSession(); $session->set('name', $name); $session->set('email', $email); ...
If there are some error messages, we add the input values to the session so that we can retrieve them after redirection.
foreach ($errorMessages as $key => $val) { $this->addFlash($key, $val); }
We add the messages to the flash bag; the flash bag is used for storing temporary messages such as our validation messages.
return $this->redirectToRoute('home');
We redirect back to the form with redirectToRoute()
.
<?php namespace App\Service; use Psr\Log\LoggerInterface; use Symfony\Component\Security\Csrf\CsrfToken; use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\PropertyAccess\PropertyAccessorInterface; class ValidationService { private $tokenManager; private $validator; private $accessor; private $logger; public function __construct(CsrfTokenManagerInterface $tokenManager, ValidatorInterface $validator, PropertyAccessorInterface $accessor, LoggerInterface $logger) { $this->tokenManager = $tokenManager; $this->validator = $validator; $this->accessor = $accessor; $this->logger = $logger; } public function validateToken($token): bool { $csrf_token = new CsrfToken('myform', $token); $isValid = $this->tokenManager->isTokenValid($csrf_token); if (!$isValid) { $this->logger->error("CSRF failure"); } return $isValid; } public function validateInput(array $input): array { $constraints = new Assert\Collection([ 'name' => [new Assert\Length(['min' => 2]), new Assert\NotBlank], 'email' => [new Assert\Email, new Assert\NotBlank], ]); $violations = $this->validator->validate($input, $constraints); if (count($violations) > 0) { $this->logger->info("Validation failed"); $messages = []; foreach ($violations as $violation) { $this->accessor->setValue($messages, $violation->getPropertyPath(), $violation->getMessage()); } return $messages; } else { return []; } } }
The ValidationService
checks the CSRF token and validates the
input.
public function __construct(CsrfTokenManagerInterface $tokenManager, ValidatorInterface $validator, PropertyAccessorInterface $accessor, LoggerInterface $logger) { $this->tokenManager = $tokenManager; $this->validator = $validator; $this->accessor = $accessor; $this->logger = $logger; }
We inject four objects in the constructor: the token manager, the validator, the property accessor and the logger.
public function validateToken($token): bool { $csrf_token = new CsrfToken('myform', $token); $isValid = $this->tokenManager->isTokenValid($csrf_token); if (!$isValid) { $this->logger->error("CSRF failure"); } return $isValid; }
This code validates the CSRF token using the token manager.
$constraints = new Assert\Collection([ 'name' => [new Assert\Length(['min' => 2]), new Assert\NotBlank], 'email' => [new Assert\Email, new Assert\NotBlank], ]);
These are constraints for validating the form input.
$violations = $this->validator->validate($input, $constraints);
With the validator we validate the form input values.
if (count($violations) > 0) { $this->logger->info("Validation failed"); $messages = []; foreach ($violations as $violation) { $this->accessor->setValue($messages, $violation->getPropertyPath(), $violation->getMessage()); } return $messages; } else { return []; }
If there are some violations, we log the failure and build validation error messages. To build the messages, we utilize the Symfony property accessor. If there are no violations, we return an empty array.
{% extends 'base.html.twig' %} {% block title %}Home page{% endblock %} {% block stylesheets %} <style> .topmargin { margin-top: 10px; } </style> {% endblock %} {% block body %} <section class="ui container topmargin"> <form class="ui form" action="message" method="post"> <input type="hidden" name="token" value="{{ csrf_token('myform') }}" /> {% for msg in app.flashes('name') %} <div class="ui small red message"> {{ msg }} </div> {% endfor %} <div class="field"> <label>Name:</label> <input type="text" name="name" value="{{app.session.get('name')}}"> </div> {% for msg in app.flashes('email') %} <div class="ui small red message"> {{ msg }} </div> {% endfor %} <div class="field"> <label>Email</label> <input type="text" name="email" , value="{{app.session.get('email')}}"> </div> <button class="ui button" type="submit">Send</button> </form> </section> {% endblock %}
The home page has a form. The form contains two fields: name and email.
<input type="hidden" name="token" value="{{ csrf_token('myform') }}" />
It also contains a hidden field to guard against cross-site request forgeries.
{% for msg in app.flashes('name') %} <div class="ui small red message"> {{ msg }} </div> {% endfor %}
If there are some error messages in the flash bag, we display them.
<input type="text" name="name" value="{{app.session.get('name')}}">
The input tag retrieves its value from the session, if there is one. This is useful after redirections to the form.
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>{% block title %}Welcome!{% endblock %}</title> <link href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.css" rel="stylesheet"> {% block stylesheets %}{% endblock %} </head> <body> {% block body %}{% endblock %} <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.3.1/semantic.min.js"></script> {% block javascripts %}{% endblock %} </body> </html>
This is the base Twig template. It contains the Semantic UI CSS framework.
In this tutorial we have validated a simple form in a Symfony application.
List all Symfony tutorials.