I don't understand why some constraints does not insert name of property in error message after validation. I have this entity class:
<?php
namespace AC\OperaBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use AC\UserBundle\Entity\Utente;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Class Episodio
* @package AC\OperaBundle\Entity
* @ORM\Entity(repositoryClass="AC\OperaBundle\Repository\EpisodioRepository")
* * @ORM\Table(
* name="ac_Episodio",
* uniqueConstraints={@ORM\UniqueConstraint(name="unique_idx", columns={"opera", "numero_episodio", "extra"})},
* indexes={ @ORM\Index(name="opera_idx", columns={"opera"}),
* @ORM\Index(name="numero_episodio_idx", columns={"numero_episodio"}),
* @ORM\Index(name="extra_idx", columns={"extra"}),
* @ORM\Index(name="attivo_idx", columns={"attivo"}) })
*/
class Episodio {
/**
* @var int
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @var Opera
*
* @ORM\ManyToOne(targetEntity="AC\OperaBundle\Entity\Opera", inversedBy="episodi")
* @ORM\JoinColumn(name="opera", referencedColumnName="id", nullable=false)
*/
protected $opera;
/**
* @var string
*
* @ORM\Column(name="numero_episodio", type="string", length=5, nullable=false)
*/
protected $numero_episodio;
/**
* @var string
*
* @ORM\Column(name="extra", type="string", length=30, nullable=false)
*/
protected $extra;
/**
* @var string
*
* @ORM\Column(name="titolo_italiano", type="string", length=150, nullable=false)
* @Assert\NotBlank()
*/
protected $titolo_italiano;
/**
* @var string
*
* @ORM\Column(name="titolo_originale", type="string", length=150, nullable=false)
* @Assert\NotBlank()
*/
protected $titolo_originale;
/**
* @var \DateTime
*
* @ORM\Column(name="data_ita", type="date", nullable=true)
* @Assert\Date()
*/
protected $data_ita;
/**
* @var \DateTime
*
* @ORM\Column(name="data_jap", type="date", nullable=true)
* @Assert\Date()
*/
protected $data_jap;
/**
* @var int
*
* @ORM\Column(name="durata", type="smallint", nullable=false, options={"default" = 0})
*/
protected $durata;
/**
* @var string
*
* @ORM\Column(name="trama", type="text", nullable=false)
*/
protected $trama;
/**
* @var int
*
* @ORM\Column(name="ordine", type="smallint", nullable=false, options={"default" = 0})
*/
protected $ordine;
/**
* @var int
*
* @ORM\Column(name="promosso", type="integer", nullable=false, options={"default" = 0})
*/
protected $promosso;
/**
* @var int
*
* @ORM\Column(name="rimandato", type="integer", nullable=false, options={"default" = 0})
*/
protected $rimandato;
/**
* @var int
*
* @ORM\Column(name="bocciato", type="integer", nullable=false, options={"default" = 0})
*/
protected $bocciato;
/**
* @var boolean
*
* @ORM\Column(name="fansub", type="boolean", nullable=false)
*/
protected $fansub;
/**
* @var boolean
*
* @ORM\Column(name="attivo", type="boolean", nullable=false)
*/
protected $attivo;
/**
* @var \DateTime
*
* @ORM\Column(name="data_aggiornamento", type="date")
*/
protected $data_aggiornamento;
/**
* @var Utente
*
* @ORM\ManyToOne(targetEntity="AC\UserBundle\Entity\Utente")
* @ORM\JoinColumn(name="utente_aggiornamento", referencedColumnName="id", nullable=true)
*/
protected $utente_aggiornamento;
/**
* @param boolean $attivo
*/
public function setAttivo($attivo)
{
$this->attivo = $attivo;
}
/**
* @return boolean
*/
public function getAttivo()
{
return $this->attivo;
}
/**
* @param int $bocciato
*/
public function setBocciato($bocciato)
{
$this->bocciato = $bocciato;
}
/**
* @return int
*/
public function getBocciato()
{
return $this->bocciato;
}
/**
* @param \DateTime $data_aggiornamento
*/
public function setDataAggiornamento($data_aggiornamento)
{
$this->data_aggiornamento = $data_aggiornamento;
}
/**
* @return \DateTime
*/
public function getDataAggiornamento()
{
return $this->data_aggiornamento;
}
/**
* @param \DateTime $data_ita
*/
public function setDataIta($data_ita)
{
$this->data_ita = $data_ita;
}
/**
* @return \DateTime
*/
public function getDataIta()
{
return $this->data_ita;
}
/**
* @param \DateTime $data_jap
*/
public function setDataJap($data_jap)
{
$this->data_jap = $data_jap;
}
/**
* @return \DateTime
*/
public function getDataJap()
{
return $this->data_jap;
}
/**
* @param int $durata
*/
public function setDurata($durata)
{
$this->durata = $durata;
}
/**
* @return int
*/
public function getDurata()
{
return $this->durata;
}
/**
* @param string $extra
*/
public function setExtra($extra)
{
$this->extra = $extra;
}
/**
* @return string
*/
public function getExtra()
{
return $this->extra;
}
/**
* @param boolean $fansub
*/
public function setFansub($fansub)
{
$this->fansub = $fansub;
}
/**
* @return boolean
*/
public function getFansub()
{
return $this->fansub;
}
/**
* @param int $id
*/
public function setId($id)
{
$this->id = $id;
}
/**
* @return int
*/
public function getId()
{
return $this->id;
}
/**
* @param string $numero_episodio
*/
public function setNumeroEpisodio($numero_episodio)
{
$this->numero_episodio = $numero_episodio;
}
/**
* @return string
*/
public function getNumeroEpisodio()
{
return $this->numero_episodio;
}
/**
* @param \AC\OperaBundle\Entity\Opera $opera
*/
public function setOpera($opera)
{
$this->opera = $opera;
}
/**
* @return \AC\OperaBundle\Entity\Opera
*/
public function getOpera()
{
return $this->opera;
}
/**
* @param int $ordine
*/
public function setOrdine($ordine)
{
$this->ordine = $ordine;
}
/**
* @return int
*/
public function getOrdine()
{
return $this->ordine;
}
/**
* @param int $promosso
*/
public function setPromosso($promosso)
{
$this->promosso = $promosso;
}
/**
* @return int
*/
public function getPromosso()
{
return $this->promosso;
}
/**
* @param int $rimandato
*/
public function setRimandato($rimandato)
{
$this->rimandato = $rimandato;
}
/**
* @return int
*/
public function getRimandato()
{
return $this->rimandato;
}
/**
* @param string $titolo_italiano
*/
public function setTitoloItaliano($titolo_italiano)
{
$this->titolo_italiano = $titolo_italiano;
}
/**
* @return string
*/
public function getTitoloItaliano()
{
return $this->titolo_italiano;
}
/**
* @param string $titolo_originale
*/
public function setTitoloOriginale($titolo_originale)
{
$this->titolo_originale = $titolo_originale;
}
/**
* @return string
*/
public function getTitoloOriginale()
{
return $this->titolo_originale;
}
/**
* @param string $trama
*/
public function setTrama($trama)
{
$this->trama = $trama;
}
/**
* @return string
*/
public function getTrama()
{
return $this->trama;
}
/**
* @param \AC\UserBundle\Entity\Utente $utente_aggiornamento
*/
public function setUtenteAggiornamento($utente_aggiornamento)
{
$this->utente_aggiornamento = $utente_aggiornamento;
}
/**
* @return \AC\UserBundle\Entity\Utente
*/
public function getUtenteAggiornamento()
{
return $this->utente_aggiornamento;
}
}
In the controller perform the classi call at the $form->isValid()
method to check validation. If there are errors i call $form->getErrorsAsString()
and this is the result:
ERROR: This value should not be blank.
ERROR: This value should not be blank.
numeroEpisodio:
No errors
titoloItaliano:
No errors
titoloOriginale:
No errors
dataIta:
ERROR: This value is not valid.
dataJap:
ERROR: This value is not valid.
durata:
No errors
trama:
No errors
extra:
No errors
fansub:
No errors
The property that use @Assert\NotBlank()
dose not put property name in error message! And for this reason i have the first two error line with e generic:
ERROR: This value should not be blank.
ERROR: This value should not be blank.
I i don't know who is the property that failed the validation. I look in the source code of Symfony Component and i see that for not blank constraint:
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @api
*/
class NotBlankValidator extends ConstraintValidator
{
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof NotBlank) {
throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\NotBlank');
}
if (false === $value || (empty($value) && '0' != $value)) {
$this->context->addViolation($constraint->message);
}
}
}
And for data constraint:
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @api
*/
class DateValidator extends ConstraintValidator
{
const PATTERN = '/^(\d{4})-(\d{2})-(\d{2})$/';
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof Date) {
throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Date');
}
if (null === $value || '' === $value || $value instanceof \DateTime) {
return;
}
if (!is_scalar($value) && !(is_object($value) && method_exists($value, '__toString'))) {
throw new UnexpectedTypeException($value, 'string');
}
$value = (string) $value;
if (!preg_match(static::PATTERN, $value, $matches) || !checkdate($matches[2], $matches[3], $matches[1])) {
$this->context->addViolation($constraint->message, array('{{ value }}' => $value));
}
}
}
The important difference is $this->context->addViolation($constraint->message);
VS $this->context->addViolation($constraint->message, array('{{ value }}' => $value));
Why?
This is core part of controller
if ($request->getMethod() == 'POST') {
try {
$form->handleRequest($request);
if ($form->isValid()) {
/* @var $item Episodio */
$item = $form->getData();
$em->persist($item);
$em->flush();
$msg = new Message(true, Message::OK_MESSAGE);
} else {
$msg = new Message(false, Message::KO_MESSAGE);
$errors = Utility::getErrorMessages($form);
$msg->setData($errors);
}
} catch (\Exception $ex) {
$msg = new Message(false, $ex->getMessage());
}
return new Response($this->get('jms_serializer')->serialize($msg, 'json'));
}
This is utility class that fetch error from form
class Utility {
static function getErrorMessages(\Symfony\Component\Form\Form $form) {
$errors = array();
foreach ($form->getErrors() as $key => $error) {
$template = $error->getMessageTemplate();
$parameters = $error->getMessageParameters();
foreach($parameters as $var => $value){
$template = str_replace($var, $value, $template);
}
$errors[$key] = $template;
}
//if ($form->hasChildren()) {
foreach ($form->all() as $child) {
if (!$child->isValid()) {
$errors[$child->getName()] = Utility::getErrorMessages($child);
}
}
//}
return $errors;
}
}
Form Class
class EpisodioForm extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('numeroEpisodio', 'text');
$builder->add('titoloItaliano', 'text',array());
$builder->add('titoloOriginale', 'text');
$builder->add('dataIta', 'date', array('widget' => 'single_text', 'format' => 'dd/MM/yyyy'));
$builder->add('dataJap', 'date', array('widget' => 'single_text', 'format' => 'dd/MM/yyyy'));
$builder->add('durata', 'text');
$builder->add('trama', 'textarea');
$builder->add('extra', 'text');
$builder->add('fansub', 'checkbox');
}
/**
* Returns the name of this type.
*
* @return string The name of this type
*/
public function getName()
{
return "episodio_form";
}
}
The version of framework is Symfony 2.4