Создание системы сообщений на PHP: от простого до продвинутого

Раздел: Веб-разработка -> Чат/уведомления

Скрипт сообщений на PHP лежит в основе многих веб-приложений: от простых чатов до систем уведомлений. Выбор подхода зависит от требований к реальному времени, нагрузке и доступных ресурсов.

Основные варианты реализации скрипта сообщений на PHP

Эффективное решение: AJAX и MySQL

Наиболее сбалансированный вариант для большинства проектов - комбинация PHP, MySQL и асинхронных HTTP-запросов (AJAX). Серверная часть обрабатывает отправку и получение сообщений через REST-подобные скрипты, клиентская часть периодически запрашивает новые данные.

Шаг 1. Создание таблицы сообщений в MySQL


CREATE TABLE messages (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user VARCHAR(50) NOT NULL,
    content TEXT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Php скрипт сообщений (скрипт сообщений на php)

Шаг 2. PHP-скрипт для отправки сообщения (send.php)


<?php
$pdo = new PDO('mysql:host=localhost;dbname=chat;charset=utf8mb4', 'user', 'pass');
$data = json_decode(file_get_contents('php://input'), true);
$stmt = $pdo->prepare('INSERT INTO messages (user, content) VALUES (?, ?)');
$stmt->execute([$data['user'], $data['content']]);
echo json_encode(['status' => 'ok']);

Шаг 3. PHP-скрипт получения сообщений (fetch.php)


<?php
$pdo = new PDO(/* ... */);
$after = isset($_GET['after']) ? (int)$_GET['after'] : 0;
$stmt = $pdo->prepare('SELECT * FROM messages WHERE id > ? ORDER BY id ASC');
$stmt->execute([$after]);
$messages = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode($messages);

Шаг 4. Клиентский JavaScript (фрагмент)


function sendMessage(user, content) {
    fetch('send.php', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({user, content})
    });
}

let lastId = 0;
function fetchMessages() {
    fetch('fetch.php?after=' + lastId)
    .then(r => r.json())
    .then(data => {
        data.forEach(m => { /* отображение */ });
        if (data.length) lastId = data[data.length-1].id;
    });
}
setInterval(fetchMessages, 2000);

Результат вывода fetch.php (пример JSON)

[
  {"id":1,"user":"Alice","content":"Привет!","created_at":"2025-03-01 12:00:00"},
  {"id":2,"user":"Bob","content":"Здравствуй","created_at":"2025-03-01 12:01:00"}
]

Типичные проблемы и их решение

  • SQL-инъекции: использование подготовленных запросов (PDO) устраняет риск.
  • XSS-атаки: при выводе сообщений экранировать HTML - htmlspecialchars().
  • Гонка состояний (race condition): при одновременной отправке возможна потеря сообщений. Решение - транзакции InnoDB или блокировка таблицы.
  • Периодический polling создает нагрузку. Для снижения частоты увеличивают интервал или используют long polling (см. варианты).

Как создать простой чат без базы данных?

Вариант: хранение сообщений в JSON-файле - подходит для учебных проектов или микросервисов с минимальной нагрузкой.


// send_file.php
$data = json_decode(file_get_contents('messages.json'), true) ?? [];
$data[] = ['user' => $_POST['user'], 'msg' => $_POST['msg'], 'time' => time()];
file_put_contents('messages.json', json_encode($data, JSON_PRETTY_PRINT));

// fetch_file.php
$data = json_decode(file_get_contents('messages.json'), true);
$after = (int)$_GET['after'];
$new = array_filter($data, fn($m) => $m['time'] > $after);
echo json_encode(array_values($new));

Проблемы: нет поддержки конкурентного доступа (блокировка файла через flock решает частично), низкая производительность, файл растет. Для реальных проектов не рекомендуется.

Как реализовать почти реальное время без WebSocket?

Вариант: Long Polling - клиент отправляет запрос, сервер держит соединение открытым до появления нового сообщения или тайм-аута.


// long_poll.php
$pdo = new PDO('mysql:...');
$lastId = isset($_GET['after']) ? (int)$_GET['after'] : 0;
$timeout = 30;
$start = time();
while (time() - $start < $timeout) {
    $stmt = $pdo->prepare('SELECT * FROM messages WHERE id > ?');
    $stmt->execute([$lastId]);
    $messages = $stmt->fetchAll();
    if (!empty($messages)) {
        echo json_encode($messages);
        exit;
    }
    usleep(500000); // 0.5 сек
}
echo json_encode([]); // тайм-аут

Клиент после получения ответа сразу инициирует новый запрос, создавая иллюзию реального времени.

Проблемы: высокое потребление ресурсов сервера (много открытых соединений). Подходит для приложений с числом пользователей до нескольких сотен.

Как создать полноценный чат в реальном времени на PHP?

Вариант: WebSocket с библиотекой Ratchet - обеспечивает двустороннюю связь без периодических запросов.

Установка через Composer: composer require cboden/ratchet.


// Chat.php
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;

class Chat implements MessageComponentInterface {
    protected $clients;
    public function __construct() { $this->clients = new \SplObjectStorage; }
    public function onOpen(ConnectionInterface $conn) { $this->clients->attach($conn); }
    public function onMessage(ConnectionInterface $from, $msg) {
        foreach ($this->clients as $client) {
            if ($from !== $client) $client->send($msg);
        }
    }
    public function onClose(ConnectionInterface $conn) { $this->clients->detach($conn); }
    public function onError(ConnectionInterface $conn, \Exception $e) { $conn->close(); }
}

// server.php
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
require 'vendor/autoload.php';
$server = IoServer::factory(
    new HttpServer(new WsServer(new Chat())),
    8080
);
$server->run();

Проблемы: требуется постоянно работающий процесс PHP, многие хостинги не поддерживают долгоживущие скрипты. Альтернатива - использование Swoole или ReactPHP.

Как сделать уведомления на PHP без WebSocket?

Вариант: Server-Sent Events (SSE) - односторонний канал от сервера к клиенту через длительное HTTP-соединение.


// sse.php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
$lastId = 0;
while (true) {
    $pdo = new PDO('mysql:...');
    $stmt = $pdo->prepare('SELECT * FROM notifications WHERE id > ?');
    $stmt->execute([$lastId]);
    foreach ($stmt as $row) {
        echo "id: {$row['id']}\n";
        echo "data: " . json_encode($row) . "\n\n";
        $lastId = $row['id'];
    }
    ob_flush(); flush();
    sleep(2);
}

Клиент: new EventSource('sse.php').

Проблемы: поддержка только одним сообщением в одном соединении, ограничение количества одновременных соединений браузером (6-8).

Расширенный пример: полноценный AJAX-чат с PDO и защитой

Полный комплект файлов для чата с обработкой ошибок, подготовленными запросами и экранированием вывода.

1. config.php - соединение с БД.

Пример

<?php
$dsn = 'mysql:host=localhost;dbname=chat;charset=utf8mb4';
$opt = [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
];
$pdo = new PDO($dsn, 'user', 'pass', $opt);

2. send.php - обработка входящих сообщений.

Пример

<?php
require 'config.php';
$input = json_decode(file_get_contents('php://input'), true);
if (!$input || empty($input['user']) || empty($input['content'])) {
    http_response_code(400);
    echo json_encode(['error' => 'Invalid data']);
    exit;
}
$user = htmlspecialchars($input['user']);
$content = htmlspecialchars($input['content']);
$stmt = $pdo->prepare('INSERT INTO messages (user, content) VALUES (?, ?)');
$stmt->execute([$user, $content]);
echo json_encode(['id' => $pdo->lastInsertId()]);

3. fetch.php - получение новых сообщений.

Пример

<?php
require 'config.php';
$after = isset($_GET['after']) ? (int)$_GET['after'] : 0;
$stmt = $pdo->prepare('SELECT id, user, content, created_at FROM messages WHERE id > ? ORDER BY id ASC LIMIT 100');
$stmt->execute([$after]);
$messages = $stmt->fetchAll();
// Экранирование для вывода на клиенте
array_walk($messages, function(&$m) {
    $m['user'] = htmlspecialchars($m['user']);
    $m['content'] = htmlspecialchars($m['content']);
});
echo json_encode($messages);

4. index.html - клиентская часть.

Пример

<!DOCTYPE html>
<html>
<body>
<div id="messages"></div>
<input id="user" placeholder="Имя">
<input id="msg" placeholder="Сообщение">
<button onclick="send()">Отправить</button>
<script>
let lastId = 0;
function load() {
    fetch('fetch.php?after=' + lastId)
        .then(r => r.json())
        .then(data => {
            data.forEach(m => {
                let div = document.createElement('div');
                div.textContent = m.user + ': ' + m.content;
                document.getElementById('messages').appendChild(div);
                lastId = m.id;
            });
            setTimeout(load, 2000);
        });
}
load();
function send() {
    let user = document.getElementById('user').value;
    let msg = document.getElementById('msg').value;
    fetch('send.php', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({user, content: msg})
    });
}
</script>
</body></html>

Результат: при отправке сообщения оно появляется у всех клиентов в течение 2 секунд (интервал polling).


Расширенный пример: WebSocket-сервер чата на Ratchet

Полноценный сервер реального времени с передачей JSON-сообщений.

composer.json

Пример

{
    "require": {
        "cboden/ratchet": "^0.4"
    }
}

ChatServer.php

Пример

<?php
namespace MyApp;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;

class ChatServer implements MessageComponentInterface {
    protected $clients;
    public function __construct() {
        $this->clients = new \SplObjectStorage;
    }
    public function onOpen(ConnectionInterface $conn) {
        $this->clients->attach($conn);
    }
    public function onMessage(ConnectionInterface $from, $msg) {
        $data = json_decode($msg, true);
        $response = json_encode([
            'user' => htmlspecialchars($data['user']),
            'message' => htmlspecialchars($data['message']),
            'time' => date('H:i:s')
        ]);
        foreach ($this->clients as $client) {
            if ($from !== $client) $client->send($response);
        }
    }
    public function onClose(ConnectionInterface $conn) {
        $this->clients->detach($conn);
    }
    public function onError(ConnectionInterface $conn, \Exception $e) {
        $conn->close();
    }
}

server.php

Пример

<?php
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use MyApp\ChatServer;
require 'vendor/autoload.php';
$server = IoServer::factory(
    new HttpServer(new WsServer(new ChatServer())),
    8080
);
echo "WebSocket server running on port 8080...\n";
$server->run();

Запуск: php server.php. Клиент подключается через WebSocket: new WebSocket('ws://localhost:8080').

Результат: мгновенная доставка сообщений без задержек polling.


Расширенный пример: Long polling с защитой от тайм-аута

Улучшенный вариант с использованием PDO и проверкой новых записей.

Пример

<?php
// poll.php
set_time_limit(0);
require 'config.php';
$lastId = isset($_GET['after']) ? (int)$_GET['after'] : 0;
$maxWait = 30;
$start = time();
while (time() - $start < $maxWait) {
    $stmt = $pdo->prepare('SELECT id, user, content, created_at FROM messages WHERE id > ? ORDER BY id ASC');
    $stmt->execute([$lastId]);
    $messages = $stmt->fetchAll();
    if (!empty($messages)) {
        echo json_encode($messages);
        exit;
    }
    usleep(500000);
}
echo json_encode([]);

Клиентский код:

Пример

function longPoll(lastId) {
    fetch('poll.php?after=' + lastId)
        .then(r => r.json())
        .then(data => {
            if (data.length) {
                data.forEach(m => { /* отображение */ });
                longPoll(data[data.length-1].id);
            } else {
                longPoll(lastId);
            }
        });
}
longPoll(0);

Результат: сообщения появляются почти сразу, но сервер держит соединение до появления данных или тайм-аута.


Расширенный пример: Server-Sent Events для уведомлений

Подходит для односторонних уведомлений (например, новые заказы).

Пример

<?php
// sse.php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Connection: keep-alive');
header('Access-Control-Allow-Origin: *');
set_time_limit(0);

$pdo = new PDO('mysql:host=localhost;dbname=notify;charset=utf8mb4', 'user', 'pass');
$lastId = isset($_GET['lastId']) ? (int)$_GET['lastId'] : 0;

while (true) {
    $stmt = $pdo->prepare('SELECT id, title, body, created_at FROM notifications WHERE id > ? ORDER BY id ASC');
    $stmt->execute([$lastId]);
    while ($row = $stmt->fetch()) {
        echo "id: {$row['id']}\n";
        echo "event: notification\n";
        echo "data: " . json_encode($row) . "\n\n";
        $lastId = $row['id'];
    }
    ob_flush(); flush();
    sleep(2);
    if (connection_aborted()) break;
}

Клиент:

Пример

const evtSource = new EventSource('sse.php?lastId=0');
evtSource.addEventListener('notification', function(e) {
    const data = JSON.parse(e.data);
    console.log('Новое уведомление:', data.title);
});

Результат: каждый раз при появлении новой записи в таблице notifications, клиент получает событие с данными.

Скрипт сообщений на PHP - comments

En
Php скрипт сообщений (php)