Когда я впервые столкнулся с задачей сделать многопользовательскую систему на Yii2, я думал, что это что-то из области фантастики. Ну серьёзно, как заставить десятки людей одновременно общаться, видеть действия друг друга и не уронить сервер? Оказалось, всё не так страшно, если знать несколько хитростей. В этой статье я расскажу, как построить такую систему с нуля, на примере простого чата, и поделюсь граблями, на которые сам наступал.
С чего начать: архитектура и инструменты
Многопользовательская система — это не просто код. Это целый комплекс: база данных, серверная логика, клиентская часть и, самое главное, способ общения между ними. В классическом вебе клиент отправляет запрос, сервер отвечает — и всё. Но в реальном времени, когда нужно, чтобы действия одного пользователя мгновенно видели другие, нужен другой подход.
В 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-сервер обрабатывает подключения и рассылку. Это сложнее, но зато масштабируется лучше.
Безопасность: как защитить систему от атак
Многопользовательская система — это лакомый кусок для хакеров. Поэтому нужно уделить внимание безопасности. Вот основные моменты:
- 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 |
| Пользователь видит чужие сообщения | Проверяйте права доступа к комнате или каналу |
Заключение
Создание многопользовательской системы на Yii2 — это не ракетостроение, но и не прогулка. Главное — правильно спроектировать базу, настроить авторизацию и выбрать способ обмена данными. Начните с простого long polling, а потом, когда почувствуете уверенность, переходите на WebSocket. Не забывайте про безопасность и не бойтесь экспериментировать.
В итоге у вас получится система, которая будет работать и радовать пользователей. А если что-то пойдёт не так — вспомните мои советы и не паникуйте. Всё решаемо.

