Example below shows us how to handle form errors manually with a service class. The problem with this approach is, it won't auto-fill form fields if an error occurs so you need to handle it yourself too.


Service definition


# sport/src/Football/FrontendBundle/Resources/config/services.yml
services:
football_frontend.service.form_error:
class: Football\FrontendBundle\Service\FormErrorService

Error service class


# src/Football/FrontendBundle/Service/FormErrorServiceInterface.php
namespace Football\FrontendBundle\Service;

use Symfony\Component\Form\FormInterface;

interface FormErrorServiceInterface
{
public function getErrors(FormInterface $form);
}

# sport/src/Football/FrontendBundle/Service/FormErrorService.php
namespace Football\FrontendBundle\Service;

use Symfony\Component\Form\FormInterface;

class FormErrorService implements FormErrorServiceInterface
{
public function getErrors(FormInterface $form)
{
$errors = array();

// This part gets global form errors (like csrf token error)
foreach ($form->getErrors() as $error) {
$errors[] = $error->getMessage();
}

// This part gets form field errors
foreach ($form->all() as $child) {
if (! $child->isValid()) {
$options = $child->getConfig()->getOptions();
$field = $options['label'] ? $options['label'] : ucwords($child->getName());
// Implode because there can be more than one field errors
$errors[strtolower($field)] = implode('; ', $this->getErrors($child));
}
}

return $errors;
}
}

Controller part


class CountryController extends Controller
{
public function createAction(Request $request)
{
// .....

if ($form->isValid() !== true) {
$errors = $this->get('football_frontend.service.form_error')->getErrors($form);

return $this->render(
'FootballTeamBundle:Create:country.html.twig',
[
'form' => $form->createView(),
'errors' => $errors
]
);
}

// .....
}
}

Twig template part


{% if errors is defined and errors is iterable %}
<div class="global-form-errors">
<ul>
{% for field, message in errors %}
<li>{{ message }}</li>
{% endfor %}
</ul>
</div>
{% endif %}