Создание системы сообщений на 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, клиент получает событие с данными.