A free and open-source book on ZF3 for beginners


12.9. Добавление нового поста

В этой главе мы создадим страницу Add New Post, которая позволит добавлять новые посты в блог. Для этого нам понадобится следующее:

12.9.1. Добавление PostForm

Сначала добавим форму PostForm, которая позволит вводить данные одного поста: его заголовок, содержимое, список связанных с ним тегов, разделенных запятыми, и статус (опубликован или черновик). Чтобы это сделать, создайте файл PostForm.php в каталоге Form под корневым каталогом модуля. Поместите в этот файл следующий код:

<?php

namespace Application\Form;

use Zend\Form\Form;
use Zend\InputFilter\InputFilter;
use Application\Entity\Post;

/**
 * Эта форма используется для сбора данных о посте.
 */
class PostForm extends Form
{
    /**
     * Конструктор.     
     */
    public function __construct()
    {
        // Определяем имя формы.
        parent::__construct('post-form');
     
        // Задает для этой формы метод POST.
        $this->setAttribute('method', 'post');
                
        $this->addElements();
        $this->addInputFilter();         
    }
    
    /**
     * Этот метод добавляет элементы к форме (поля ввода и кнопку отправки формы).
     */
    protected function addElements() 
    {
                
        // Добавляем поле "title"
        $this->add([           
            'type'  => 'text',
            'name' => 'title',
            'attributes' => [
                'id' => 'title'
            ],
            'options' => [
                'label' => 'Title',
            ],
        ]);
        
        // Добавляем поле "content"
        $this->add([
            'type'  => 'textarea',
            'name' => 'content',
            'attributes' => [                
                'id' => 'content'
            ],
            'options' => [
                'label' => 'Content',
            ],
        ]);
        
        // Добавляем поле "tags"
        $this->add([
            'type'  => 'text',
            'name' => 'tags',
            'attributes' => [                
                'id' => 'tags'
            ],
            'options' => [
                'label' => 'Tags',
            ],
        ]);
        
        // Добавляем поле "status"
        $this->add([
            'type'  => 'select',
            'name' => 'status',
            'attributes' => [                
                'id' => 'status'
            ],
            'options' => [
                'label' => 'Status',
                'value_options' => [
                    Post::STATUS_PUBLISHED => 'Published',
                    Post::STATUS_DRAFT => 'Draft',
                ]
            ],
        ]);
        
        // Добавляем кнопку отправки формы
        $this->add([
            'type'  => 'submit',
            'name' => 'submit',
            'attributes' => [                
                'value' => 'Create',
                'id' => 'submitbutton',
            ],
        ]);
    }
    
    /**
     * Этот метод создает фильтр входных данных (используется для фильтрации/валидации).
     */
    private function addInputFilter() 
    {
        
        $inputFilter = new InputFilter();        
        $this->setInputFilter($inputFilter);
        
        $inputFilter->add([
                'name'     => 'title',
                'required' => true,
                'filters'  => [
                    ['name' => 'StringTrim'],
                    ['name' => 'StripTags'],
                    ['name' => 'StripNewlines'],
                ],                
                'validators' => [
                    [
                        'name'    => 'StringLength',
                        'options' => [
                            'min' => 1,
                            'max' => 1024
                        ],
                    ],
                ],
            ]);
        
        $inputFilter->add([
                'name'     => 'content',
                'required' => true,
                'filters'  => [                    
                    ['name' => 'StripTags'],
                ],                
                'validators' => [
                    [
                        'name'    => 'StringLength',
                        'options' => [
                            'min' => 1,
                            'max' => 4096
                        ],
                    ],
                ],
            ]);   
        
        $inputFilter->add([
                'name'     => 'tags',
                'required' => true,
                'filters'  => [                    
                    ['name' => 'StringTrim'],
                    ['name' => 'StripTags'],
                    ['name' => 'StripNewlines'],
                ],                
                'validators' => [
                    [
                        'name'    => 'StringLength',
                        'options' => [
                            'min' => 1,
                            'max' => 1024
                        ],
                    ],
                ],
            ]);
            
        $inputFilter->add([
                'name' => 'status',
                'required' => true,
                'validators' => [
                    [
                        'name' => 'InArray',
                        'options'=> [
                            'haystack' => [Post::STATUS_PUBLISHED, Post::STATUS_DRAFT],
                        ]
                    ],
                ],
            ]);
    }
}

Как видите из этого фрагмента, класс PostForm определяет ZF3-форму с полями заголовка содержимого, тегов и статуса. У него также есть кнопка Submit.

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

12.9.2. Добавление сервиса PostManager

Согласно паттерну DDD , мы размещаем бизнес-логику в моделях сервисов. В нашем примере Blog мы создадим и зарегистрируем сервис PostManager. Этот сервис будет иметь public-метод addNewPost(), содержащий бизнес-логику добавления сущности Post в БД и ее связи с одной или несколькими сущностями Tag.

Сервис PostManager будет содержать бизнес-логику примера Blog. Эта бизнес-логика включает в себя добавление нового поста в блог (но не ограничена этим).

Создайте файл PostManager.php в каталоге Service под корневым каталогом модуля. Поместите в этот файл следующий фрагмент кода:

<?php
namespace Application\Service;

use Application\Entity\Post;
use Application\Entity\Comment;
use Application\Entity\Tag;
use Zend\Filter\StaticFilter;

// Сервис The PostManager, отвечающий за дополнение новых постов.
class PostManager 
{
    /**
     * Doctrine entity manager.
     * @var Doctrine\ORM\EntityManager
     */
    private $entityManager;
  
    // Конструктор, используемый для внедрения зависимостей в сервис.
    public function __construct($entityManager)
    {
        $this->entityManager = $entityManager;
    }
    
    // Этот метод добавляет новый пост.
    public function addNewPost($data) 
    {
        // Создаем новую сущность Post.
        $post = new Post();
        $post->setTitle($data['title']);
        $post->setContent($data['content']);
        $post->setStatus($data['status']);
        $currentDate = date('Y-m-d H:i:s');
        $post->setDateCreated($currentDate);        
        
        // Добавляем сущность в менеджер сущностей.
        $this->entityManager->persist($post);
        
        // Добавляем теги к посту.
        $this->addTagsToPost($data['tags'], $post);
        
        // Применяем изменения к базе данных.
        $this->entityManager->flush();
    }
  
    // Добавляет/обновляет теги в заданном посте.
    private function addTagsToPost($tagsStr, $post) 
    {
        // Удаляем связи тегов (если таковые есть)
        $tags = $post->getTags();
        foreach ($tags as $tag) {            
            $post->removeTagAssociation($tag);
        }
        
        // Добавляем теги к посту
        $tags = explode(',', $tagsStr);
        foreach ($tags as $tagName) {
            
            $tagName = StaticFilter::execute($tagName, 'StringTrim');
            if (empty($tagName)) {
                continue; 
            }
            
            $tag = $this->entityManager->getRepository(Tag::class)
                      ->findOneByName($tagName);
            if ($tag == null)
                $tag = new Tag();
            $tag->setName($tagName);
            $tag->addPost($post);
            
            $this->entityManager->persist($tag);
            
            $post->addTag($tag);
        }
    }    
}

В строках 25-43 находится public-метод addNewPost(), принимающий в качестве аргумента переменную $data (эта переменная должна содержать данные, введенные пользователем сайта в форму). Мы создаем новый экземпляр сущности Post и заполняем его свойства введенными пользователем данными. Затем мы используем метод persist() класса EntityManager (строка 36), чтобы добавить только что созданную сущность в менеджер сущностей. Private-метод addTagsToPost() вызывается (строка 39), чтобы добавить к посту один или несколько тегов, а метод flush() - чтобы применить изменения к базе данных в одно действие.

Private-метод addTagsToPost() содержит алгоритмы для удаления старых связей между постом и тегами (строки 49-52), разбора списка разделенных запятыми тегов (строка 55) и добавления новых тегов к посту (строки 56-73).

Далее, добавим фабрику для класса PostManager. Для этого добавьте файл PostManagerFactory.php под каталог Service/Factory под корневым каталогом модуля и заполните его следующим содержимым:

<?php
namespace Application\Service\Factory;

use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;
use Application\Service\PostManager;

/**
 * Это фабрика для PostManager. Ее целью является
 * инстанцирование сервиса.
 */
class PostManagerFactory implements FactoryInterface
{
    public function __invoke(ContainerInterface $container, 
                    $requestedName, array $options = null)
    {
        $entityManager = $container->get('doctrine.entitymanager.orm_default');
        
        // Инстанцируем сервис и внедряем зависимости.
        return new PostManager($entityManager);
    }
}

И наконец, мы регистрируем сервис PostManager изменив файл конфигурации module.config.php таким образом:

<?php
//...
return [
    //...
    'service_manager' => [
        //...
        'factories' => [
            Service\PostManager::class => Service\Factory\PostManagerFactory::class,
        ],
    ],
    //...
];

12.9.3. Создание действия контроллера и шаблона представления

Для управления постами (добавления, изменения, просмотра и удаления) мы создадим класс контроллера PostController. Внутри этого класса мы создадим метод действия addAction(), который позволит добавлять новый пост в блог (см. код ниже):

class PostController extends AbstractActionController 
{
    /**
     * Менеджер сущностей.
     * @var Doctrine\ORM\EntityManager 
     */
    private $entityManager;
    
    /**
     * Менеджер постов.
     * @var Application\Service\PostManager 
     */
    private $postManager;
    
    /**
     * Конструктор, используемый для внедрения зависимостей в контроллер.
     */
    public function __construct($entityManager, $postManager) 
    {
        $this->entityManager = $entityManager;
        $this->postManager = $postManager;
    }

    /**
     * Это действие отображает страницу "New Post". Она содержит 
     * форму, позволяющую ввести заголовок поста, содержимое и теги.
     * Когда пользователь нажимает кнопку отправки формы, создается
     * новая сущность Post.
     */
    public function addAction() 
    {     
        // Создаем форму.
        $form = new PostForm();
        
        // Проверяем, является ли пост POST-запросом.
        if ($this->getRequest()->isPost()) {
            
            // Получаем POST-данные.
            $data = $this->params()->fromPost();
            
            // Заполняем форму данными.
            $form->setData($data);
            if ($form->isValid()) {
                                
                // Получаем валидированные данные формы.
                $data = $form->getData();
                
                // Используем менеджер постов для добавления нового поста в базу данных.                
                $this->postManager->addNewPost($data);
                
                // Перенаправляем пользователя на страницу "index".
                return $this->redirect()->toRoute('application');
            }
        }
        
        // Визуализируем шаблон представления.
        return new ViewModel([
            'form' => $form
        ]);
    }   
}

В строке 33 фрагмента выше мы создаем экземпляр формы PostForm. В строке 36 мы проверяем, является ли запрос POST-запросом. Если это так, мы заполняем форму входными данными и валидируем данные. Если данные действительны, мы вызываем метод addNewPost() для сервиса PostManager (строка 49) и перенаправляем пользователя к списку постов.

Чтобы инстанцировать PostController, нам понадобится фабрика. Создайте фабрику контроллера, добавив файл PostControllerFactory.php в каталог Controller/Factory под корневым каталогом модуля:

<?php
namespace Application\Controller\Factory;

use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;
use Application\Service\PostManager;
use Application\Controller\PostController;

/**
 * Это фабрика для PostController. Ее целью является инстанцирование
 * контроллера.
 */
class PostControllerFactory implements FactoryInterface
{
    public function __invoke(ContainerInterface $container, 
                           $requestedName, array $options = null)
    {
        $entityManager = $container->get('doctrine.entitymanager.orm_default');
        $postManager = $container->get(PostManager::class);
        
        // Инстанцируем контроллер и внедряем зависимости
        return new PostController($entityManager, $postManager);
    }
}

Затем зарегистрируйте контроллер PostController в файле module.config.php:

<?php
//...
return [
    //...
    'controllers' => [
        //...
        'factories' => [
            Controller\PostController::class => 
                           Controller\Factory\PostControllerFactory::class,
        ],
    ],
    //...
];

Далее, добавьте маршрут posts для созданного контроллера (измените module.config.php, как показано ниже):

<?php
//...
return [
    //...
    'router' => [
        'routes' => [
            //...
            'posts' => [
                'type'    => Segment::class,
                'options' => [
                    'route'    => '/posts[/:action[/:id]]',
                    'constraints' => [
                        'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
                        'id' => '[0-9]*'
                    ],
                    'defaults' => [
                        'controller'    => Controller\PostController::class,
                        'action'        => 'index',
                    ],
                ],
            ],
        ],
    ],
    //...
];

И наконец, добавим шаблон представления. Создайте файл add.phtml в каталоге application/post под каталогом модуля view и поместите в него следующий код:

<?php
$form = $this->form;
$form->get('title')->setAttributes([
    'class'=>'form-control', 
    'placeholder'=>'Enter post title here'
    ]);
$form->get('content')->setAttributes([
    'class'=>'form-control', 
    'placeholder'=>'Type content here',
    'rows'=>6
    ]);
$form->get('tags')->setAttributes([
    'class'=>'form-control', 
    'placeholder'=>'comma, separated, list, of, tags'
    ]);
$form->get('status')->setAttributes([
    'class'=>'form-control'   
    ]);
$form->get('submit')->setAttributes(['class'=>'btn btn-primary']);
$form->prepare();

?>

<h1>Add New Post</h1>

<p>
    Please fill out the following form and click the <i>Create</i> button.
</p>

<div class="row">
    <div class="col-md-6">
        <?= $this->form()->openTag($form); ?>
        
        <div class="form-group">
            <?= $this->formLabel($form->get('title')); ?>
            <?= $this->formElement($form->get('title')); ?>
            <?= $this->formElementErrors($form->get('title')); ?>                  
        </div>
        
        <div class="form-group">
            <?= $this->formLabel($form->get('content')); ?>
            <?= $this->formElement($form->get('content')); ?>
            <?= $this->formElementErrors($form->get('content')); ?>                  
        </div>
        
        <div class="form-group">
            <?= $this->formLabel($form->get('tags')); ?>
            <?= $this->formElement($form->get('tags')); ?>
            <?= $this->formElementErrors($form->get('tags')); ?>                  
            <p class="help-block">Separate tags with comma.</p>
        </div>
        
        <div class="form-group">
            <?= $this->formLabel($form->get('status')); ?>
            <?= $this->formElement($form->get('status')); ?>
            <?= $this->formElementErrors($form->get('status')); ?>                  
        </div>
        
        <?= $this->formElement($form->get('submit')); ?>
        
        <?= $this->form()->closeTag(); ?>
    </div>    
</div>   

Теперь, если вы откроете в браузере URL http://localhost/posts/add, вы должны будете увидеть страницу добавления нового поста Add New Post, как показано ниже на рисунке 12.7:

Рисунок 12.7. Страница Add New Post Рисунок 12.7. Страница Add New Post

Заполнив форму и нажав кнопку Create, вы сохраните новый пост в базе данных. Затем вы сможете посмотреть созданный вами пост в списке постов на странице Home.


Top