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.

In this example we're going to see how to define form validation annotations in model classes rather than entities or formtypes. Unfortunately uniqueConstraints and UniqueEntity constraint errors cannot be handled in model validations so it is up to you to handle them manually in controller like shown below.


Entity


Assume that this is the child entity of one-to-many relationship and Country is the parent.


namespace Football\FrontendBundle\Entity;

use DateTime;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;

/**
* @ORM\Entity
* @ORM\Table(
* name="league",
* uniqueConstraints={@ORM\UniqueConstraint(columns={"name", "country_id"})}
* )
* @UniqueEntity(
* fields={"name","country"},
* message="League for given country already exists in database."
* )
*/
class League
{
/**
* @ORM\Id
* @ORM\Column(type="smallint")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;

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

/**
* @ORM\ManyToOne(
* targetEntity="Country",
* inversedBy="league"
* )
* @ORM\JoinColumn(
* name="country_id",
* referencedColumnName="id",
* onDelete="CASCADE",
* nullable=false
* )
*/
protected $country;
}

LeagueModel class


namespace Football\FrontendBundle\Model;

use Symfony\Component\Validator\Constraints as Assert;

class LeagueModel
{
/**
* @var string
*
* @Assert\NotBlank(message="Name is required.")
* @Assert\Length(
* max=100,
* maxMessage="Name cannot be longer than {{ limit }} characters."
* )
*/
protected $name;

/**
* @var string
*
* @Assert\NotBlank(message="Country is required.")
*/
protected $country;

public function getName()
{
return $this->name;
}

public function setName($name)
{
$this->name = $name;

return $this;
}

public function getCountry()
{
return $this->country;
}

public function setCountry($country)
{
$this->country = $country;

return $this;
}
}

FormType


namespace Football\FrontendBundle\Form\Type;

use Doctrine\ORM\EntityRepository;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class LeagueType extends AbstractType
{
private $country;

public function __construct()
{
$this->country = [
'error_bubbling' => true,
'class' => 'FootballFrontendBundle:Country',
'property' => 'name',
'multiple' => false,
'expanded' => false,
'required' => false,
'empty_value' => '',
'query_builder' => function (EntityRepository $repo)
{
return $repo->createQueryBuilder('c')->orderBy('c.name', 'ASC');
}
];
}

public function buildForm(FormBuilderInterface $builder, array $options = [])
{
$builder
->setMethod($options['method'])
->setAction($options['action'])
->add('name', 'text', ['error_bubbling' => true])
->add('country', 'entity', $this->country);
}

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

public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(
['data_class' => 'Football\FrontendBundle\Model\LeagueModel']
);
}
}

Controller


/**
* @Route("/league")
*/
class LeagueController extends Controller
{
const ROUTER_PREFIX = 'football_frontend_league_';

/**
* Create page.
*
* @Route("/create")
* @Method({"GET"})
*
* @return Response
*/
public function createAction()
{
$form = $this->getForm(
new LeagueModel(),
'POST',
$this->generateUrl(self::ROUTER_PREFIX . 'create')
);

return $this->getFormView('create', ['form' => $form->createView()]);
}

/**
* Creates processing.
*
* @param Request $request
*
* @Route("/create")
* @Method({"POST"})
*
* @return RedirectResponse|Response
* @throws LeagueException
*/
public function createProcessAction(Request $request)
{
if ($request->getMethod() != 'POST') {
throw new LeagueException('League create: only POST method is allowed.');
}

$form = $this->getForm(
new LeagueModel(),
'POST',
$this->generateUrl(self::ROUTER_PREFIX . 'create')
);
$form->handleRequest($request);

if (!$form->isSubmitted()) {
throw new LeagueException('League create: form is not submitted.');
}

if ($form->isValid() !== true) {
return $this->getFormView('create', ['form' => $form->createView()]);
}

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

$league = new League();
$league->setName($data->getName());
$league->setCountry($data->getCountry());

$em = $this->getDoctrine()->getManager();
$em->persist($league);
$em->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 LeagueException($message);
}

return $this->redirect($this->generateUrl(self::ROUTER_PREFIX . 'list'));
}

/**
* Creates form object.
*
* @param LeagueModel $league
* @param string $method
* @param string $action
*
* @return Form
*/
private function getForm(LeagueModel $league, $method, $action)
{
return $this->createForm(
new LeagueType(),
$league,
[
'method' => $method,
'action' => $action
]
);
}

/**
* Creates webform.
*
* @param string $template
* @param array $parameters
*
* @return Response
*/
private function getFormView($template, array $parameters = [])
{
return $this->render(
sprintf('FootballFrontendBundle:League:%s.html.twig', $template),
$parameters
);
}
}

Twig template


{% extends 'FootballFrontendBundle:League:index.html.twig' %}

{% block body %}
{% spaceless %}
{{ parent() }}
LEAGUE - Create
<hr />
{{ form_start(form, {attr: {novalidate: 'novalidate'}}) }}
{% if form_errors(form) != '' %}

{{ form_errors(form) }}

{% endif %}

<p>NAME: {{ form_widget(form.name) }}</p>
<p>COUNTRY: {{ form_widget(form.country) }}</p>

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