Extendiendo el sfActions de Symfony 3/3

Trabajando con formularios

Después de mucho tiempo de trabajo continuamos con el tercer artículo de la serie Extendiendo el sfActions de Symfony en la que venimos hablando sobre hacer más genéricas las opciones que siempre usamos al trabajar con Symfony.

Trabajar con formularios es algo que siempre vamos a hacer ya que sin ellos es imposible pedir información al usuario. Lastimosamente el libro oficial “A Gentle Introduction to symfony” no se encuentra todavía traducido al castellano pero les dejo en enlace al capítulo de formularios aquí.

El subframework de formularios de Symfony es algo muy interesante ya que la construcción de cada formulario se encuentra encapsulada en un objeto incluyendo las validaciones del mismo. De esta manera nos permite concentrarnos en la construcción del formulario y no desviarnos del foco.

La idea de este artículo será analizar la forma de validación de los formularios y construir un método nuevo en el BaseActions a fin de escribir menos al la hora de validar los formularios.

Para el ejemplo usaremos un formulario de contacto bien básico.

class ContactoForm extends BaseForm
{
 
    public function configure()
    {
        //-- Creo los campos del formulario
        $this->setWidgets(array(
            'nombre' => new sfWidgetFormInput(),
            'asunto' => new sfWidgetFormInput(),
            'email' => new sfWidgetFormInput(),
            'comentario' => new sfWidgetFormTextarea()
        ));
 
        //-- Asigno el nombre del formulario
        $this->widgetSchema->setNameFormat('contacto[%s]');
 
        //-- Agrego los validadores
        $this->setValidators(array(
            'nombre' => new sfValidatorString(
                array(
                    'required' => true,
                ),
                array(
                    'required' => 'Ingrese su nombre',
            )),
            'asunto' => new sfValidatorString(
                array(
                    'required' => true,
                ),
                array(
                    'required' => 'Ingrese un asunto',
            )),
            'email' => new sfValidatorEmail(
                array(
                    'required' => true,
                ),
                array(
                    'required' => 'Ingrese su dirección de email',
                    'invalid' => 'Debe ingresar una dirección de email válida',
            )),
            'comentario' => new sfValidatorString(
                array(
                    'required' => true,
                ),
                array(
                    'required' => 'Escriba su comentario',
            )),
        ));
    }
 
}

Una vez que tenemos la clase del formulario simplemente lo instanciamos dentro del action que queremos usar:

public function executeContacto(sfWebRequest $request)
{
    $this->form = new ContactoForm();
}

Una vez instanciado vamos al template del action y le damos un poco de formato para que se entienda mejor el objeto

<h1>Contacto</h1>
<form action="<?php echo url_for('principal/contacto') ?>" method="post">
    <div>
        <?php echo $form['nombre']->renderLabel() ?>
        <?php echo $form['nombre'] ?>
        <?php echo $form['nombre']->renderError() ?>
    </div>
    <div>
        <?php echo $form['asunto']->renderLabel() ?>
        <?php echo $form['asunto'] ?>
        <?php echo $form['asunto']->renderError() ?>
    </div>
    <div>
        <?php echo $form['email']->renderLabel() ?>
        <?php echo $form['email'] ?>
        <?php echo $form['email']->renderError() ?>
    </div>
    <div>
        <?php echo $form['comentario']->renderLabel() ?>
        <?php echo $form['comentario'] ?>
        <?php echo $form['comentario']->renderError() ?>
    </div>
    <input value="Enviar" type="submit">
    <!-- Agrego el campo de tipo hidden ya que si no lo agrego no va a dejar enviar el form -->
    <?php echo $form['_csrf_token'] ?>
</form>

Lo que tenemos hasta el momento es un formulario que no responde a las validaciones que creamos en la clase ContactoForm.class.php por lo que, siguiendo el libro oficial mencionado arriba, vemos que el truco está en hacer el submit del formulario al mismo action y escribir un poco de código para la validación, por lo que vamos a modificar un poco nuestra acción.

public function executeContacto(sfWebRequest $request)
{
    $this->form = new ContactoForm();
 
    //-- Cuando se accede al action por post se valida en formulario
    if($request->isMethod(sfRequest::POST))
    {
        //-- Obteiene los datos del formulario y los integra al $form
        $this->form->bind($request->getParameter('contacto'));
 
        //-- En caso de se válidos se puede procesar el formulario y en caso contrario
        //   se continúa con la página mostrando los mensajes de error de validación.
        if($this->form->isValid())
        {
            //-- Se obtienen los datos del formulario en un array $contacto
            $contacto = $this->form->getValues();
 
            //-- Se procesa el formulario
 
            //-- Siempre se debería hacer una redirección al procesar todo el formulario para no
            //   tener problemas con el F5
            $this->redirect('principal/contacto');
        }
    }
}

Analizando el código de arriba vemos que siempre tendremos que validar la llegada del post, obtener los datos y validarlos, procesar el formulario y finalmente redireccionar. Como el procesamiento de los datos es dependiente de cada action, lo tendremos que dejar dentro del action pero lo que si podemos generalizar son las validaciones anteriores a fin de estar seguros de que podemos procesar lo que llega en ese momento.

Para esto crearemos un método más dentro de nuestro BaseActions al que le llamaremos formIsValid:

/**
 * Comprueba un formulario $form recibido por post $request con datos de acuerdo a los validadores
 * que existen dentro de la clase del formulario. En caso de que el procesamiento del formulario
 * no se encuentre dentro del mismo action permite un parametro $template con el nombre del
 * template a usar.
 * En caso de que los datos sean válidos devuelve un array con los datos y false de lo contario. Si
 * la llamada al método no viene por una petición post devuelve null.
 * @param sfWebRequest $request
 * @param sfForm $form
 * @param string $template
 * @return mixed
 */
protected function formIsValid(sfWebRequest $request, sfForm $form, $template=null)
{
    //-- En caso de que la llamada no venga por post retorna null
    if(!$request->isMethod(sfRequest::POST))
        return null;
 
    //-- Obtiene los datos del formulario y los integra al $form
    $form->bind($request->getParameter($form->getName()));
 
    //-- En caso de que los datos no sean válidos con relación a los validadores
    //   del $form setea el template a usar y retorna false
    if (!$form->isValid())
    {
        //-- En caso de haberse enviado un template lo setea
        if($template != null)
            $this->setTemplate($template);
 
        return false;
    }
 
    //-- En caso de ser válido devuelve un array con los datos enviados
    return $form->getValues();
}

Este método nos ayuda a validar los datos del formulario y nos devuelve un array con los datos en caso de ser válidos, por lo que lo podemos implementar de la siguiente forma:

public function executeContacto(sfWebRequest $request)
{
    $this->form = new ContactoForm();
 
    if($contacto = $this->formIsValid($request, $this->form))
    {
        //-- Se procesa el formulario
 
        $this->redirect('principal/contacto');
    }
}

De esta manera, si ingresa al if, tendremos ya cargados los datos del form para usarlo dentro de la variable $contacto que es un array de la siguiente manera:

Array
(
    [nombre] => Jhon Doe
    [asunto] => Prueba de contacto
    [email] => jdoe@gmail.com
    [comentario] => Esta es una prueba de contacto
)

En caso de retornar false, se mostrarán los errores de validación ya que como dentro del método estamos trabajando con el objeto ContactoForm y hemos realizado el ->bind() el mismo formulario contiene la información necesaria con los mensajes a fin de mostrarse en el template.

Con este método podremos, con una sola línea de código, realizar la validación, obtener los datos para trabajar con ellos y olvidarnos del resto.

En caso de que tengamos dos acciones separadas, una para mostrar el formulario y otra para procesarlo simplemente usaremos el parámetro opcional del método formIsValid() y le pasaremos cual es el template a usar en caso de existir errores de validación.

if($contacto = $this->formIsValid($request, $this->form, 'contacto'))

Espero que les haya sido de utilidad y nos encontramos pronto con el siguiente artículo.

Un comentario en “Extendiendo el sfActions de Symfony 3/3”

Comenta este artículo