Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Пример разработки блога на Zend Framework 2. Часть 2. Модуль MyBlog

Anna | 31.05.2014 | нет комментариев

Это вторая из 3 частей статьи, посвященной разработке простого приложения при помощи Zend Framework 2.В первой части я разглядел конструкцию ZendSkeletonApplication, а в этой части приведу пример разработки простого модуля. Третья часть будет посвящена работе с пользователями и шаблонизатором Twig.

Установка и настройка дополнительных модулей

Первым делом хочу подметить, что установка стороннего модуля в Zend Framework обыкновенно состоит из приблизительно таких четырех шагов:

  1. добавляем соответствующую строчку в composer.json, Дабы известить Композеру о новом модуле,
  2. исполняем команду php composer.phar update, Дабы Композер загрузил новейший модуль и при необходимости перегенерировал автолоад файлы,
  3. добавляем новейший модуль в список modules в файле config/application.config.php,
  4. при необходимости, размещаем конфигурационный файл модуля (обыкновенно пример такого файла находится в папке config модуля) в config/autoload и делаем в нем нужные правки.

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

Давайте начнем с установки простого, но пригодного модуля Zend Developer Tools.

Zend Developer Tools

Zend Developer Tools — это комфортный тулбар, содержащий пригодную для разработчика информацию о сделанной странице: число и список запросов к БД, список ролей нынешнего пользователя, использованные Entity, загруженная конфигурация сайта и т.д. Разумеется, тулбар может быть расширен всякий иной вспомогательной информацией. Обнаружить его дозволено здесь:github.com/zendframework/ZendDeveloperTools.

Дабы установить тулбар вначале добавим строчку:

"zendframework/zend-developer-tools": "dev-master",

в файл composer.json в корне плана и после этого исполним команду php composer.phar update в корне плана.

После этого в файл config/application.config.php в массив modules необходимо добавить элемент ZendDeveloperTools:

'modules' => array(
    'Application',
    'ZendDeveloperTools',
),

Сейчас осталось скопировать файл vendor/zendframework/zend-developer-tools/config/zenddevelopertools.local.php.dist в папку config/autoload нашего плана и переименовать его, скажем, в zenddevelopertools.local.php (часть имени до local.php по огромному счету значения не имеет).

Всё, сейчас, по умолчанию, внизу всех страниц выводится информация о затраченных на генерацию страницы источниках, конфигурация плана и т.п.

Хочу обратить внимание на то, что по умолчанию тулбар будет доступен каждому посетителям сайта по этому в production окружении его применять не стоит.

Нынешняя версия приложения доступна на Гитхабе в репозитории плана с тэгом zenddevelopertools:github.com/romka/zend-blog-example/tree/zenddevelopertools

Doctrine ORM

Для интеграции с Концепцией потребуются модули DoctrineModule и DoctrineORMModule (https://github.com/doctrine/DoctrineModule и github.com/doctrine/DoctrineORMModule).

Добавим в секцию require файла composer.json строчки:

"doctrine/common": ">=2.1",
"doctrine/doctrine-orm-module": "0.7.*"

и исполним в консоли команду php composer.phar update.

Модуль DoctrineModule дозволено не указывать очевидно в нашем composer.json, так как эта связанность прописана на ярусе модуля DoctrineORMModule.

Сейчас необходимо в директории config/autoload поместить файл doctrine.local.php с параметрами доступа к БД, тот, что будет применяться Концепцией, его содержимое должно быть приблизительно таким:

<?php
return array(
    'doctrine' => array(
        'connection' => array(
            'orm_default' => array(
                'driverClass' =>'DoctrineDBALDriverPDOMySqlDriver',
                'params' => array(
                    'host'     => 'localhost',
                    'port'     => '3306',
                    'user'     => 'username',
                    'password' => 'pass',
                    'dbname'   => 'dbname',
                )
            )
        ),
    ),
);

Сейчас если мы перезагрузим страницу нашего сайта, то внизу страницы в Zend девлопер тулбаре увидим два новых блока, показывающих число исполненных запросов и список маппингов к БД. Оба значения равны нулю, так как маппинги мы пока не сделали и, как следствие, запросов к базе нет.

В этом туториале я хочу разработать примитивный блог и теперь настало время написать первые строчки кода нового модуля.

Модуль MyBlog

В каталоге modules сделаем следующие директории и файлы:

MyBlog/
    config/
        module.config.php
    src/
        MyBlog/
            Entity/
                BlogPost.php
    Module.php

Содержимое файла Module.php должно быть таким:

<?php
namespace MyBlog;

class Module
{
  public function getAutoloaderConfig()
  {
    return array(
      'ZendLoaderStandardAutoloader' => array(
        'namespaces' => array(
          __NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
        ),
      ),
    );
  }

  public function getConfig()
  {
    return include __DIR__ . '/config/module.config.php';
  }
}

Файл аналогичен тому, что применяется в модуле Application, мы говорим ядру фреймворка где искать конфигурационный файл модуля и файлы исходников.

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

Файл src/MyBlog/Entity/BlogPost.php является связью (маппингом) между Концепцией и базой данных и о нем необходимо побеседовать подробнее.

BlogPost.php

Всякий блогпост в моем примере будет содержать следующие поля:

  • заголовок,
  • тело блогпоста,
  • id автора (0 для анонимов),
  • ранг (опубликовано/не опубликовано)
  • дата публикации.

Для простоты я не стану в этом туториале заморачиваться с тэгами, комментариями и другими присущными блогам фичами.

Данный файл объявляет класс BlogPost, тот, что содержит изложения полей блогпоста и способов доступа к ним. Полную версию файла вы можете посмотреть на Гитхабе (https://github.com/romka/zend-blog-example/blob/master/module/MyBlog/src/MyBlog/Entity/BlogPost.php), вот так выглядит его часть:

<?php
namespace MyBlogEntity;

use DoctrineCommonCollectionsArrayCollection;
use DoctrineORMMapping as ORM;

class BlogPost
{
  /**
   * @var int
   * @ORMId
   * @ORMColumn(type="integer")
   * @ORMGeneratedValue(strategy="AUTO")
   */
  protected $id;

  /**
   * @var string
   * @ORMColumn(type="string", length=255, nullable=false)
   */
  protected $title;

  /**
   * Get id.
   *
   * @return int
   */
  public function getId()
  {
    return $this->id;
  }

  /**
   * Set id.
   *
   * @param int $id
   *
   * @return void
   */
  public function setId($id)
  {
    $this->id = (int) $id;
  }

  /**
   * Get title.
   *
   * @return string
   */
  public function getTitle()
  {
    return $this->title;
  }

  /**
   * Set title.
   *
   * @param string $title
   *
   * @return void
   */
  public function setTitle($title)
  {
    $this->title = $title;
  }

}

Всякая переменная в этом классе станет полем в БД, параметры полей задаются в аннотациях, которые будут прочитаны Концепцией (приблизительно вот так: php.net/manual/en/reflectionclass.getdoccomment.php, классDoctrineCommonAnnotationsAnnotationReader способ getClassAnnotations()).

Сейчас в конфигурационный файл модуля config/module.config.php дозволено добавить информацию о нашей новойEntity, которая будет использована Концепцией:

return array(
    'doctrine' => array(
        'driver' => array(
            'myblog_entity' => array(
                'class' =>'DoctrineORMMappingDriverAnnotationDriver',
                'paths' => array(__DIR__ . '/../src/MyBlog/Entity')
            ),
            'orm_default' => array(
                'drivers' => array(
                    'MyBlogEntity' => 'myblog_entity',
                )
            )
        )
    ),
);

И осталось добавить модуль MyBlog в список энергичных модулей в application.config.php.

Мы завершили настройку сущности BlogPost и теперь необходимо сделать соответствующую таблицу в базе данных, для этого воспользуемся консольной утилитой, поставляющейся в комплекте с Концепцией. В корне плана исполним команду:

./vendor/bin/doctrine-module orm:info

И итогом должно быть сообщение вида:

Found 1 mapped entities:
[OK]   MyBlogEntityBlogPost

Позже того как мы удостоверились в том, что Концепция видит наш объект BlogPost исполним команду:

./vendor/bin/doctrine-module orm:validate-schema

В итоге должна возвратиться оплошность вида:

[Mapping]  OK - The mapping files are correct.
[Database] FAIL - The database schema is not in sync with the current mapping file.

Это и разумно, так как наша база данных все еще пуста и теперь мы сделаем надобную таблицу командой:

./vendor/bin/doctrine-module orm:schema-tool:update --force

Её итогом будет дальнейший итог:

Updating database schema...
Database schema updated successfully! "1" queries were executed

И сейчас вызов команды:

./vendor/bin/doctrine-module orm:validate-schema

вернет итог:

[Mapping]  OK - The mapping files are correct.
[Database] OK - The database schema is in sync with the mapping files.

Если теперь обновить страницу нашего сайта, то в тулбаре внизу страницы мы увидим, что Концепция видит один маппинг MyblogEntityBlogPost.

Начальные коды нынешней версии плана дозволено обнаружить в репозитории плана на Гитхабе с тэгомblogpost_entitygithub.com/romka/zend-blog-example/tree/blogpost_entity.

Сейчас, когда у нас есть сущность для работы с блогпостами дозволено перейти к написанию своего первого контроллера, реализующего форму добавления блогпоста.

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

В директории src/MyBlog модуля сделаем два новых каталога с файлами:

Controller/
    BlogController.php
Form/
    BlogPostForm.php
    BlogPostInputFilter.php

Дальше в конфигурационный файл модуля необходимо добавить элементы, объявляющие список контроллеров модуля, маршруты и путь к директории с образцами:

'controllers' => array(
    'invokables' => array(
        'MyBlogControllerBlogPost' => 'MyBlogControllerBlogController',
    ),
),

'router' => array(
    'routes' => array(
        'blog' => array(
            'type'    => 'segment',
            'options' => array(
                'route'    => '/blog[/][:action][/:id]',
                'constraints' => array(
                    'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
                    'id'     => '[0-9] ',
                ),
                'defaults' => array(
                    'controller' => 'MyBlogControllerBlogPost',
                    'action'     => 'index',
                ),
            ),
        ),
    ),
),

'view_manager' => array(
    'template_path_stack' => array(
        __DIR__ . '/../view',
    ),
),

Исходя из заданных выше настроек, все страницы нашего блога будут иметь урлы вида blog/[action]/[id](элементы пути в квадратных скобках не непременны).

Файл BlogPostForm.php будет содержать форму, которая будет применяться для добавления/редактирования блогпоста, давайте сотворим эту форму.

BlogPostForm.php

В самом простом случае код формы будет выглядеть вот так (это не полный начальный код формы, целиком его дозволено увидеть здесь: github.com/romka/zend-blog-example/blob/master/module/MyBlog/src/MyBlog/Form/BlogPostForm.php):

class BlogPostForm extends Form
{
    public function __construct($name = null)
    {
        parent::__construct('blogpost');
        $this->setAttribute('method', 'post');
        $this->add(array(
            'name' => 'id',
            'type' => 'Hidden',
        ));
        $this->add(array(
            'name' => 'title',
            'type' => 'Text',
            'options' => array(
                'label' => 'Title',
            ),
            'options' => array(
                'min' => 3,
                'max' => 25
            ),
        ));
        $this->add(array(
            'name' => 'text',
            'type' => 'Textarea',
            'options' => array(
                'label' => 'Text',
            ),
        ));
        $this->add(array(
            'name' => 'state',
            'type' => 'Checkbox',
        ));
        $this->add(array(
            'name' => 'submit',
            'type' => 'Submit',
            'attributes' => array(
                'value' => 'Save',
                'id' => 'submitbutton',
            ),
        ));
    }
}

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

BlogController.php

Полный код контроллера вы можете посмотреть в репозитории (https://github.com/romka/zend-blog-example/blob/master/module/MyBlog/src/MyBlog/Controller/BlogController.php), ниже его ключевая часть:

class BlogController extends AbstractActionController
{

    public function indexAction()
    {
        return new ViewModel();
    }

    public function addAction()
    {
        $form = new MyBlogFormBlogPostForm();
        $form->get('submit')->setValue('Add');

        $request = $this->getRequest();
        if ($request->isPost()) {
            $form->setData($request->getPost());

            if ($form->isValid()) {
                $objectManager = $this->getServiceLocator()->get('DoctrineORMEntityManager');

                $blogpost = new MyBlogEntityBlogPost();

                $blogpost->exchangeArray($form->getData());

                $blogpost->setCreated(time());
                $blogpost->setUserId(0);

                $objectManager->persist($blogpost);
                $objectManager->flush();

                // Redirect to list of blogposts
                return $this->redirect()->toRoute('blog');
            }
        }
        return array('form' => $form);
    }
}

Важным для нас является код экшена addAction (имена всех экшенов обязаны создаваться по маске nameAction()). В нем мы вначале создаем объект формы и заменяем текст кнопки submit на ней (у нас одна и та же форма будет применяться и для создания, и для редактирования блогпостов, по этому тексты на этой кнопке комфортно иметь различные):

$form = new MyBlogFormBlogPostForm();
$form->get('submit')->setValue('Add');

После этого, в случае если форма прошла валидацию (а валидацию теперь она пройдет в любом случае, так как валидаторов у нас пока нет) мы создаем экземпляр класса MyBlogEntityBlogPost(), тот, что является связью между нашим приложением и БД, наполняем сделанный объект данными и сберегаем их в БД:

$blogpost->exchangeArray($form->getData());

$blogpost->setCreated(time());
$blogpost->setUserId(0);

$objectManager->persist($blogpost);
$objectManager->flush();

Нынешнюю версию образца, отвечающего за отображение формы, дозволено увидеть по ссылкеgithub.com/romka/zend-blog-example/blob/blogpost_form_1/module/MyBlog/view/my-blog/blog/add.phtml.

Если cейчас испробовать сберечь пустую форму, то Концепция вернет сообщение об ошибке вида:

An exception occurred while executing 'INSERT INTO blogposts (title, text, userId, created, state) VALUES (?, ?, ?, ?, ?)' with params [null, null, 0, 1377086855, null]:

SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'title' cannot be null

Это верно, чай у нас есть только одно поле, помеченное как nullable=«true» — это поле state, а все остальные обязаны быть заполнены. Давайте добавим к форме инпут фильтры и валидаторы, Дабы перехватывать сходственные ошибки еще до попытки сберечь данные (на ярусе нашего приложения, а не БД), Дабы у пользователя была вероятность поправить ошибку.

Валидация форм

В ранее сделанном файле BlogPostInputFilter.php поместим такой код (полная версия на Гитхабе:github.com/romka/zend-blog-example/blob/master/module/MyBlog/src/MyBlog/Form/BlogPostInputFilter.php):

class BlogPostInputFilter extends InputFilter
{
    public function __construct()
    {
        $this->add(array(
            'name' => 'title',
            'required' => true,
            'validators' => array(
                array(
                    'name' => 'StringLength',
                    'options' => array(
                        'min' => 3,
                        'max' => 100,
                    ),
                ),
            ),
            'filters' => array(
                array('name' => 'StripTags'),
                array('name' => 'StringTrim'),
            ),

        ));

        $this->add(array(
            'name' => 'text',
            'required' => true,
            'validators' => array(
                array(
                    'name' => 'StringLength',
                    'options' => array(
                        'min' => 50,
                    ),
                ),
            ),
            'filters' => array(
                array('name' => 'StripTags'),
                array('name' => 'StringTrim'),
            ),
        ));

        $this->add(array(
            'name' => 'state',
            'required' => false,
        ));
    }
}

Полагаю, что толк этих строк должен быть подсознательно внятным: для полей title и text мы добавляем инпут фильтры, которые удалят из текстов все html тэги (фльтр StripTags) и обрежут пробелы по краям строк (StringTrim), а также добавляем валидаторы, определяющие значения минимальной и максимальной длины полей (StringLength).

Осталось присоединить новейший фильтр к форме, добавив в класс формы строчку:

$this->setInputFilter(new MyBlogFormBlogPostInputFilter());

Сейчас форма не пройдет валидацию, если в нее введены некорректные данные.

View плагины

Позже того как блогпост удачно сохранен (либо не сохранен) мы перенаправляем пользователя на страницу /blog, на которой в грядущем у нас будет полный список блогпостов. Хотелось бы не легко сделать редирект, но и вывести на экран сообщение об удачно исполненном действии.

Добавить такие сообщения дозволено с поддержкой способов:

$this->flashMessenger()->addMessage($message);
$this->flashMessenger()->addErrorMessage($message);

Извлечь сообщения, добавленные таким методом, дозволено в контроллере либо в phtml-образцах таким образом:

$this->flashMessenger()->getMessages();
$this->flashMessenger()->getErrorMessages();

Задача в том, что неудобно (а в Twig-образцах, которые мы будем применять позднее, и совсем немыслимо) вызывать PHP-код для итога сообщений. По этому мы напишем маленький View-плагин, тот, что сумеет одной строчкой выводить на экран все сообщения.

Для этого в директории srcMyBlog модуля сделаем такие директории и файлы:

View
    Helper
        ShowMessages.php

Содержимое ShowMessages.php дозволено посмотреть здесь: github.com/romka/zend-blog-example/blob/master/module/MyBlog/src/MyBlog/View/Helper/ShowMessages.php, оно не дюже увлекательно, я тут легко получаю список сообщений, форматирую и возвращаю готовый html-код для их отображения.

Осталось сделать три действия:

  1. зарегистрировать View-плагин,
  2. добавить его применение в образец,
  3. и вывести сообщения о успешном/неудачном сохранении формы.

Дабы зарегистрировать плагин добавим в настройки модуля в сецкию view_helper => invokables строчку:

'view_helpers' => array(
    'invokables' => array(
        'showMessages' => 'MyBlogViewHelperShowMessages',
    ),
),

В образцы добавим итог сообщений:

print $this->showMessages();

Для итога сообщений на экран добавим в контроллер такие строчки:

$message = 'Blogpost succesfully saved!';
$this->flashMessenger()->addMessage($message);

Сейчас у нас есть вероятность выводить пользователю системные сообщения.

Эту версию приложения вы можете обнаружить в гит-репозитории с тэгом blogpost_form_1:github.com/romka/zend-blog-example/tree/blogpost_form_1.

На нынешнем этапе у нас есть:

  1. сущность для связи приложения и БД, сделанная при помощи Концепции,
  2. контроллер обслуживающий страницу добавления блогпоста,
  3. форма добавления блогпоста с инпут фильтрами и валидаций,
  4. свой кастомный View-плагин для итога сообщений на экран.

Сейчас давайте добавим страницы одного блогпоста, списка блогпостов и формы редактирования/удаления поста.

Страница блогпоста

Добавим в контроллер BlogpostController новое действие view:

public function viewAction()
{
    $id = (int) $this->params()->fromRoute('id', 0);
    if (!$id) {
        $this->flashMessenger()->addErrorMessage('Blogpost id doesn't set');
        return $this->redirect()->toRoute('blog');
    }

    $objectManager = $this->getServiceLocator()->get('DoctrineORMEntityManager');

    $post = $objectManager
        ->getRepository('MyBlogEntityBlogPost')
        ->findOneBy(array('id' => $id));

    if (!$post) {
        $this->flashMessenger()->addErrorMessage(sprintf('Blogpost with id %s doesn't exists', $id));
        return $this->redirect()->toRoute('blog');
    }

    $view = new ViewModel(array(
        'post' => $post->getArrayCopy(),
    ));

    return $view;
}

Данный экшен доступен по адресу blog/view/ID. В нем мы вначале мы проверяем, что в URL’е задан id блогпоста, если это не так, то возвращаем ошибку и редиректим пользователя на страницу со списком блогпостов. Если id указан, то мы извлекаем пост из базы и передаем его в образец.

В качестве имени образца по умолчанию применяется имя контроллера, по этому сейчас в директории модуля view/my-blog/blog необходимо сделать файл view.phtml с приблизительно вот таким содержимым:

<?php
    print $this->showMessages();
    print '<h1>' . $post['title'] . '</h1>';
    print '<div>' . $post['text'] . '</div>';

Список блогпостов

Обновим код нашего indexAction до такого вида:

public function indexAction()
{
    $objectManager = $this->getServiceLocator()->get('DoctrineORMEntityManager');

    $posts = $objectManager
        ->getRepository('MyBlogEntityBlogPost')
        ->findBy(array('state' => 1), array('created' => 'DESC'));

    $view = new ViewModel(array(
        'posts' => $posts,
    ));

    return $view;
}

Тут мы выбираем все опубликованные блогпосты (state == 1), сортируем их по дате публикации и передаем в образец index.phtml github.com/romka/zend-blog-example/blob/blogpost_form_2/module/MyBlog/view/my-blog/blog/index.phtml. В образце выводятся заголовки блогпостов и ссылки на их редактирование и удаление.

Малое отхождение

Выше, при создании формы я позабыл добавить поле userId, в котором хранится айдишник автора блогпоста. Так как теперь регистрации/авторизации в нашем блоге нет, это поле по умолчанию заполняется нулем, но в грядущем оно сгодится, по этому теперь я добавил в форму hidden поле userId.

Помимо того, я добавил к форме Csrf-токен (поле security), тот, что должен защитить форму от подделки. По умолчанию данный токен формируется на основании пользовательской сессии и соли и живет 300 секунд (ZendFormElementCsrf.php), но может быть (и по классному должен быть) переопределен и к нему как минимум должна быть добавлена связанность от ip посетителя.

Редактирование блогпоста

Для редактирования поста мы будем применять теснее существующую форму. В контроллере нужно сделать действие editAction(), которое будет создавать форму, наполнять её существующими данными и отдавать пользователю. Данный экшен является смесью addAction(), в части работы с формой, и viewAction(), в части выборки данных github.com/romka/zend-blog-example/blob/blogpost_form_2/module/MyBlog/src/MyBlog/Controller/BlogController.php#L95.

Вот самая увлекательная часть этого контроллера:

if ($form->isValid()) {
    $objectManager = $this->getServiceLocator()->get('DoctrineORMEntityManager');

    $data = $form->getData();
    $id = $data['id'];
    try {
        $blogpost = $objectManager->find('MyBlogEntityBlogPost', $id);
    }
    catch (Exception $ex) {
        return $this->redirect()->toRoute('blog', array(
            'action' => 'index'
        ));
    }

    $blogpost->exchangeArray($form->getData());

    $objectManager->persist($blogpost);
    $objectManager->flush();

    $message = 'Blogpost succesfully saved!';
    $this->flashMessenger()->addMessage($message);

    // Redirect to list of blogposts
    return $this->redirect()->toRoute('blog');
}

Тут мы загружаем из БД блогпост, базируясь на id, тот, что пришел в форме, обновляем данные:

$blogpost->exchangeArray($form->getData());

и кладем обновленный блогпост в базу:

$objectManager->persist($blogpost);
$objectManager->flush();

Удаление блогпостов

Удаление блогпоста задача банальная, довольно вывести пользователю форму с вопросом вида ”Действительно ли вы хотите удалить пост?” и в случае если пользователь нажмет кнопку “Да”, исполнить соответствующие действия.

Код соответствующего контроллера и образца дозволено посмотреть на Гитхабе: github.com/romka/zend-blog-example/blob/blogpost_form_2/module/MyBlog/src/MyBlog/Controller/BlogController.php#L161.

Исходники с тэгом blogpost_form_2 (https://github.com/romka/zend-blog-example/tree/blogpost_form_2) содержат формы редактирования и удаления блогпоста, список постов и соответствующие образцы.

На этом я бы хотел закончить вторую часть статьи. В третьей части мы займёмся работой с пользователями и прикрутим к плану шаблонизатор Twig.

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB