Создание блога на Symfony 2.8 lts [ Часть 5.1]

Привет, Хабр! Хочу немного вклиниться в цикл статей и показать как можно простым путём сделать обновление списка комментариев в ленте в режиме реального времени. Как это происходит, например, на github

Навигация по частям руководства

Для реализации этой задачи будем использовать сервис Post Hawk.

Устанавливаем зависимости


Первое, что нам нужно это добавить в composer.json зависимость:

composer require post-hawk/hawk-api-bundle ~1.0


Или если вы переключились непосредственно на ветку 5.1, то достаточно просто

composer update


и дождаться установки пакета.

Далее устанавливаем erlang. Билды под разные системы находятся здесь, или, если есть желание, можно собрать из исходников.

Клонируем сервер и клиент

git clone https://github.com/postHawk/hawk_client.git
git clone https://github.com/postHawk/hawk_server

Собираем rebar:

$ git clone git://github.com/rebar/rebar.git
$ cd rebar
$ ./bootstrap


получившийся файлик (rebar) копируем в оба репозитория. Он нам необходим для сборки проекта.

Проект использует 2 версию rebar. Под rebar3 пока запустить не удаётся

Переходим в папку с сервером, собираем его и запускаем:

cd hawk_server
nano src/hawk_server.app.src
#заполняем данные о пользователе

{env, [
            {statistic, [{use, false}]},
            user, #{
               <<"login">> => <<"symblog">>,
               <<"domain">> => [ %список доменов с которых будут приниматься подключения
                   <<"127.0.0.1:8000">>
               ],
               <<"key">> => <<"very secret key">> %api ключ. Должен совпадать на сервере и клиенте
            }}
]}

mv .erlang .erlang_
rebar get-deps compile
mv .erlang_ .erlang

erl -name 'hawk_server@127.0.0.1' -boot start_sasl  -setcookie test -kernel inet_dist_listen_min 9000  inet_dist_listen_max 9005

Аналогично собираем и запускаем клиент:

cd hawk_client
nano src/hawk_client.app.src
#заполните название server_node, например, 'test_hawk_server@127.0.0.1' и api_key (должен совпадать с серверным). Сохраните файл

{env, [
         {api_key, <<"very secret key">>},
         {server_node, 'hawk_server@127.0.0.1'}
     ]}

mv .erlang .erlang_
./rebar get-deps compile
mv .erlang_ .erlang
erl -name 'hawk_client@127.0.0.1' -boot start_sasl  -setcookie test -kernel inet_dist_listen_min 9000  inet_dist_listen_max 9005

Если нужно запустить процесс в фоне, просто добавьте к набору параметров опцию -detached
Для пользователей windows утилиту запуска стоит изменить с erl на werl


Дорабатываем блог


Кофигурация бандла:

//app/AppKernel.php
$bundles = array(
    ...
    new Hawk\ApiBundle\HawkApiBundle(),
    new FOS\JsRoutingBundle\FOSJsRoutingBundle(),
);

#app/config/config.yml
hawk_api:
    client:
        host: '%hawk_api.client.host%' #ip или домен
        port: '%hawk_api.client.port%' #порт, который слушает клиент
        key: '%hawk_api.client.key%'

#app/config/parameters.yml
parameters:
    hawk_api.client.host: 127.0.0.1
    hawk_api.client.port: 7777
    hawk_api.client.key: 'very secret key'

#app/config/routing.yml
hawk:
    resource: '@HawkApiBundle/Controller/'
    prefix:   /hawk

fos_js_routing:
    resource: "@FOSJsRoutingBundle/Resources/config/routing/routing.xml"

Ставим assets:

php app/console assets:install --symlink web

Дорабатываем контроллер:

...
use Blogger\BlogBundle\Entity\Blog;
use Hawk\ApiBundle\Event\GroupMessage;


В функцию добавления комментария помещаем отправку уведомлений:

...
$em->persist($comment);
$em->flush();

$this->sendNotification($comment, $blog);

return $this->redirect($this->generateUrl('BloggerBlogBundle_blog_show', array(
...


ну и добавляем саму функцию:

Код
/**
 * Отправка уведомления о новом комментарии
 * @param Comment $comment комментарий
 * @param Blog $blog блог
 */
private function sendNotification(Comment $comment, Blog $blog)
{
    //формируем тело комментария
    $comment_text = $this->renderView('BloggerBlogBundle:Comment:index.html.twig', [
        'comments'  => [$comment]
    ]);

    //формируем сообщение
    $gMessage = new GroupMessage();
    $gMessage
        ->setFrom('comment_demon')
        ->setGroups(['blog_' . $blog->getId()])
        ->setText(['comment' => $comment_text])
        ->setEvent('new_comment') //будет сгенерирован на клиенте
    ;

    //отсылаем
    $api = $this
        ->container
        ->get('event_dispatcher')
        ->dispatch(GroupMessage::NEW_MESSAGE, $gMessage)
        ->getResult() //HawkApi
    ;

    //если возникли ошибки пишем их в лог
    if($api->hasErrors()){
        $logger = $this->get('logger');
        $logger->error('Error sending message: ' . print_r($api->getErrors(), 1));
    }
}


Немного остановлюсь на том, что здесь происходит. Отправка сообщений через сервис возможна двумя способами. Первый это как на примере выше, через систему событий symfony. Создаётся объект сообщения (простого или группового) и посылается с определённым типом события. Второй вариант — это отправка непосредственно с помощью апи, которое можно получить из контейнера:

$api = $this->get('hawk_api.api')->getApi();
$api
    ->registerUser($id)
    ->sendMessage($from, $to, $text, $event)
    ->execute()
    ->getResult('sendMessage')
;

Дорабатываем шаблоны:


В src/Blogger/BlogBundle/Resources/views/Blog/show.html.twig меняем строку

    


на

    


, так как нам необходим будет id блога для подписки пользователя.

Подключаем скрипты:

{#src/Blogger/BlogBundle/Resources/views/layout.html.twig#}
{% block javascripts %}
    {% javascripts
        '@BloggerBlogBundle/Resources/public/js/*'
        output='js/plugins,js'
        filter='?yui_js'
        %}
        
    {% endjavascripts %}
    {% javascripts
        '../vendor/post-hawk/hawk-api/Resources/public/js/hawk_api.min.js'
    %}
        
    {% endjavascripts %}
    
    
{% endblock %}

Ну и пишем немного js


код
$(document).ready(function () {
    connectToHawk();
});

function connectToHawk()
{
    //отправляем запрос за токеном
    $.post(Routing.generate('hawk_token', {
        useSessionId: 1
    }), {}, function (data) {
        if(data.errors === false){
            //инициализируем подключение
            HAWK_API.init({
                user_id: data.result.id,
                token: data.result.token,
                url: data.result.ws,
                debug: true
            });

            //подписываемся на новые комментарии
            HAWK_API.bind_handler('new_comment', function(e, msg){
                //игнорируем служебные сообщения
                if(msg.from === 'hawk_client')
                    return;
                //находим список комментариев и последний из них
                //создаём объект нового
                var $comments = $('.previous-comments'),
                    $last = $comments.find('.comment:last'),
                    cls = 'odd',
                    $comment = $(msg.text.comment)
                    ;

                //определяемся с классом комментария
                if($last.size()){
                    cls = $last.hasClass('odd') ? 'even' : 'odd';
                }

                $comment
                    .removeClass('odd')
                    .addClass(cls)
                    .hide()
                ;
                //показываем
                $comments.append($comment);
                $comment.show('normal')
            });

            //подписываемся на новые комментарии
            HAWK_API.bind_handler('open', function(e, msg){
                var id = $('.blog').data('blogId');
                //добавляем пользователя в группу блога
                //если её нет, то она будет создана с публичным доступом
                HAWK_API.add_user_to_group(['blog_' + id]);
             });
        } else {
            if(data.errors !== 'no_user') {
                console.error(data);
            }
        }
    });
}


Первое, что мы делаем, это отправляем запрос за токеном для подключения к серверу сообщений. Так как пользователь у нас не аторизованный, то говорим контроллеру использовать в качестве id пользователя, id его сессии.
Если всё хорошо, то мы получим в ответе: id пользователя, токен подключения и адрес сервера.
Далее, инициализируем подключение и подписываемся на события. В данном случае, нас интересуют два из них. Первое — open, возникает после успешного подключения к сокету. Второе — new_comment (его мы передаём при отправке сообщения), непосредственно новый комментарий.

Вот собственно и всё. Благодарю за внимание. Код этой части доступен на github в соответствующей ветке.

© Habrahabr.ru