Чат на Laravel 5.6 + Ratchet. Часть 1. Подготовительные работы

Кто из вас не мечтал о собственном уютном чатике с преферансом и куртизанками? Фреймворк Laravel, позволяет создать его, не сильно напрягаясь, используя Web Socket и библиотеку Ratchet. На установке Ratchet особо останавливаться не буду. По этому поводу много чего написано и есть например замечательные уроки от Дмитрия Афанасьева

В принципе всё что нужно для работы Web Socket сервера можно смело взять от туда.

/app/Classes/Socket/Base/BasePusher.php Базовый класс нашего пушера

<?php

namespace App\Classes\Socket\Base;

use Ratchet\ConnectionInterface;
use Ratchet\Wamp\WampServerInterface;

class BasePusher implements WampServerInterface
{
    protected $subcribed_topics = [];

    /**
     * @return array
     */
    public function getSubcribedTopics()
    {
        return $this->subcribed_topics;
    }

    public function addSubscribedTopic($topic){
        $this->subcribed_topics[$topic->getId()] = $topic;
    }

    public function onSubscribe(ConnectionInterface $conn, $topic)
    {
        $this->addSubscribedTopic($topic);
    }

    public function onUnSubscribe(ConnectionInterface $conn, $topic)
    {
        // TODO: Implement onUnSubscribe() method.
    }

    public function onOpen(ConnectionInterface $conn)
    {
        echo ('New connection ('.$conn->resourceId.')'.PHP_EOL);
    }

    public function onClose(ConnectionInterface $conn)
    {
        echo ('Connection ('.$conn->resourceId.') has disconnected'.PHP_EOL);
    }

    public function onCall(ConnectionInterface $conn, $id, $topic, array $params)
    {
        $conn->callError($id, $topic, 'You are not allowed to make calls')->close();
    }

    public function onPublish(ConnectionInterface $conn, $topic, $event, array $exclude, array $eligible)
    {
        $conn->close();
    }
    public function onError(ConnectionInterface $conn, \Exception $e)
    {
        echo ('Error: '.$e->getMessage().PHP_EOL);
        $conn->close();
    }
}

/app/Classes/Socket/Pusher.php — собственно пушер

<?php

namespace App\Classes\Socket;

use App\Classes\Socket\Base\BasePusher;
use ZMQContext;
use Illuminate\Support\Facades\Config;

class Pusher extends BasePusher
{
    static function sendDataToServer(array $data)
    {
        $context = new ZMQContext;
        $socket  = $context->getSocket(\ZMQ::SOCKET_PUSH, 'my_pusher');
        $ip      = Config::get('app.ip');

        $socket->connect('tcp://' . $ip . ':5555');

        $data = json_encode($data);

        $socket->send($data);
    }

    public function broadcast($json_data_to_send)
    {
        $data_to_send      = json_decode($json_data_to_send);
        $subscribed_topics = $this->getSubcribedTopics();
        if(is_object($data_to_send)){
            if (isset($subscribed_topics[$data_to_send->topic_id])) {
                $topic = $subscribed_topics[$data_to_send->topic_id];
                $topic->broadcast($data_to_send);
            }
        }
    }
}

в /config/app.php прописываем ip адрес для нашего соединения

'ip' => env('APP_IP', '192.168.10.10'), //стандартный IP адрес для Homestead

ну и в /.env можно прописать например

APP_IP='192.168.10.10'

Далее напишем наш Пуш сервер. /app/Console/Commands/PushServer.php

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use App\Classes\Socket\Pusher;
use React\EventLoop\Factory as ReactLoop;
use React\ZMQ\Context as ReactContext;
use React\Socket\Server as ReactServer;
use Ratchet\Wamp\WampServer;
use Illuminate\Support\Facades\Config;

class PushServer extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'push_server:serve';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Push server started';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        $loop    = ReactLoop::create();
        $pusher  = new Pusher;
        $context = new ReactContext($loop);
        $ip      = Config::get('app.ip');
        echo($ip);

        $pull = $context->getSocket(\ZMQ::SOCKET_PULL);
        $pull->bind('tcp://' . $ip . ':5555');
        $pull->on('message', [$pusher, 'broadcast']);

        $web_sock = new ReactServer('tcp://0.0.0.0:8080', $loop); //обратите внимание! Здесь синтаксис изменился относительно описанного в видео для Laravel 5.4
        $web_server = new IoServer(
            new HttpServer(
                new WsServer(
                    new WampServer($pusher)
                )
            ),
            $web_sock
        );
        $this->info('Push server is running');
        $loop->run();
    }
}

Веб сервер готов! Можете запустить его командой

php artisan push_server:serve

Теперь создадим три таблицы для нашего чата:

1) Таблица «Беседы» (комнаты, обсуждения). Миграция:

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateConversationsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('conversations', function (Blueprint $table) {
            $table->increments('id');
            $table->string('title')->nullable();
            $table->integer('owner_id')->nullable();
            $table->integer('is_visible')->nullable()->default(1);
            $table->timestamps();
        });
    }
    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('conversations');
    }
}

2) Сообщения:

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateMessagesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('messages', function (Blueprint $table) {
            $table->increments('id');
            $table->text('text')->nullable();
            $table->integer('conversation_id')->nullable();
            $table->integer('is_visible')->default(1)->nullable();
            $table->integer('attachment_id')->nullable();
            $table->integer('author_id')->nullable();
            $table->timestamps();
        });
    }
    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('messages');
    }
}

3) Подписчики:

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateFollowersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('followers', function (Blueprint $table) {
            $table->increments('id');
            $table->string('type')->nullable();
            $table->integer('follower_id')->nullable();
            $table->integer('post_id')->nullable();
            $table->timestamps();
        });
    }
    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('followers');
    }
}

Так как функционал подписчиков можно использовать не только для чата, зададим ему поле 'type', на всякий случай.

Что-то получается слишком длинно. Так что ждите разобью пожалуй статью на несколько частей.

Вторая часть
Третья часть
Четвертая часть

5 thoughts on “Чат на Laravel 5.6 + Ratchet. Часть 1. Подготовительные работы”

    1. да. забыл один момент. в app/Console/Kernel.php надо прописать

      class Kernel extends ConsoleKernel

      {

      /**

      * The Artisan commands provided by your application.

      *

      * @var array

      */

      protected $commands = [

      \App\Console\Commands\PushServer::class,

      ];

      теоретически должно заработать

  1. Добрый день, подскажите пожалуйста.

    php artisan push_server:serve

    UnexpectedValueException : The stream or file «/home/mdma/public_html/test3/storage/logs/laravel.log» could not be opened: failed to open stream: Permission denied

    at /home/mdma/public_html/test3/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php:107

    103| }

    104| restore_error_handler ();

    105| if (!is_resource ($this->stream)) {

    106| $this->stream = null;

    > 107| throw new \UnexpectedValueException (sprintf ('The stream or file «%s» could not be opened: '.$this->errorMessage, $this->url));

    108| }

    109| }

    110|

    111| if ($this->useLocking) {

    Exception trace:

    1 Monolog\Handler\StreamHandler::write ()

    /home/mdma/public_html/test3/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php:37

    2 Monolog\Handler\AbstractProcessingHandler::handle ()

    /home/mdma/public_html/test3/vendor/monolog/monolog/src/Monolog/Logger.php:337

    Please use the argument -v to see more details.

    1. С правами разобрался, очень странно они назначились, непонятно как. Дальше — хуже) php artisan push_server:serve

      Symfony\Component\Debug\Exception\FatalThrowableError : Class 'React\ZMQ\Context' not found

      at /home/mdma/public_html/test3/app/Console/Commands/PushServer.php:51

      47| public function handle ()

      48| {

      49| $loop = ReactLoop::create ();

      50| $pusher = new Pusher;

      > 51| $context = new ReactContext ($loop);

      52| $ip = Config::get ('app.ip');

      53| echo ($ip);

      54|

      55| $pull = $context->getSocket (\ZMQ::SOCKET_PULL);

      Exception trace:

      1 App\Console\Commands\PushServer::handle ()

      /home/mdma/public_html/test3/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:29

      2 call_user_func_array ([])

      /home/mdma/public_html/test3/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:29

      Please use the argument -v to see more details.

Добавить комментарий