Чат на сайте — это не просто модная фишка, а реальный инструмент для удержания пользователей. Но как сделать его быстрым и отзывчивым? Ответ прост: использовать вебсокеты. В этой статье мы разберём, как построить чат на Yii2 с вебсокетами, шаг за шагом, с примерами кода и практическими советами.

Digital-студия WNDER

Почему вебсокеты, а не AJAX?

Классический AJAX-чат работает так: браузер каждые пару секунд спрашивает сервер: «Есть новые сообщения?». Сервер отвечает: «Нет» или «Да, вот». Это как дёргать продавца в магазине каждые 5 секунд: «А есть хлеб? А сейчас? А сейчас?». Продавец злится, вы тратите время.

Вебсокеты — это постоянный канал связи. Как телефонный звонок: один раз соединились и общаетесь, пока не надоест. Сервер сам присылает сообщение, как только оно появилось. Никаких лишних запросов, мгновенная доставка.

В Yii2 нет встроенной поддержки вебсокетов, но это не проблема. Мы можем использовать популярную библиотеку Ratchet для PHP. Она отлично дружит с Yii2.

Установка и настройка Ratchet

Для начала установим Ratchet через Composer. Откройте терминал в корне вашего Yii2-проекта и выполните:

composer require cboden/ratchet

Теперь создадим консольную команду, которая запустит WebSocket-сервер. В Yii2 для этого удобно использовать контроллеры в папке commands.

Создайте файл commands/ChatController.php:

<?php
namespace app\commands;

use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use yii\console\Controller;
use app\components\ChatServer;

class ChatController extends Controller
{
    public function actionStart()
    {
        $server = IoServer::factory(
            new HttpServer(
                new WsServer(
                    new ChatServer()
                )
            ),
            8080
        );
        $server->run();
    }
}

Здесь мы создаём сервер на порту 8080. Класс ChatServer мы напишем чуть позже — он будет обрабатывать подключения и сообщения.

Пишем логику чата: класс ChatServer

Создайте файл components/ChatServer.php. Этот класс реализует интерфейсы MessageComponentInterface и ConnectionInterface из Ratchet. Вот его основа:

<?php
namespace app\components;

use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Yii;

class ChatServer implements MessageComponentInterface
{
    protected $clients;

    public function __construct()
    {
        $this->clients = new \SplObjectStorage;
    }

    public function onOpen(ConnectionInterface $conn)
    {
        $this->clients->attach($conn);
        echo "Новое подключение: ({$conn->resourceId})\n";
    }

    public function onMessage(ConnectionInterface $from, $msg)
    {
        $data = json_decode($msg, true);
        // Сохраняем сообщение в базу данных
        $message = new \app\models\Message();
        $message->user_id = $data['user_id'];
        $message->text = $data['text'];
        $message->created_at = time();
        $message->save();

        // Рассылаем всем клиентам
        foreach ($this->clients as $client) {
            $client->send(json_encode([
                'user_id' => $data['user_id'],
                'text' => $data['text'],
                'created_at' => date('H:i:s'),
            ]));
        }
    }

    public function onClose(ConnectionInterface $conn)
    {
        $this->clients->detach($conn);
        echo "Подключение закрыто: ({$conn->resourceId})\n";
    }

    public function onError(ConnectionInterface $conn, \Exception $e)
    {
        echo "Ошибка: {$e->getMessage()}\n";
        $conn->close();
    }
}

Этот код принимает сообщения в формате JSON, сохраняет их в базу (модель Message нужно создать) и рассылает всем подключённым клиентам.

Правило: Всегда проверяйте входящие данные. Даже если чат внутренний, пользователь может отправить что угодно. Используйте валидацию Yii2 перед сохранением в базу.

Клиентская часть: JavaScript и WebSocket

Теперь напишем клиентский скрипт, который будет подключаться к нашему серверу и отправлять сообщения. Добавьте на страницу чата следующий код:

// Подключаемся к WebSocket-серверу
const socket = new WebSocket('ws://localhost:8080');

// Отправка сообщения
function sendMessage() {
    const input = document.getElementById('messageInput');
    const text = input.value.trim();
    if (text === '') return;

    const data = {
        user_id: 1, // Здесь должен быть ID текущего пользователя
        text: text
    };
    socket.send(JSON.stringify(data));
    input.value = '';
}

// Получение сообщений
socket.onmessage = function(event) {
    const data = JSON.parse(event.data);
    const messagesDiv = document.getElementById('messages');
    const messageElement = document.createElement('div');
    messageElement.innerHTML = `<strong>Пользователь ${data.user_id}</strong> (${data.created_at}): ${data.text}`;
    messagesDiv.appendChild(messageElement);
};

// Обработка закрытия соединения
socket.onclose = function() {
    console.log('Соединение закрыто. Попробуйте перезагрузить страницу.');
};

Не забудьте добавить HTML-разметку для поля ввода и области сообщений:

<div id="messages" style="height: 300px; overflow-y: scroll; border: 1px solid #ccc; padding: 10px; margin-bottom: 10px;"></div>
<input type="text" id="messageInput" placeholder="Введите сообщение...">
<button onclick="sendMessage()">Отправить</button>

Это минимальный рабочий пример. В реальном проекте стоит добавить аутентификацию, индикатор набора текста и историю сообщений.

Сравнение подходов: AJAX vs WebSocket

Чтобы вы окончательно поняли, почему вебсокеты лучше, вот таблица сравнения:

Характеристика AJAX WebSocket
Задержка Высокая (зависит от интервала опроса) Низкая (мгновенно)
Нагрузка на сервер Высокая (много лишних запросов) Низкая (одно соединение)
Сложность реализации Простая Средняя
Поддержка браузерами Везде Современные браузеры

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

Лайфхак: Если ваш хостинг не поддерживает WebSocket, можно использовать сервисы вроде Pusher или Firebase. Они берут всю работу на себя, а вы только подписываетесь на события.

Запуск и отладка

Чтобы запустить сервер, выполните в терминале:

php yii chat/start

Сервер начнёт слушать порт 8080. Откройте браузер, перейдите на страницу с чатом — и попробуйте отправить сообщение. Если что-то не работает, проверьте консоль браузера (F12) и вывод терминала.

Частая проблема: порт 8080 занят. Поменяйте его на другой, например 8081.

Ещё один момент: в продакшене нельзя просто запустить скрипт в терминале. Нужно использовать супервизор (supervisor) или systemd, чтобы сервер автоматически перезапускался при падении.

Что в итоге?

Мы построили простой чат на Yii2 с вебсокетами. Да, это только основа, но вы уже можете отправлять и получать сообщения в реальном времени. Дальше можно добавить приватные комнаты, уведомления, историю сообщений и многое другое.

Главное — не бойтесь экспериментировать. Вебсокеты открывают двери в мир реального времени, и Yii2 с Ratchet — отличный тандем для этого.

Студия WNDER