Hello everyone!

We have been investing plenty of personal time and energy for many years to share our knowledge with you all. However, we now need your help to keep this blog running. All you have to do is just click one of the adverts on the site, otherwise it will sadly be taken down due to hosting etc. costs. Thank you.

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