1

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

4

1 回答 1

2

为什么$this->context->addViolation($constraint->message);VS $this->context->addViolation($constraint->message, array('{{ value }}' => $value));

这是因为日期约束验证器在错误消息中显示了验证失败的值,例如

45.04.2014 is not a valid date.

NotBlank约束验证器不需要,因为导致错误的值始终为空。

将属性名称放入错误消息中

这可以实现:

  • 通过将实体中约束的注释更改为:

    class Episodio {
      /**
       *  @Assert\NotBlank(message="'titolo_italiano' should not be blank.")
       */
       protected $titolo_italiano;
    
       /**
        * @Assert\NotBlank(message="'titolo_originale' should not be blank.")
        */
        protected $titolo_originale;
    }
    

    为了便于阅读,我省略了实体中的列定义和其他字段。

  • 或者通过定义将属性名称传递给错误消息的自定义验证器:

    约束:

    class MyCustomNotBlank extends NotBlank 
    {
        public $message = "'{{ propertyName }}' should not be blank.";
    }
    

    验证者:

    use Symfony\Component\Validator\Constraint;
    use Symfony\Component\Validator\ConstraintValidator;
    
    class MyCustomNotBlankValidator extends ConstraintValidator
    {
        public function validate($value, Constraint $constraint)
        {
            if (!$value) {
                $this->context->addViolation(
                    $constraint->message,
                    array('{{ propertyName}}' => $this->context->getPropertyPath())
                );
            }
        }
    }
    

    这向您展示了如何定义自定义验证约束: http ://symfony.com/doc/current/cookbook/validation/custom_constraint.html

获取具有属性名称和错误消息的键值数组

Afaik,Symfony2 的表单组件中没有执行此操作的功能,因此必须实现它。

请参阅https://stackoverflow.com/a/13763053/277106。您可以将该功能添加到服务以实现可重用性。在您的(AJAX)控制器中,您可以执行以下操作:

public function someAction(Request $request)
{
    $errors = array();
    $form = $this->createForm(new MyType);

    if (!$form->bindRequest($request)->isValid()) {
        $errors = $this->myFormService->getErrorMessages($form);
    }
    else {
        // save the entity
    }
    return JsonResponse($errors);
}
于 2014-05-14T18:08:37.333 回答