If you want to separate validation into sections and create a few forms for a single entity then you can use group validation feature of symfony as shown below. We're going to create user profile in two steps by using two forms and a single entity.


User entity


namespace Application\FrontendBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity
* @ORM\Table(name="user")
*/
class User
{
/**
* @ORM\Id
* @ORM\Column(type="smallint")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;

/**
* @ORM\Column(name="name", type="string", length=50)
*/
protected $name;

/**
* @ORM\Column(name="surname", type="string", length=50)
*/
protected $surname;

/**
* @ORM\Column(name="middle_name", type="string", length=50, nullable=true)
*/
protected $middleName;

/**
* @ORM\Column(name="lucky_number", type="string", length=2, options={"fixed"=true})
*/
protected $luckyNumber;

/**
* @ORM\Column(name="favorite_colour", type="string", length=50, nullable=true)
*/
protected $favoriteColour;

/**
* @return integer
*/
public function getId()
{
return $this->id;
}

/**
* @param string $name
* @return User
*/
public function setName($name)
{
$this->name = $name;

return $this;
}

/**
* @return string
*/
public function getName()
{
return $this->name;
}

/**
* @param string $surname
* @return User
*/
public function setSurname($surname)
{
$this->surname = $surname;

return $this;
}

/**
* @return string
*/
public function getSurname()
{
return $this->surname;
}

/**
* @param string $middleName
* @return User
*/
public function setMiddleName($middleName)
{
$this->middleName = $middleName;

return $this;
}

/**
* @return string
*/
public function getMiddleName()
{
return $this->middleName;
}

/**
* @param string $luckyNumber
* @return User
*/
public function setLuckyNumber($luckyNumber)
{
$this->luckyNumber = $luckyNumber;

return $this;
}

/**
* @return string
*/
public function getLuckyNumber()
{
return $this->luckyNumber;
}

/**
* @param string $favoriteColour
* @return User
*/
public function setFavoriteColour($favoriteColour)
{
$this->favoriteColour = $favoriteColour;

return $this;
}

/**
* @return string
*/
public function getFavoriteColour()
{
return $this->favoriteColour;
}
}

ProfileStepOneType.php


namespace Application\FrontendBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\NotBlank;

class ProfileStepOneType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options = [])
{
$builder
->setMethod($options['method'])
->setAction($options['action'])
->add(
'name',
'text',
[
'error_bubbling' => true,
'constraints' => [
new NotBlank(
[
'message' => 'Name is required.',
'groups' => ['step_one']
]
)
]
]
)
->add('middleName', 'text', ['error_bubbling' => true])
->add(
'surname',
'text',
[
'error_bubbling' => true,
'constraints' => [
new NotBlank(
[
'message' => 'Surname is required.',
'groups' => ['step_one']
]
)
]
]
);
}

public function getName()
{
return 'profile_step_one';
}

public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(['data_class' => 'Application\FrontendBundle\Entity\User']);
}
}

ProfileStepTwoType.php


namespace Application\FrontendBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\NotBlank;

class ProfileStepTwoType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options = [])
{
$builder
->setMethod($options['method'])
->setAction($options['action'])
->add(
'luckyNumber',
'text',
[
'error_bubbling' => true,
'constraints' => [
new NotBlank(
[
'message' => 'Lucky Number is required.',
'groups' => ['step_two']
]
)
]
]
)
->add('favoriteColour', 'text', ['error_bubbling' => true]);
}

public function getName()
{
return 'profile_step_two';
}

public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(['data_class' => 'Application\FrontendBundle\Entity\User']);
}
}

Controllers.yml


services:
application_frontend.controller.profile:
class: Application\FrontendBundle\Controller\ProfileController
arguments:
- @router
- @form.factory
- @templating
- @doctrine.orm.entity_manager
- @session

ProfileController.php


namespace Application\FrontendBundle\Controller;

use Application\FrontendBundle\Entity\User;
use Application\FrontendBundle\Form\ProfileStepOneType;
use Application\FrontendBundle\Form\ProfileStepTwoType;
use Doctrine\DBAL\DBALException;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\ORMException;
use Exception;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
use Symfony\Component\Routing\RouterInterface;
use WebDriver\Exception\InvalidRequest;

/**
* @Route("profile", service="application_frontend.controller.profile")
*/
class ProfileController extends Controller
{
const ROUTER_PREFIX = 'application_frontend_profile_';

private $router;
private $formFactory;
private $templating;
private $entityManager;
private $session;

public function __construct(
RouterInterface $router,
FormFactoryInterface $formFactory,
EngineInterface $templating,
EntityManager $entityManager,
SessionInterface $session
) {
$this->router = $router;
$this->formFactory = $formFactory;
$this->templating = $templating;
$this->entityManager = $entityManager;
$this->session = $session;
}

/**
* @Method({"GET"})
* @Route("/step_one")
*/
public function stepOneAction()
{
$form = $this->getForm(
new User(),
new ProfileStepOneType(),
'POST',
$this->router->generate(self::ROUTER_PREFIX . 'stepone'),
['step_one']
);

return $this->getTemplate(
'step_one',
[
'form' => $form->createView(),
'header' => 'STEP ONE'
]
);
}

/**
* @Method({"GET"})
* @Route("/step_two")
*/
public function stepTwoAction()
{
if (!$this->session->has('profile')) {
throw new InvalidRequest('STEP TWO: You must complete Step One first.');
} else {
$profile = $this->session->get('profile');
if (!isset($profile['step_one'])) {
throw new InvalidRequest('STEP TWO: You must complete Step One first.');
}
}

$form = $this->getForm(
new User(),
new ProfileStepTwoType(),
'POST',
$this->router->generate(self::ROUTER_PREFIX . 'steptwo'),
['step_two']
);

return $this->getTemplate(
'step_two',
[
'form' => $form->createView(),
'header' => 'STEP TWO'
]
);
}

/**
* @param Request $request
*
* @Method({"POST"})
* @Route("/step_one")
*
* @return Response
*/
public function stepOneProcessAction(Request $request)
{
if ($request->getMethod() != 'POST') {
throw new MethodNotAllowedException('Step One: Only POST method is allowed.');
}

$form = $this->getForm(
new User(),
new ProfileStepOneType(),
'POST',
$this->router->generate(self::ROUTER_PREFIX . 'stepone'),
['step_one']
);
$form->handleRequest($request);

if (!$form->isSubmitted()) {
throw new BadRequestHttpException('Step One: Form is not submitted.');
}

if ($form->isValid() !== true) {
return $this->getTemplate(
'step_one',
[
'form' => $form->createView(),
'header' => 'STEP ONE',
'errors' => $form->getErrors()
]
);
}

$data = $form->getData();

$this->session->set(
'profile',
[
'step_one' => [
'name' => $data->getName(),
'middle_name' => $data->getMiddleName(),
'surname' => $data->getSurname()
]
]
);

return $this->redirect($this->router->generate(self::ROUTER_PREFIX . 'steptwo'));
}

/**
* @param Request $request
*
* @Method({"POST"})
* @Route("/step_two")
*
* @return Response
* @throws Exception
*/
public function stepTwoProcessAction(Request $request)
{
if ($request->getMethod() != 'POST') {
throw new MethodNotAllowedException('Step Two: Only POST method is allowed.');
}

$form = $this->getForm(
new User(),
new ProfileStepTwoType(),
'POST',
$this->router->generate(self::ROUTER_PREFIX . 'steptwo'),
['step_two']
);
$form->handleRequest($request);

if (!$form->isSubmitted()) {
throw new BadRequestHttpException('Step Two: Form is not submitted.');
}

if ($form->isValid() !== true) {
return $this->getTemplate(
'step_two',
[
'form' => $form->createView(),
'header' => 'STEP TWO',
'errors' => $form->getErrors()
]
);
}

try {
$data = $form->getData();

$luckyNumber = $data->getLuckyNumber();
$favoriteColour = $data->getFavoriteColour();
$profile = $this->session->get('profile');
$this->session->remove('profile');

$user = new User();
$user->setName($profile['step_one']['name']);
$user->setMiddleName($profile['step_one']['middle_name']);
$user->setSurname($profile['step_one']['surname']);
$user->setLuckyNumber($luckyNumber);
$user->setFavoriteColour($favoriteColour);

$this->entityManager->persist($user);
$this->entityManager->flush();
} catch (DBALException $e) {
$message = sprintf('DBALException [%s]: %s', $e->getCode(), $e->getMessage());
} catch (ORMException $e) {
$message = sprintf('ORMException [%s]: %s', $e->getCode(), $e->getMessage());
} catch (Exception $e) {
$message = sprintf('Exception [%s]: %s', $e->getCode(), $e->getMessage());
}

if (isset($message)) {
throw new Exception($message);
}

return $this->redirect($this->router->generate(self::ROUTER_PREFIX . 'stepone'));
}

/**
* @param object $object
* @param object $form
* @param string $method
* @param string $action
* @param array $validationGroups
*
* @return Form
*/
private function getForm($object, $form, $method, $action, array $validationGroups = [])
{
return $this->formFactory->create(
$form,
$object,
[
'method' => $method,
'action' => $action,
'validation_groups' => $validationGroups
]
);
}

/**
* @param string $template
* @param array $parameters
*
* @return Response
*/
private function getTemplate($template, array $parameters = [])
{
return $this->templating->renderResponse(
sprintf('ApplicationFrontendBundle:Profile:%s.html.twig', $template),
$parameters
);
}
}

Step_one.html.twig


{% block body %}
{% spaceless %}
{{ parent() }}
{{ form_start(form, { attr: {novalidate: 'novalidate'} }) }}
{% if form_errors(form) != '' %}

{{ form_errors(form) }}
{% endif %}

<p>NAME: {{ form_widget(form.name) }}</p>
<p>MIDDLE NAME: {{ form_widget(form.middleName) }}</p>
<p>SURNAME: {{ form_widget(form.surname) }}</p>

<p><button name="button">Submit</button></p>
{{ form_end(form) }}
{% endspaceless %}
{% endblock %}

Step_two.html.twig


{% block body %}
{% spaceless %}
{{ parent() }}
{{ form_start(form, { attr: {novalidate: 'novalidate'} }) }}
{% if form_errors(form) != '' %}

{{ form_errors(form) }}
{% endif %}

<p>LUCKY NUMBER: {{ form_widget(form.luckyNumber) }}</p>
<p>FAVORITE COLOUR: {{ form_widget(form.favoriteColour) }}</p>

<p><button name="button">Submit</button></p>
{{ form_end(form) }}
{% endspaceless %}
{% endblock %}

Forms