Conceptos básicos de validación personalizada en Symfony 4/5 con ejemplos

En Symfony, en mi opinión, una funcionalidad muy conveniente para la validación de entidades. En particular, el uso del formato de anotación para configurar las reglas de validación realmente me soborna. En la gran mayoría de las tareas, las soluciones llave en mano cubren casos estándar. Pero, como sabe, la validación es un asunto delicado, y nunca se sabe qué restricciones tendrá que imponer esta vez. Por otro lado, una validación más flexible y bien pensada siempre ayudará a evitar errores del usuario.


Debajo del corte, lo invito a que vea lo fácil que es escribir sus restricciones y ampliar las comparaciones de dos campos que están disponibles utilizando el ejemplo de validación básica y validación. El artículo puede ser de interés para aquellos que todavía están poco familiarizados con la validación en Symfony o que han pasado por alto la posibilidad de escribir sus propios validadores.


En este artículo tendremos en cuenta el contexto clásico de validación. Existe una determinada entidad, cuyos datos se completan desde el formulario correspondiente, mientras que la validación impone restricciones sobre cualquier aspecto de los datos ingresados. Vale la pena señalar que los validadores pueden usarse sin restricciones para controlar la lógica interna, la validación de API, etc.


Determinamos de inmediato que los ejemplos propuestos no son las únicas soluciones posibles y no reclaman la máxima optimización o uso extensivo, sino que se ofrecen con fines de demostración. Por otro lado, partes de los ejemplos están tomados y simplificados de proyectos de combate, y espero que puedan ser útiles para ilustrar la simplicidad de escribir validaciones personalizadas sin interrupción de la realidad.


Validación básica


Para realizar una validación completamente funcional, debe crear solo dos clases: los herederos de Constraint y ConstraintValidator. La restricción, como su nombre lo indica, define y describe las restricciones, mientras que el Validador de restricciones las valida. Como ejemplo, escribiremos un validador de tiempo en el formato "hh: mm" almacenado en un formato de texto. En la documentación oficial, se propone que Restricción describa las propiedades públicas de la restricción. Hagamoslo.


namespace App\Custom\Constraints;

use Symfony\Component\Validator\Constraint;

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

Aquí, la anotación Target determina si se usará la validación: para una propiedad o para una clase. También puede establecer este parámetro anulando la función.


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

La propiedad del mensaje, como puede suponer, se usa para mostrar información sobre un error de validación.


, .


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;
    }
}

Solo en este caso (una de las implementaciones posibles) anulamos la función compareValues, que devuelve verdadero / falso en el éxito de la validación y es procesada en AbstractComparisonValidator por la función validate ().


Esto se describe esencialmente como sigue


...
 /**
     * @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;
...

Esperemos que este pequeño análisis y ejemplos hayan demostrado lo fácil que es usar validadores personalizados. Dado que, para empezar, consideramos los validadores más simples, por supuesto, para no sobrecargar la presentación con descripciones de temas, no pudimos dar ejemplos excepcionales. En futuros artículos planeo considerar validadores más complejos y casos específicos.


All Articles