A free and open-source book on ZF3 for beginners

Translation into this language is not yet finished. You can help this project by translating the chapters and contributing your changes.

7.11. Usar el Formulario en una Acción de Controlador

Cuando la clase para el modelo de formulario esta lista podemos usar finalmente el formulario en un método de acción del controlador.

Como ya deberíamos saber la manera en que el sitio funciona junto al formulario es generalmente un proceso interactivo (ilustrado esquemáticamente en la figura 7.18).

Figura 7.18. Flujo de trabajo típico del uso de un formulario Figura 7.18. Flujo de trabajo típico del uso de un formulario

La clase base Form provee varios métodos para alcanzar este objetivos (ver tabla 7.11).

Tabla 7.11. Métodos provistos par la clase base Form
Nombre del método Descripción
setData($data) Coloca los datos del formulario para la validación.
getData($flag) Recupera los datos validados.
isValid() Valida el formulario.
hasValidated() Revisa si el formulario ha sido validado.
getMessages($elementName = null) Regresa una lista de mensajes de fracaso en la validación, si se indica solo para un elemento o para todos los elementos del formulario.

Así el flujo de uso de un formulario genérico es el siguiente:

El código de abajo muestra como implementar este flujo de trabajo típico en nuestro método de acción en el controlador:

<?php
namespace Application\Controller;

use Application\Form\ContactForm;
// ...

class IndexController extends AbstractActionController
{
  // This action displays the feedback form
  public function contactUsAction()
  {
    // Create Contact Us form
    $form = new ContactForm();

    // Check if user has submitted the form
    if($this->getRequest()->isPost())
    {
      // Fill in the form with POST data
      $data = $this->params()->fromPost();
      $form->setData($data);

      // Validate form
      if($form->isValid()) {

        // Get filtered and validated data
        $data = $form->getData();

        // ... Do something with the validated data ...

        // Redirect to "Thank You" page
        return $this->redirect()->toRoute('application', ['action'=>'thankYou']);
      }
    }

    // Pass form variable to view
    return new ViewModel([
          'form' => $form
       ]);
  }
}

En el código de arriba definimos el método de acción contactUsAction() dentro de la clase IndexController (línea 10). En el método de acción creamos una instancia de la clase ContactForm (línea 13).

Luego en la línea 16 revisamos si la petición es una petición POST (revisando la primera línea de la petición HTTP).

En la línea 19 recuperamos los datos crudos enviados por el usuario. Extraemos todas las variables POST con la ayuda del complemento para controladores Params. Los datos se regresan en forma de un arreglo y se guardan dentro de la variable $data.

Los datos enviados por el usuario pueden contener errores y deberían ser filtrados y validados antes de su uso. Para hacer esto, en la línea 20 colocamos los datos en el modelo de formulario con el método setData() que provee la clase base Form. Validamos los datos del formulario con el método isValid() (línea 23), que regresa true si la validación es exitosa. Si la validación es exitosa recuperamos los datos validados usando el método getData() (línea 26) y luego podemos pasar los datos a nuestra capa de lógica de negocio.

Una vez que hemos usado los datos validados en la línea 31 dirigimos al usuario de la página web a la página Gracias a ti. Para redirigir se ejecuta el complemento para controladores Redirect. El método toRoute() del complemento Redirect toma dos parámetros: el primer parámetro es el nombre de la ruta («application») y el segundo parámetro es el arreglo de parámetros que se pasa al enrutador. Estos identifican la página web a donde redirigimos el usuario.

Prepararemos la acción de controlador y la plantilla de vista para la página Gracias a ti un poco más adelante.

En la línea 37 pasamos el modelo de formulario a través de la variable $form a la plantilla de vista. La plantilla de vista tendrá acceso a esta variable y la usará para mostrar en pantalla el formulario (y los posibles errores de validación).

7.11.1. Pasar los Datos del Formulario al Modelo

Para dar un ejemplo realista de como usar la validación de datos en el formulario de contacto crearemos en esta sección una clase de modelo simple llamada MailSender 24 que se usará para enviar un correo electrónico a una dirección de correo electrónico. Cuando el usuario envía el formulario validamos los datos del formulario y pasamos los datos validados al modelo MailServer pidiéndole que envíe el correo electrónico al destinatario.

24) En la terminología DDD MailSender se puede considerar como un modelo de servicio porque su objetivo es manipular datos y no guardar datos.

La lectura de esta sección es opcional y esta pensada fundamentalmente para principiantes. Podemos saltarla y revisar directamente la siguiente sección Form Presentation.

El modelo MailSender usará internamente el componente Zend\Mail. El componente Zend\Mail es un componente que provee Zend Framework 3 diseñado para proveer las funcionalidades necesarias para construir mensajes de correo electrónico (la clase Zend\Mail\Message) y varias clases que implementan los diferentes transportes para enviar correos electrónicos (en este ejemplo usaremos la clase Zend\Mail\Transport\Sendmail que usa el programa sendmail para enviar correos electrónicos).

Instalamos el componente Zend\Mail con Composer escribiendo el siguiente comando:

php composer.phar require zendframework/zend-mail

El programa sendmail es un MTA (Mail Transfer Agent) de software libre para sistemas GNU/Linux y Unix. Este MTA acepta mensajes pasados por scripts de PHP y decide en base a la cabecera del mensaje que método de envío debe usar y luego pasa el mensaje mediante el protocolo SMTP al servidor de correo apropiado (como Google Mail) para enviarlo al destinatario.

Comenzaremos creando el archivo MailSender.php dentro de la carpeta Service que está dentro de la carpeta src del módulo (ver la figura 7.19).

Figura 7.19. Crear el archivo MailSender.php Figura 7.19. Crear el archivo MailSender.php

El siguiente es el código que deberíamos colocar dentro del archivo MailSender.php:

<?php
namespace Application\Service;

use Zend\Mail;
use Zend\Mail\Message;
use Zend\Mail\Transport\Sendmail;

// This class is used to deliver an E-mail message to recipient.
class MailSender
{
  // Sends the mail message.
  public function sendMail($sender, $recipient, $subject, $text)
  {
    $result = false;
    try {

      // Create E-mail message
      $mail = new Message();
      $mail->setFrom($sender);
      $mail->addTo($recipient);
      $mail->setSubject($subject);
      $mail->setBody($text);

      // Send E-mail message
      $transport = new Sendmail('-f'.$sender);
      $transport->send($mail);
      $result = true;
    } catch(\Exception $e) {
      $result = false;
    }

    // Return status
    return $result;
  }
}

En el código de arriba definimos el namespace Application\Service (línea 2) porque la clase MailSender se comporta como un modelo de servicio (su objetivo es manipular los datos y no guardarlos).

En las líneas 4-6 declaramos los alias para las clases Mail, Message y Zend\Mail\Transport\Sendmail que provee el componente Zend\Mail.

En las líneas 9-35 definimos la clase MailSender. La clase tiene un único método sendMail() (línea 12) que toma cuatro argumentos: remitente del correo electrónico, destinatario del correo, el asunto del mensaje y el cuerpo del mensaje.

En la línea 18 creamos una instancia de la clase Message. Usamos los métodos que provee esta clase para construir el mensaje (colocar el asunto, el cuerpo, etc) en las líneas 19-22.

En la línea 25 creamos una instancia de la clase Sendmail que usa el programa sendmail para pasar el mensaje al servidor de correo apropiado (ver líneas 25-26). Como las clases que provee el componente Zend\Mail pueden lanzar una excepción en el caso de un fallo encerramos el bloque de código entre el administrador de excepciones try-catch.

El método sendMail() regresará true si el correo electrónico se envía con éxito de lo contrario regresará false (línea 33).

Configurar un sistema de correo para nuestro servidor web es una tarea que reviste algo de complejidad. Generalmente es necesario instalar sendmail y configurar el registro MX DNS del servidor para usar determinado servidor de correo (o también un servidor de correo local, por ejemplo, Postfix o un servidor remoto como Google Mail). Por la complejidad del problema este no se discute en este libro. Podemos encontrar en internet información adicional sobre la configuración de un servidor de correo electrónico para nuestra sistema en particular.

Ahora vamos a registrar el servicio MailSender en nuestro archivo module.config.php de la siguiente manera:

return [
    //...
    'service_manager' => [
        'factories' => [
            Service\MailSender::class => InvokableFactory::class,
        ],
    ],

    //...
];

Luego vamos a instanciar el modelo MailSender en nuestro método IndexController::contactUsAction() y le pasaremos los datos del formulario validado.

Como usamos el servicio MailSender en nuestro controlador este servicio es una dependencia de nuestro controlador. Así, necesitaremos crear una fábrica para el controlador e inyectar la dependencia dentro de constructor del controlador. Parece complejo a primera vista pero a medida que mejoremos nuestras habilidades encontraremos que esto más simple y que mejora enormemente la estructura del código.

Vamos a crear una fábrica para el IndexController, la colocaremos dentro de la subcarpeta Factory que esta dentro de la subcarpeta Controller. Como podemos ver el único trabajo de la clase fábrica es crear el controlador y pasarle la dependencia.

<?php
namespace Application\Controller\Factory;

use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;
use Application\Service\MailSender;
use Application\Controller\IndexController;

class IndexControllerFactory
{
    public function __invoke(ContainerInterface $container,
                             $requestedName, array $options = null)
    {
        $mailSender = $container->get(MailSender::class);

        // Instantiate the controller and inject dependencies
        return new IndexController($mailSender);
    }
}

Modificamos el archivo module.config.php para usar la fábrica ajustada a la medida que acabamos de crear:

return [
    //...
    'controllers' => [
        'factories' => [
            Controller\IndexController::class => Controller\Factory\IndexControllerFactory::class,
        ],
    ],

    //...
];

Luego agregamos el constructor y los métodos contactUsAction(), thankYouAction() y sendErrorAction() al controlador. Abajo mostramos todo el código:

<?php
// ...
use Application\Service\MailSender;

class IndexController extends AbstractActionController
{
  private $mailSender;

  public function __construct($mailSender)
  {
    $this->mailSender = $mailSender;
  }

  public function contactUsAction()
  {
    // Create Contact Us form
    $form = new ContactForm();

    // Check if user has submitted the form
    if($this->getRequest()->isPost()) {

      // Fill in the form with POST data
      $data = $this->params()->fromPost();

      $form->setData($data);

      // Validate form
      if($form->isValid()) {

        // Get filtered and validated data
        $data = $form->getData();
        $email = $data['email'];
        $subject = $data['subject'];
        $body = $data['body'];

        // Send E-mail
        if(!$this->mailSender->sendMail('no-reply@example.com', $email,
                        $subject, $body)) {
          // In case of error, redirect to "Error Sending Email" page
          return $this->redirect()->toRoute('application',
                        ['action'=>'sendError']);
        }

        // Redirect to "Thank You" page
        return $this->redirect()->toRoute('application',
                        ['action'=>'thankYou']);
      }
    }

    // Pass form variable to view
    return new ViewModel([
      'form' => $form
    ]);
  }

  // This action displays the Thank You page. The user is redirected to this
  // page on successful mail delivery.
  public function thankYouAction()
  {
    return new ViewModel();
  }

  // This action displays the Send Error page. The user is redirected to this
  // page on mail delivery error.
  public function sendErrorAction()
  {
    return new ViewModel();
  }
}

Como podemos ver en el código hacemos lo siguiente:


Top