Система управления задачами на PHP: реализация и варианты
Основные подходы к управлению задачами
В веб-разработке часто требуется реализовать функционал списка задач (todo-приложения). Базовая структура включает отображение, добавление, удаление и изменение статуса задач. В качестве идентификатора удобно использовать индекс элемента в массиве (task index php index). Рассмотрим несколько способов реализации с разными уровнями сложности и сохранности данных.
Наиболее эффективное решение: база данных MySQL и PDO
Для промышленного использования рекомендуется хранение задач в реляционной базе данных. Это обеспечивает целостность, многопользовательский доступ и удобство масштабирования.
Как создать простое приложение для задач с использованием MySQL и PDO?
Создадим таблицу tasks:
CREATE TABLE tasks (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
status TINYINT(1) DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Task index php index (управление задачами)
Код index.php:
<?php
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
$action = $_GET['action'] ?? 'index';
if ($action === 'add' && $_SERVER['REQUEST_METHOD'] === 'POST') {
$title = trim($_POST['title']);
if ($title) {
$stmt = $pdo->prepare('INSERT INTO tasks (title) VALUES (?)');
$stmt->execute([$title]);
}
header('Location: index.php');
exit;
}
if ($action === 'toggle' && isset($_GET['id'])) {
$id = (int)$_GET['id'];
$pdo->exec("UPDATE tasks SET status = NOT status WHERE id = $id");
header('Location: index.php');
exit;
}
if ($action === 'delete' && isset($_GET['id'])) {
$id = (int)$_GET['id'];
$pdo->exec("DELETE FROM tasks WHERE id = $id");
header('Location: index.php');
exit;
}
$stmt = $pdo->query('SELECT * FROM tasks ORDER BY created_at DESC');
$tasks = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>Task Manager</title></head>
<body>
<h2>Список задач</h2>
<form method="post" action="?action=add">
<input type="text" name="title" required>
<button>Добавить</button>
</form>
<ul>
<?php foreach ($tasks as $task): ?>
<li>
<?= htmlspecialchars($task['title']) ?>
<a href="?action=toggle&id=<?= $task['id'] ?>">
<?= $task['status'] ? '✅' : '⬜' ?>
</a>
<a href="?action=delete&id=<?= $task['id'] ?>">❌</a>
</li>
<?php endforeach; ?>
</ul>
</body>
</html>
Типичные ошибки:
- Отсутствие проверки существования записи перед обновлением/удалением.
- SQL-инъекции при прямом подставлении id в запрос (в примере использовано приведение к int, но лучше использовать подготовленные выражения).
Вариант 1: Хранение задач в сессии
Как реализовать управление задачами без базы данных, используя только сессии?
Подходит для прототипов или однопользовательских инструментов, где данные не нужно сохранять между посещениями.
<?php
session_start();
if (!isset($_SESSION['tasks'])) {
$_SESSION['tasks'] = [];
}
$action = $_GET['action'] ?? 'index';
if ($action === 'add' && $_SERVER['REQUEST_METHOD'] === 'POST') {
$title = trim($_POST['title']);
if ($title) {
$_SESSION['tasks'][] = ['title' => $title, 'done' => false];
}
header('Location: index.php');
exit;
}
if ($action === 'toggle' && isset($_GET['index'])) {
$index = (int)$_GET['index'];
if (isset($_SESSION['tasks'][$index])) {
$_SESSION['tasks'][$index]['done'] = !$_SESSION['tasks'][$index]['done'];
}
header('Location: index.php');
exit;
}
if ($action === 'delete' && isset($_GET['index'])) {
$index = (int)$_GET['index'];
if (isset($_SESSION['tasks'][$index])) {
array_splice($_SESSION['tasks'], $index, 1);
}
header('Location: index.php');
exit;
}
$tasks = $_SESSION['tasks'];
?>
Проблемы:
- Данные теряются после закрытия браузера (если не настроить постоянное хранение сессий).
- При одновременной работе нескольких пользователей данные пересекаются.
Вариант 2: Хранение в JSON-файле
Как сохранять задачи в файл для простого хранения между запросами?
Подходит для небольших проектов без базы данных, где данные должны переживать перезапуск сервера.
<?php
$file = 'tasks.json';
function loadTasks() {
global $file;
if (!file_exists($file)) return [];
$data = file_get_contents($file);
return json_decode($data, true) ?? [];
}
function saveTasks($tasks) {
global $file;
file_put_contents($file, json_encode($tasks, JSON_PRETTY_PRINT));
}
$tasks = loadTasks();
$action = $_GET['action'] ?? 'index';
if ($action === 'add' && $_SERVER['REQUEST_METHOD'] === 'POST') {
$title = trim($_POST['title']);
if ($title) {
$tasks[] = ['title' => $title, 'done' => false];
saveTasks($tasks);
}
header('Location: index.php');
exit;
}
if ($action === 'toggle' && isset($_GET['index'])) {
$index = (int)$_GET['index'];
if (isset($tasks[$index])) {
$tasks[$index]['done'] = !$tasks[$index]['done'];
saveTasks($tasks);
}
header('Location: index.php');
exit;
}
if ($action === 'delete' && isset($_GET['index'])) {
$index = (int)$_GET['index'];
if (isset($tasks[$index])) {
array_splice($tasks, $index, 1);
saveTasks($tasks);
}
header('Location: index.php');
exit;
}
?>
Ошибки:
- Необходимость прав на запись в файл.
- Одновременная запись из нескольких процессов может привести к повреждению файла (конкуренция).
Вариант 3: Использование SQLite
Как использовать легковесную базу данных SQLite для управления задачами?
Хороший компромисс между простотой файлового хранения и мощью SQL. Не требует отдельного сервера.
<?php
$db = new PDO('sqlite:tasks.db');
$db->exec('CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
done INTEGER DEFAULT 0
)');
$action = $_GET['action'] ?? 'index';
if ($action === 'add' && $_SERVER['REQUEST_METHOD'] === 'POST') {
$title = trim($_POST['title']);
if ($title) {
$stmt = $db->prepare('INSERT INTO tasks (title) VALUES (?)');
$stmt->execute([$title]);
}
header('Location: index.php');
exit;
}
if ($action === 'toggle' && isset($_GET['id'])) {
$id = (int)$_GET['id'];
$db->exec("UPDATE tasks SET done = NOT done WHERE id = $id");
header('Location: index.php');
exit;
}
if ($action === 'delete' && isset($_GET['id'])) {
$id = (int)$_GET['id'];
$db->exec("DELETE FROM tasks WHERE id = $id");
header('Location: index.php');
exit;
}
$tasks = $db->query('SELECT * FROM tasks ORDER BY id DESC')->fetchAll(PDO::FETCH_ASSOC);
?>
Недостатки:
- Не подходит для высоконагруженных проектов.
- Одновременная запись может блокироваться (но для одного пользователя это не критично).
Цели и случаи использования
Выбор варианта зависит от требований:
- MySQL + PDO для production-приложений с множеством пользователей.
- Сессии для быстрых прототипов или временных данных.
- JSON-файл для небольших личных инструментов или бэкенда без БД.
- SQLite для десктопных или мобильных веб-приложений, где нужна легкость развертывания.
Расширенные примеры с пошаговыми пояснениями
Пример 1: Добавление категорий и приоритетов (MySQL)
Как расширить функционал задач, добавив категории и приоритеты?
Создадим дополнительные таблицы category и приоритет (поле priority).
-- Таблица категорий
CREATE TABLE categories (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL
);
-- Таблица задач с новыми полями
CREATE TABLE tasks (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
description TEXT,
status TINYINT(1) DEFAULT 0,
priority INT DEFAULT 1, -- 1 - низкий, 2 - средний, 3 - высокий
category_id INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (category_id) REFERENCES categories(id)
);
-- Вставка тестовых категорий
INSERT INTO categories (name) VALUES ('Работа'), ('Личное'), ('Учёба');
Код для добавления задачи с выбором категории:
<?php
// index.php (фрагмент)
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
$categories = $pdo->query('SELECT * FROM categories')->fetchAll();
if ($action === 'add' && $_SERVER['REQUEST_METHOD'] === 'POST') {
$title = trim($_POST['title']);
$priority = (int)$_POST['priority'] ?? 1;
$category_id = !empty($_POST['category']) ? (int)$_POST['category'] : null;
if ($title) {
$stmt = $pdo->prepare('INSERT INTO tasks (title, priority, category_id) VALUES (?, ?, ?)');
$stmt->execute([$title, $priority, $category_id]);
}
header('Location: index.php');
exit;
}
?>
<form method="post" action="?action=add">
<input type="text" name="title" required>
<select name="priority">
<option value="1">Низкий</option>
<option value="2" selected>Средний</option>
<option value="3">Высокий</option>
</select>
<select name="category">
<option value="">Без категории</option>
<?php foreach ($categories as $cat): ?>
<option value="<?= $cat['id'] ?>"><?= $cat['name'] ?></option>
<?php endforeach; ?>
</select>
<button>Добавить</button>
</form>
Результат:
Форма теперь содержит выпадающие списки для приоритета и категории. При сохранении данные заносятся в связанные столбцы.
Пример 2: Фильтрация и сортировка на стороне сервера (JSON)
Как реализовать фильтр задач по статусу и сортировку по дате?
В случае JSON-файла можно обрабатывать массив средствами PHP.
<?php
$tasks = loadTasks(); // загружаем массив
// Фильтрация
$filterStatus = $_GET['status'] ?? 'all'; // 'all', 'active', 'done'
if ($filterStatus === 'active') {
$tasks = array_filter($tasks, fn($t) => !$t['done']);
} elseif ($filterStatus === 'done') {
$tasks = array_filter($tasks, fn($t) => $t['done']);
}
// Сортировка (по названию, по статусу)
$sortBy = $_GET['sort'] ?? 'title';
$order = $_GET['order'] ?? 'asc';
if ($sortBy === 'status') {
usort($tasks, function($a, $b) use ($order) {
$cmp = $a['done'] <=> $b['done'];
return $order === 'desc' ? -$cmp : $cmp;
});
} else {
usort($tasks, function($a, $b) use ($order) {
$cmp = strcmp($a['title'], $b['title']);
return $order === 'desc' ? -$cmp : $cmp;
});
}
?>
<!-- HTML часть для выбора параметров -->
<a href="?status=active&sort=status&order=desc">Активные, сначала выполненные</a>
Для наглядности выведем отфильтрованный список:
- [⬜] Купить молоко
- [✅] Позвонить клиенту
Пример 3: Использование транзакций в PDO (MySQL)
Как гарантировать целостность данных при массовом обновлении задач?
Предположим, нужно пометить все задачи как выполненные.
<?php
$pdo->beginTransaction();
try {
$pdo->exec('UPDATE tasks SET status = 1 WHERE status = 0');
// Другие операции (логирование, обновление счётчика)
$pdo->exec('UPDATE stats SET last_batch = NOW()');
$pdo->commit();
echo 'Все активные задачи выполнены.';
} catch (Exception $e) {
$pdo->rollBack();
echo 'Ошибка: ' . $e->getMessage();
}
Результат:
Все активные задачи выполнены.
Пример 4: AJAX для добавления задачи без перезагрузки (JSON + jQuery)
Как улучшить UX, используя асинхронные запросы?
Клиентский код отправляет POST на сервер, сервер возвращает JSON с обновлённым списком.
// server.php (отдельный скрипт)
header('Content-Type: application/json');
$action = $_POST['action'] ?? '';
if ($action === 'add') {
$title = trim($_POST['title']);
if ($title) {
$_SESSION['tasks'][] = ['title' => $title, 'done' => false];
echo json_encode(['success' => true, 'tasks' => $_SESSION['tasks']]);
exit;
}
}
echo json_encode(['success' => false]);
JavaScript:
$('#form').on('submit', function(e) {
e.preventDefault();
$.post('server.php', { action: 'add', title: $('#title').val() }, function(data) {
if (data.success) {
// Обновляем список на странице
renderTasks(data.tasks);
}
}, 'json');
});
Результат:
При отправке формы страница не перезагружается, новый элемент появляется мгновенно.