Noções básicas de validação personalizada no Symfony 4/5 com exemplos

No Symfony, na minha opinião, uma funcionalidade muito conveniente para validação de entidade. Em particular, o uso do formato de anotação para configurar regras de validação realmente me suborna. Na grande maioria das tarefas, as soluções chave na mão cobrem casos padrão. Mas, como você sabe, a validação é uma questão delicada e você nunca sabe quais restrições terá que impor dessa vez. Por outro lado, uma validação mais flexível e bem pensada sempre ajudará a evitar erros do usuário.


Convidamos você a ver como é fácil escrever suas restrições e estender as comparações de dois campos disponíveis usando o exemplo de validação e validação básicas. O artigo pode ser de interesse para aqueles que ainda estão pouco familiarizados com a validação no Symfony ou que ignoraram a possibilidade de escrever seus próprios validadores.


Neste artigo, teremos em mente o contexto clássico de validação. Existe uma certa entidade, cujos dados são preenchidos no formulário correspondente, enquanto a validação impõe restrições a qualquer aspecto dos dados inseridos. Vale ressaltar que os validadores podem ser usados ​​sem restrições para controlar a lógica interna, validação da API etc.


Determinamos imediatamente que os exemplos propostos não são as únicas soluções possíveis e não reivindicam ótima otimização ou uso extensivo, mas são oferecidos para fins de demonstração. Por outro lado, partes dos exemplos são tomadas e simplificadas em projetos de combate, e espero que possam ser úteis para ilustrar a simplicidade de escrever validações personalizadas sem interromper a realidade.


Validação básica


Para fazer uma validação totalmente funcional, você precisa criar apenas duas classes - os herdeiros de Constraint e ConstraintValidator. Restrição, como o nome indica, define e descreve as restrições, enquanto o ConstraintValidator as valida. Como exemplo, escreveremos um validador de hora no formato "hh: mm" armazenado em formato de texto. Na documentação oficial, propõe-se que o Constraint descreva as propriedades públicas da restrição. Então, vamos fazê-lo.


namespace App\Custom\Constraints;

use Symfony\Component\Validator\Constraint;

/**
 * @Annotation
 * @Target({"PROPERTY"})
 */
class TextTime extends Constraint
{
    public $message = '  ';
}

Aqui, a anotação Target determina se a validação será usada: para uma propriedade ou para uma classe. Você também pode definir esse parâmetro substituindo a função.


public function getTargets()
{
    //   self::CLASS_CONSTRAINT
    return self::PROPERTY_CONSTRAINT;
}

A propriedade da mensagem, como você pode imaginar, é usada para exibir informações sobre um erro de validação.


, .


public function validatedBy()
{
    return \get_class($this).'Validator';
}

, , ConstraintValidator. ConstraintValidator validate.


namespace App\Custom\Constraints;

use Symfony\Component\Validator\ConstraintValidator;

class TextTimeValidator extends ConstraintValidator
{
    /**
     *    
     *
     * @param mixed $value  
     * @param Constraint $constraint   
     */
    public function validate($value, Constraint $constraint)
    {
        $time = explode(':', $value);

        $hours = (int) $time[0];
        $minutes = (int) $time[1];

        if ($hours >= 24 || $hours < 0)
            $this->fail($constraint);
        else if ((int) $time[1] > 60 || $minutes < 0)
            $this->fail($constraint);
    }

    private function fail(Constraint $constraint) 
    {
        $this->context->buildViolation($constraint->message)
            ->addViolation();
    }
}

, , , .


...
    /**
     * @Assert\NotBlank()
     * @Assert\Regex(
     *     pattern="/\d{2}:\d{2}/",
     *     message=" "
     * )
     * @CustomAssert\TextTime()
     * @ORM\Column(type="string", length=5)
     */
    private $timeFrom;
...

, , . , , , message .


. , Symfony Time. : . , "hh:mm:ss". , .



, , - . , , : "hh:mm", "hh:mm"-"hh:mm". , , .


, AbstractComparsion. AbstractComparsion Constraint — .


namespace App\Custom\Constraints;

use Symfony\Component\Validator\Constraints\AbstractComparison;

/**
 * @Annotation
 * @Target({"PROPERTY"})
 */
class TextTimeInterval extends AbstractComparison
{   
    public $message = '       {{ compared_value }}.';
}


namespace App\Custom\Constraints;

use Symfony\Component\Validator\Constraints\AbstractComparisonValidator;

class TextTimeIntervalValidator extends AbstractComparisonValidator
{
    /**
     *       
     *
     * @param mixed $timeFrom   
     * @param mixed $timeTo   
     *
     * @return  true   , false 
     */
    protected function compareValues($timeFrom, $timeTo)
    {
        $compareResult = true;

        $from = explode(':', $timeFrom);
        $to = explode(':', $timeTo);

        try {
            if ((int) $from[0] > (int) $to[0])
                $compareResult = false;
            else if (((int) $from[0] == (int) $to[0]) && ((int) $from[1] > (int) $to[1])) {
                $compareResult = false;
            }
        } catch (\Exception $exception) {
            $compareResult = false;
        }

        return $compareResult;
    }
}

Somente neste caso (uma das implementações possíveis) substituímos a função compareValues, que retorna true / false no êxito da validação e é processada no AbstractComparisonValidator pela função validate ().


Isso é essencialmente descrito da seguinte forma


...
 /**
     * @Assert\NotBlank()
     * @Assert\Regex(
     *     pattern="/\d{2}:\d{2}/",
     *     message=" "
     * )
     * @CustomAssert\TextTime()
     * @CustomAssert\TextTimeInterval(
     *     propertyPath="timeTo",
     *     message="       "
     * )
     * @ORM\Column(type="string", length=5)
     */
    private $timeFrom;

    /**
     * @Assert\NotBlank()
     * @Assert\Regex(
     *     pattern="/\d{2}:\d{2}/",
     *     message=" "
     * )
     * @CustomAssert\TextTime()
     * @ORM\Column(type="string", length=5)
     */
    private $timeTo;
...

Esperamos que esta pequena análise e exemplos tenham mostrado como é fácil usar validadores personalizados. Como para iniciantes, consideramos os validadores mais simples, é claro, para não sobrecarregar a apresentação com descrições de assuntos, falhamos em fornecer exemplos excepcionais. Em artigos futuros, planejo considerar validadores mais complexos e casos específicos.


All Articles