A free and open-source book on ZF3 for beginners


7.11. Использование формы в действии контроллера

Когда класс модели формы будет готов, вы наконец сможете использовать форму в методе действия контроллера.

Как вы уже, возможно, знаете, то, как пользователь работает с формой - это, как правило, итеративный процесс (схематически изображен на рисунке 7.18):

Рисунок 7.18. Типичный процесс использования формы Рисунок 7.18. Типичный процесс использования формы

Базовый класс Form предоставляет для этих целей несколько методов (см. таблицу 7.11).

Таблица 7.11.Методы, предоставляемые базовым классом Form
Имя метода Описание
setData($data) Задает данные формы для валидации.
getData($flag) Извлекает валидированные данные.
isValid() Валидирует форму.
hasValidated() Проверяет, была ли форма валидирована.
getMessages($elementName = null) Возвращает список сообщений о неудачных валидациях, если такие есть, для одного элемента или для всех элементов формы.

Таким образом, общий процесс использования формы таков:

Пример кода ниже показывает, как реализовать этот процесс в методе действия контроллера:

<?php
namespace Application\Controller;

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

class IndexController extends AbstractActionController 
{
    // Это действие отображает форму обратной связи
    public function contactUsAction() 
    {
        // Создаем форму
        $form = new ContactForm();
        
        // Проверяем, отправил ли пользователь форму
        if($this->getRequest()->isPost()) 
        {
            // Заполняем форму POST-данными
            $data = $this->params()->fromPost();            
            $form->setData($data);
            
            // Валидируем форму
            if($form->isValid()) {
                
                // Получаем фильтрованные и валидированные данные
                $data = $form->getData();
                
                // ... Какие-то действия с валидированными данными ...
		
                // Перенаправление на страницу "Спасибо"
                return $this->redirect()->toRoute('application', ['action'=>'thankYou']);
            }            
        } 
        
        // Передаем переменную формы представлению
        return new ViewModel([
           'form' => $form
        ]);
    }
}

В этом фрагменте мы определяем метод действия contactUsAction() в классе IndexController (строка 10). В методе действия мы создаем экземпляр класса ContactForm (строка 13).

Затем в строке 16 мы проверяем, является ли запрос POST-запросом (проверяя первую строку HTTP-запроса).

В строке 19 мы извлекаем необработанные данные, отправленные пользователем. Все переменные POST извлекаются с помощью плагина контроллера Params. Данные возвращаются в форме массива и сохраняются в переменную $data.

Данные, отправленные пользователем, могут содержать ошибки и должны пройти фильтрацию и валидацию перед дальнейшим использованием. Для этого в строке 20 мы задаем эти данные модели с помощью метода setData() базового класса Form. Мы затем валидируем данные формы методом isValid() (строка 23), который возвращает значение true при успешной валидации. Если валидация проходит успешно, мы извлекаем валидированные данные с помощью метода getData() (строка 26) и затем можем передать их уровню бизнес-логики.

После использования валидированных данных мы перенаправляем веб-пользователю на страницу Thank You (строка 31). Перенаправление осуществляется с помощью плагина контроллера Redirect. Метод этого плагина toRoute() принимает два параметра: первый параметр - имя маршрута ("application"), а второй - массив параметров, которые нужно передать маршрутизатору. Эти параметры определяют страницу, куда перенаправляется пользователь.

Мы подготовим действие контроллера и шаблон представления для страницы благодарности немного позже.

В строке 37 мы передаем модель формы шаблону представлению через переменную $form. Шаблон представления будет обращаться к этой переменной и использовать ее для визуализации формы (и возможных ошибок валидации).

7.11.1. Передача модели данных формы

В качестве реального примера использования валидированных данных формы обратной связи, в этом примере мы создадим простой класс модели MailSender ?, которая может быть использована для отправки электронного сообщения на адрес электронной почты. Когда пользователь отправит форму, мы проведем валидацию данных формы, а затем передадим валидированные данные модели MailSender и потребуем отправить сообщение электронной почты получателю.

24) С точки зрения DDD, MailSender можно отнести к моделям сервисов, так как ее задачей является управление данными, а не их хранение.

Этот раздел по большей части предназначен для начинающих. Вы можете пропустить его и сразу перейти к следующему разделу Представление формы.

Модель MailSender будет внутренне использовать компонент Zend\Mail. Этот компонент, предоставляемый Zend Framework 3, предназначен для обеспечения разработчика удобной функциональностью для составления почтовых сообщений (класс Zend\Mail\Message) и несколькими классами, реализующими доступные методы передачи (в этом примере мы будем использовать класс Zend\Mail\Transport\Sendmail, который использует программу sendmail для доставки электронной почты).

Установите компонент Zend\Mail с помощью Composer'a, прописав следующую команду:

php composer.phar require zendframework/zend-mail

Программа sendmail - распространяемый бесплатно вместе с исходными кодами агент передачи почты для операционных систем Linux/Unix. Он принимает сообщения, передаваемые ему PHP-скриптом, решает, какой метод доставки использовать, в зависимости от заголовка сообщения, и затем передает сообщение через протокол SMTP на почтовый сервер (например, Google Mail) для доставки получателю.

Начнем с создания файла MailSender.php под каталогом Service под корневым каталогом модуля (см. рисунок 7.19).

Создание 7.19. Создание файла MailSender.php Создание 7.19. Создание файла MailSender.php

Ниже представлен код, который нужно поместить в файл MailSender.php:

<?php
namespace Application\Service;

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

// Этот класс используется для доставки электронного письма получателю.
class MailSender 
{
    // Отправляет эл. сообщение.
    public function sendMail($sender, $recipient, $subject, $text) 
    {
        $result = false;
        try {
        
            // Создаем эл. сообщение
            $mail = new Message();
            $mail->setFrom($sender);
            $mail->addTo($recipient);
            $mail->setSubject($subject);
            $mail->setBody($text);
	  
            // Посылаем эл. сообщение
            $transport = new Sendmail('-f'.$sender);
            $transport->send($mail);
            $result = true;
        } catch(\Exception $e) {
            $result = false;
        }
        
        // Возвращаем статус 
        return $result;
    }
}

В этом фрагменте мы определяем пространство имен Application\Service (строка 12), так как класс MailSender можно отнести к моделям сервисов (его задачей является управление данными, а не их хранение).

В строках 4-6 мы объявляем псевдонимы для классов Mail, Message и Zend\Mail\Transport\Sendmail, предоставляемых компонентом Zend\Mail.

В строках 9-35 мы определяем класс MailSender. У него есть один единственный метод sendMail() (строка 12), который принимает четыре аргумента: адрес электронной почты отправителя, адрес электронной почты получателя, тема сообщения и, наконец, основной текст сообщения.

В строке 18 мы создаем экземпляр класса Message. Мы используем методы этого класса, чтобы составить сообщение (задать его тему, текст и т.д.) в строках 19-22.

В строке 25 мы создаем экземпляр класса Sendmail, который использует программу sendmail для передачи сообщения почтовому серверу (см. строки 25-26). Так как классы, предоставляемые компонентом Zend\Mail могут выбрасывать исключение при ошибке, мы заключим этот фрагмент кода в блок обработки исключений try-catch

Метод sendMail() вернет значение true, если сообщение было успешно отправлено; в противном случае он вернет false (строка 33).

Настройка почтовой системы для веб-сервера - это довольно сложная задача. Она, как правило, требует установки sendmail и настройки записи MX DNS сервера для использования определенного почтового сервера (либо локального, например, Posftix, либо удаленного вроде Google Mail). Из-за сложности этой темы, мы не обсуждаем ее в данной книге. Вы можете найти дополнительную информацию о настройки почты для вашей конкретной системы в Интернете.

Теперь зарегистрируем сервис MailSender в файле module.config.php следующим образом:

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

Далее вы сможете инстанцировать модель MailSender в методе IndexController::contactUsAction() и передать ей валидированные данные формы.

Так как мы используем сервис MailSender в нашем контроллере, этот сервис является зависимостью для контроллера. Таким образом, нам необходимо будет создать фабрику для контроллера и внедрить зависимость в конструктор контроллера. Кажется сложным на первый взгляд, но когда вы улучшите свои навыки, то поймете, что это довольно просто и значительно улучшает структуру вашего кода.

Давайте создадим фабрику для IndexController (поместим ее в подкаталог Factory под каталогом Controller). Как видите, вся работа класса фабрики заключается в создании контроллера и передаче ему зависимости.

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

Изменим файл module.config.php, чтобы он использовал только что созданную нами фабрику:

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

Затем добавим к контроллеру конструктор, а также методы contactUsAction(), thankYouAction() иsendErrorAction().

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

class IndexController extends AbstractActionController 
{
    private $mailSender;
    
    public function __construct($mailSender) 
    {
        $this->mailSender = $mailSender;
    }
    
    public function contactUsAction() 
    {
        // Создаем форму обратной связи
        $form = new ContactForm();
        
        // Проверяем, отправил ли пользователь форму
        if($this->getRequest()->isPost()) {
            
            // Заполняем форму POST-данными
            $data = $this->params()->fromPost();            
            
            $form->setData($data);
            
            // Валидируем форму
            if($form->isValid()) {
                    
                // Получаем фильтрованные и валидированные данные
                $data = $form->getData();
                $email = $data['email'];
                $subject = $data['subject'];
                $body = $data['body'];
                    
                // Отправляем эл. почту
                if(!$this->mailSender->sendMail('no-reply@example.com', $email, 
                            $subject, $body)) {
                    // В случае ошибки перенаправляем на страницу "Ошибка отправки электронной почты"
                    return $this->redirect()->toRoute('application', 
                            ['action'=>'sendError']);
                }
                    
                // Перенаправляем на страницу "Спасибо"
                return $this->redirect()->toRoute('application', 
                            ['action'=>'thankYou']);
            }            
        } 
        
        // Передаем переменную формы представлению
        return new ViewModel([
            'form' => $form
        ]);
    }
    
    // Это действие отображает страницу благодарности. Пользователь перенаправляется на эту
    // страницу при успешной отправке письма.
    public function thankYouAction() 
    {
        return new ViewModel();
    }
    
    // Это действие отображает страницу ошибки. Пользователь перенаправляется на эту
    // страницу в случае ошибки при отправке письма.
    public function sendErrorAction() 
    {
        return new ViewModel();
    }
}

Как вы видите из фрагмента кода выше, мы делаем следующее:


Top