© 2008-2024 г.
Все права защищены
Алексей Костюк

Простая система реакций для постов как у меня в блоге. PHP8+MYSQL+JS

Недавно я писал как сделать систему лайков для сайта и я решил пойти дальше написав код для создания системы реакций, как у меня на сайте. Данная система будет реализована на PHP не ниже 7.4, у меня она работает на PHP 8 с подключением к базе данных MYSQL и небольшой скрипт JS.

Содержание статьи

Требования к реакциям

  1. Реакцию можно поставить только 1 раз. Определяем пользователя по IP.
  2. Все реакции пользователей должны храниться в базе данных.
  3. Для каждой реакции выводим счетчик их количества из базы данных.

HTML код блока реакций

<div class="reaction-container" data-post-id="{{id}}" id="reactionContainer">
	<div class="reaction" data-emoji="👍" data-id="like"> <img src="/assets/svg/thumbs.svg" alt="" style="width: 24px;height: 24px;"> <span>0</span> </div>
	<div class="reaction" data-emoji="❤️" data-id="heart"> <img src="/assets/svg/heart.svg" alt="" style="width: 24px;height: 24px;"> <span>0</span> </div>
	<div class="reaction" data-emoji="🔥" data-id="fire"> <img src="/assets/svg/fire.svg" alt="" style="width: 24px;height: 24px;"> <span>0</span> </div>
	<div class="reaction" data-emoji="😂" data-id="laugh"> <img src="/assets/svg/tears.svg" alt="" style="width: 24px;height: 24px;"> <span>0</span> </div>
	<div class="reaction" data-emoji="😮" data-id="surprise"> <img src="/assets/svg/astonished.svg" alt="" style="width: 24px;height: 24px;"> <span>0</span> </div>
</div>

Разъяснения по коду выше. Вообще-то это не совсем чистый HTML. У меня CMS Publii, который генерирует статический HTML код через файлы hbs.

В атрибуте  data-post-id="{{id}}"  я получаю ID поста. Я не знаю какая у вас CMS или фреймворк, поэтому вы должны посмотреть в документации как получить id конкретного поста и сделать это.

В атрибуте data-emoji="" я вывожу emoji реакции. Вы можете определить свой набор emoji, например вы можете взять их из этой таблицы

PHP обработка добавления реакции в базу данных

Для начала нужно создать таблицу user_reactions в базе данных Mysql, которая будет содержать уникальный id, id поста, тип реакции, ip адрес пользователя, дата и время реакции.

CREATE TABLE `user_reactions` (
  `id` int(11) NOT NULL,
  `post_id` int(11) NOT NULL,
  `reaction_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `ip_address` varchar(50) DEFAULT NULL,
  `created_at` datetime DEFAULT current_timestamp()
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;

Добавим обязательные индексы:

ALTER TABLE `user_reactions`
  ADD PRIMARY KEY (`id`),
  ADD UNIQUE KEY `unique_reaction` (`post_id`,`ip_address`);

Добавим AUTO_INCREMENT для колонки id, чтобы автоматически добавлялся уникальная цифра, при каждом добавлении реакции:

ALTER TABLE `user_reactions`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

Должна получиться такая структура:

Создаем файл reactions_handler.php, который будет обрабатывать добавление реакции

<?php
// Запрет прямого доступа
if (!isset($_SERVER['HTTP_X_REQUESTED_WITH']) || $_SERVER['HTTP_X_REQUESTED_WITH'] !== 'XMLHttpRequest') {
    http_response_code(403);
    exit('Access denied');
}

header('Content-Type: application/json; charset=utf-8');

$host = 'localhost';
$dbname = 'bd';
$username = 'bd_user';
$password = 'password';

try {
    $pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8mb4", $username, $password, [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
    ]);
} catch (PDOException $e) {
    echo json_encode(['status' => 'error', 'message' => 'Database connection failed']);
    exit();
}

$data = json_decode(file_get_contents('php://input'), true);

if (!isset($data['postId']) || !isset($data['emoji'])) {
    echo json_encode(['status' => 'error', 'message' => 'Invalid data']);
    exit();
}

$postId = intval($data['postId']);
$emoji = $data['emoji'];
$ipAddress = $_SERVER['REMOTE_ADDR'];

// Список допустимых реакций
$validReactions = ['❤️', '👍', '😂', '🔥', '😮'];

if (!in_array($emoji, $validReactions)) {
    echo json_encode(['status' => 'error', 'message' => 'Invalid reaction']);
    exit();
}

// Проверяем существующую реакцию
$stmt = $pdo->prepare("
    SELECT id FROM user_reactions 
    WHERE post_id = :post_id AND ip_address = :ip_address
");
$stmt->execute(['post_id' => $postId, 'ip_address' => $ipAddress]);
$userReactionExists = $stmt->fetchColumn();

if ($userReactionExists) {
    echo json_encode(['status' => 'error', 'message' => 'Вы уже поставили реакцию этому посту']);
    exit();
}

// Добавляем реакцию
$stmt = $pdo->prepare("
    INSERT INTO user_reactions (post_id, reaction_type, ip_address, created_at) 
    VALUES (:post_id, :reaction_type, :ip_address, NOW())
");
$stmt->execute(['post_id' => $postId, 'reaction_type' => $emoji, 'ip_address' => $ipAddress]);

echo json_encode(['status' => 'success']);

Важные моменты по коду:

В этой части нужно прописать данные подключения к базе данных, которые выдает хостинг-провайдер.

$host = 'localhost';
$dbname = 'bd'; // Имя базы данных
$username = 'bd_user'; // Имя пользователя
$password = 'password'; // Пароль базы данных

В этой переменной указываем все emoji реакции, которые вы изначально добавили в HTML.

// Список допустимых реакций
$validReactions = ['❤️', '👍', '😂', '🔥', '😮'];

PHP обработка получения реакции из базы данных

Теперь нужно данные, которые были записаны в БД подтянуть для вывода на сайте, а это счетчик каждой реакции.

Создаем файл get_reactions.php.

<?php
// Запрет прямого доступа
if (!isset($_SERVER['HTTP_X_REQUESTED_WITH']) || $_SERVER['HTTP_X_REQUESTED_WITH'] !== 'XMLHttpRequest') {
    http_response_code(403);
    exit('Access denied');
}

header('Content-Type: application/json; charset=utf-8');

$host = 'localhost';
$dbname = 'bd';
$username = 'bd_user';
$password = 'password';

try {
    $pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8mb4", $username, $password, [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
    ]);
} catch (PDOException $e) {
    echo json_encode(['status' => 'error', 'message' => 'Database connection failed']);
    exit();
}

if (!isset($_GET['post_id'])) {
    echo json_encode(['status' => 'error', 'message' => 'Post ID is missing']);
    exit();
}

$postId = intval($_GET['post_id']);
$ipAddress = $_SERVER['REMOTE_ADDR'];

// Получаем реакции для поста
$stmt = $pdo->prepare("
    SELECT reaction_type, COUNT(*) as count
    FROM user_reactions
    WHERE post_id = :post_id
    GROUP BY reaction_type
");
$stmt->execute(['post_id' => $postId]);
$reactions = $stmt->fetchAll(PDO::FETCH_ASSOC);

// Проверяем реакцию текущего пользователя
$stmt = $pdo->prepare("
    SELECT reaction_type
    FROM user_reactions
    WHERE post_id = :post_id AND ip_address = :ip_address
");
$stmt->execute(['post_id' => $postId, 'ip_address' => $ipAddress]);
$userReaction = $stmt->fetchColumn();

$response = [
    'status' => 'success',
    'reactions' => array_map(function ($reaction) {
        return [
            'emoji' => $reaction['reaction_type'],
            'count' => intval($reaction['count']),
        ];
    }, $reactions),
    'userReaction' => $userReaction,
];

echo json_encode($response);

И опять в этом файле указываем подключение к базе данных. Да, можно было создать отдельный файл config.php и подключать через него не указывая 2 раза данные для подключения к БД, но в моем случае в этом нет необходимости, а вы можете это сделать, если знаете как.

Скрипт JS для обработки реакций

Теперь нужно подключить файле reactions.js, чтобы система реакций корректно заработала.

document.addEventListener('DOMContentLoaded', () => {
    const reactionContainers = document.querySelectorAll('.reaction-container');

    // Обработка каждого контейнера реакций
    reactionContainers.forEach(container => {
        const postId = container.getAttribute('data-post-id');

        // Загрузка реакций для каждого поста
        fetch(`/get_reactions.php?post_id=${postId}`, {
            headers: { 'X-Requested-With': 'XMLHttpRequest' }
        })
            .then(response => response.json())
            .then(data => {
                if (data.status === 'success') {
                    updateReactionsUI(container, data.reactions, data.userReaction);
                }
            })
            .catch(err => console.error('Error loading reactions:', err));

        // Обработка кликов на реакции
        container.addEventListener('click', (event) => {
            const reaction = event.target.closest('.reaction');
            if (!reaction) return;

            const emoji = reaction.getAttribute('data-emoji');

            console.log(`Clicked on post ID: ${postId}, reaction: ${emoji}`);

            // Отправка реакции на сервер
            fetch('/reactions_handler.php', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'X-Requested-With': 'XMLHttpRequest'
                },
                body: JSON.stringify({ postId, emoji })
            })
                .then(response => response.json())
                .then(data => {
                    if (data.status === 'success') {
                        const countSpan = reaction.querySelector('span');
                        countSpan.textContent = parseInt(countSpan.textContent, 10) + 1;
                    } else {
                        alert(data.message);
                    }
                })
                .catch(err => console.error('Error sending reaction:', err));
        });
    });

    function updateReactionsUI(container, reactions, userReaction) {
        reactions.forEach(reaction => {
            const reactionElement = container.querySelector(`[data-emoji="${reaction.emoji}"]`);
            if (reactionElement) {
                const countSpan = reactionElement.querySelector('span');
                countSpan.textContent = reaction.count;
                if (reaction.emoji === userReaction) {
                    reactionElement.classList.add('active');
                } else {
                    reactionElement.classList.remove('active');
                }
            }
        });
    }
});

Данный скрипт:

  1. Находит блок с классом reaction-container, который мы создали раннее.
  2. В переменную postId получает id поста из атрибута data-post-id.
  3. Отправляет POST запрос на сервер в php обработчик reactions_handler.php.
  4. Возвращает для каждого поста данные из базы данных при помощи php обработчика get_reactions.php.
  5. При помощи функции updateReactionsUI обновляет счетчик реакции.

Для пользователей, которые возможно будут внедрять данную систему реакций я подготовил архив, куда поместил все необходимые файлы. Вот и всё! Удачный проектов!

Привет Username! Я Алексей Костюк
14 лет занимаюсь тем, что помогаю людям создавать сайты и дизайн, настраивать рекламу, продвигать проекты в Интернете. Я создал этот блог, чтобы делиться своим опытом и мыслями, а если вы хотите, то можете меня поддержать, это поможет мне создавать больше полезных статей для вас.

Похожие посты

Код
0

Простая система лайков для любого сайта без подключения к базе данных на PHP+JS с определением IP посетителя

Мой блог создан на CMS Publii и из коробки нет практически ничего для блога - комментариев, счетчиков просмотра постов и лайков и это даже хорошо.