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

Digital-студия WNDER

С чего начать: архитектура и инструменты

Многопользовательская система — это не просто код. Это целый комплекс: база данных, серверная логика, клиентская часть и, самое главное, способ общения между ними. В классическом вебе клиент отправляет запрос, сервер отвечает — и всё. Но в реальном времени, когда нужно, чтобы действия одного пользователя мгновенно видели другие, нужен другой подход.

В Yii2 есть несколько способов организовать это. Самый простой — использовать длинные опросы (long polling) или WebSocket. Для новичка long polling проще, но WebSocket — это уже профессиональный уровень. Я советую начинать с long polling, а потом, когда поймёте принцип, переходить на WebSocket.

Для примера возьмём чат. Пользователи заходят, отправляют сообщения, видят сообщения других. Всё по-взрослому.

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

Первым делом нужно спроектировать базу. У нас будет две основные таблицы: users и messages. Таблица users уже есть в Yii2 по умолчанию, если вы настроили авторизацию. Но нам нужно добавить поле для статуса (онлайн/офлайн) и, возможно, последнюю активность.

Таблица messages будет хранить сообщения: id, user_id (кто отправил), text, created_at. Вот как это выглядит в миграции:


use yii\db\Migration;

class m250101_000000_create_messages_table extends Migration
{
    public function up()
    {
        $this->createTable('messages', [
            'id' => $this->primaryKey(),
            'user_id' => $this->integer()->notNull(),
            'text' => $this->text()->notNull(),
            'created_at' => $this->timestamp()->defaultExpression('NOW()'),
        ]);

        $this->addForeignKey(
            'fk-messages-user_id',
            'messages',
            'user_id',
            'users',
            'id',
            'CASCADE'
        );
    }

    public function down()
    {
        $this->dropTable('messages');
    }
}

Не забывайте про индексы. Если у вас много сообщений, то запросы без индексов будут тормозить. Добавьте индекс на поле created_at — это ускорит выборку.

Авторизация и управление сессиями

Для многопользовательской системы критически важно правильно управлять сессиями. В Yii2 по умолчанию используется PHP-сессии, но для реального времени лучше использовать токены. Например, при входе выдаём пользователю токен, который он хранит в localStorage и отправляет с каждым запросом.

Как это сделать? Переопределяем метод login в модели User:


use Yii;
use yii\db\ActiveRecord;
use yii\web\IdentityInterface;

class User extends ActiveRecord implements IdentityInterface
{
    public static function findIdentityByAccessToken($token, $type = null)
    {
        return static::findOne(['access_token' => $token]);
    }

    public function generateAccessToken()
    {
        $this->access_token = Yii::$app->security->generateRandomString(32);
        $this->save();
        return $this->access_token;
    }
}

Теперь при входе мы вызываем generateAccessToken и отправляем его клиенту. На каждом запросе проверяем токен через поведение в контроллере.

Важно: не храните токены в базе в открытом виде. Лучше хешировать, а сам токен передавать по HTTPS. Иначе злоумышленник может перехватить и зайти под чужим именем.

Real-time обновления: long polling и WebSocket

Теперь самое интересное — как сделать, чтобы сообщения появлялись у всех мгновенно. Начнём с long polling. Идея проста: клиент отправляет запрос на сервер, сервер держит соединение открытым, пока не появится новое сообщение (или не пройдёт таймаут). Как только сообщение появилось — сервер отвечает, клиент обновляет чат и сразу отправляет новый запрос.

В Yii2 это делается через контроллер. Вот пример:


use yii\web\Controller;
use yii\db\Query;

class MessageController extends Controller
{
    public function actionPoll()
    {
        $lastId = Yii::$app->request->get('last_id', 0);
        $timeout = 30; // ждём до 30 секунд
        $start = time();

        while (time() - $start < $timeout) {
            $messages = (new Query())
                ->from('messages')
                ->where(['>', 'id', $lastId])
                ->orderBy('id')
                ->limit(50)
                ->all();

            if (!empty($messages)) {
                Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
                return $messages;
            }

            // Ждём немного, чтобы не нагружать сервер
            usleep(500000); // 0.5 секунды
        }

        // Таймаут, вернём пустой массив
        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
        return [];
    }
}

На клиенте используем JavaScript, чтобы отправлять запрос и обрабатывать ответ:


let lastId = 0;

function poll() {
    fetch('/message/poll?last_id=' + lastId)
        .then(response => response.json())
        .then(data => {
            if (data.length > 0) {
                data.forEach(msg => {
                    // Добавляем сообщение в DOM
                    addMessage(msg);
                    lastId = msg.id;
                });
            }
            poll(); // сразу повторяем
        })
        .catch(() => {
            setTimeout(poll, 3000); // при ошибке ждём 3 сек
        });
}

poll();

Если хотите более продвинутый вариант — WebSocket. Тут уже нужен отдельный сервер (например, Ratchet или Swoole). Yii2 выступает как REST API, а WebSocket-сервер обрабатывает подключения и рассылку. Это сложнее, но зато масштабируется лучше.

Лайфхак: Если вы новичок, не начинайте с WebSocket. Сначала сделайте long polling — он проще и для начала вполне подойдёт. А когда поймёте, что не хватает производительности, переходите на WebSocket.

Безопасность: как защитить систему от атак

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

  • CSRF-защита — в Yii2 она включена по умолчанию, но проверьте, что в ваших формах есть csrf-токен.
  • Права доступа — используйте RBAC (роли и разрешения). Например, только админ может удалять сообщения.
  • Валидация входных данных — не доверяйте пользователю. Проверяйте всё, что приходит с клиента.
  • Ограничение частоты запросов — чтобы злоумышленник не флудил сообщениями. Используйте поведение throttle.

Пример настройки права на отправку сообщения:


use yii\filters\AccessControl;

public function behaviors()
{
    return [
        'access' => [
            'class' => AccessControl::className(),
            'rules' => [
                [
                    'allow' => true,
                    'actions' => ['send'],
                    'roles' => ['@'], // только авторизованные
                ],
            ],
        ],
    ];
}

Масштабирование: когда пользователей становится много

Когда ваша система набирает популярность, один сервер может не справиться. Что делать? Вариантов несколько:

  • Кэширование — храните списки сообщений в Redis или Memcached, чтобы не грузить базу.
  • Очереди — для отправки уведомлений используйте RabbitMQ или Redis Queue.
  • Балансировка нагрузки — ставьте несколько серверов и распределяйте трафик.

Но помните: преждевременная оптимизация — зло. Начните с одного сервера, а масштабируйте только когда реально понадобится.

Практические советы и типичные ошибки

На основе моего опыта, вот несколько советов, которые сэкономят вам нервы:

Проблема Решение
Сообщения теряются при обрыве сети Используйте механизм подтверждения доставки (например, клиент отправляет ack)
Сервер перегружен из-за long polling Увеличьте интервал опроса или переходите на WebSocket
Пользователь видит чужие сообщения Проверяйте права доступа к комнате или каналу
Правило: всегда логируйте события. В многопользовательской системе без логов вы будете как слепой котёнок. Используйте Yii::$app->log, чтобы записывать входящие запросы, ошибки, действия пользователей.

Заключение

Создание многопользовательской системы на Yii2 — это не ракетостроение, но и не прогулка. Главное — правильно спроектировать базу, настроить авторизацию и выбрать способ обмена данными. Начните с простого long polling, а потом, когда почувствуете уверенность, переходите на WebSocket. Не забывайте про безопасность и не бойтесь экспериментировать.

В итоге у вас получится система, которая будет работать и радовать пользователей. А если что-то пойдёт не так — вспомните мои советы и не паникуйте. Всё решаемо.

Студия WNDER