Вы читаете предварительную версию v0.1. API могут измениться.
~ 2 мин чтенияОбновлено 2026-10-29

Создание полноценной доски задач на Elang

Этот проект объединяет маршруты, middleware, контроллеры, валидацию, сессии, CSRF и HTML-шаблоны в одном небольшом приложении. Вы добавите задачу, отметите её выполненной и удалите без JavaScript-фреймворка.

Пример хранит до 20 задач в сессии браузера. В нём нет учётных записей или общей базы данных, а задачи могут исчезнуть по истечении сессии. Это ограничение позволяет сосредоточить первый полноценный проект на жизненном цикле HTTP.

Что нужно знать заранее

Пройдите настройку окружения, первый маршрут и валидацию. Вы должны уметь запускать PHP и Composer, редактировать PHP-файл и узнавать HTML-форму.

Можно попробовать работающую доску задач перед чтением кода. У каждой сессии браузера собственный список задач; тестовая учётная запись или пароль для входа не нужны.

Шаг 1: создайте полное приложение

В родительском каталоге, где хотите разместить проект, выполните:

composer create-project --repository=https://elangphp.com/packages elang/task-board task-board 0.1.0-dev
cd task-board
php elang doctor
php elang serve

Откройте http://127.0.0.1:8000. Вы должны увидеть доску задач, пустое состояние и поле с подписью «What would you like to do?» («Что вы хотите сделать?»). Для этого примера не нужны миграция базы данных или установка Node.js.

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

После однократной настройки глобального CLI можно также создать этот проект командой elang new task-board --example=task-board. Composer автоматически устанавливает зависимости и создаёт ключ приложения.

Шаг 2: разберитесь в файлах проекта

app/Controllers/TaskBoardController.php  Read and change the task list
bootstrap/app.php                      Create Elang and load routes
config/app.php                         Set the board's base path
routes/web.php                         Register GET and POST routes
resources/views/task-board.twig        Render the HTML page
public/assets/task-board.css           Style the page
storage/sessions/                      Private server-side session files

Открывайте файлы в этом порядке. Созданное приложение содержит полный исходный код, поэтому не придётся восстанавливать недостающие методы по отдельным фрагментам.

Шаг 3: проследите один запрос

Маршрут GET отображает текущий список. Маршруты POST создают задачи, переключают их состояние и удаляют:

$router->get('/', [TaskBoardController::class, 'index'], 'board');
$router->post('/add', [TaskBoardController::class, 'store']);
$router->post('/toggle/{id}', [TaskBoardController::class, 'toggle'])
    ->where('id', '[a-f0-9]{16}');
$router->post('/delete/{id}', [TaskBoardController::class, 'delete'])
    ->where('id', '[a-f0-9]{16}');

Они выполняются внутри группы с ограничением частоты запросов, middleware сессии и middleware CSRF. Middleware сессии подготавливает сессию до проверки токена CSRF. Затем маршрут разрешает контроллер и его типизированные зависимости.

Шаг 4: проверяйте данные до сохранения

Обработчик store читает заголовок и применяет три ограничения: значение не должно быть пустым, должно быть строкой и не должно превышать 100 символов.

$valid = $validator->validate(['title' => $title], [
    'title' => [
        new Assert\NotBlank(),
        new Assert\Type('string'),
        new Assert\Length(max: 100),
    ],
]);

Полный контроллер перехватывает ValidationException и отображает форму с полезными ошибками полей и исходным вводом. Для недопустимых данных сервер возвращает 422. Браузерные атрибуты required и maxlength удобны, но серверные проверки остаются необходимыми, поскольку вызывающая сторона может обойти валидацию браузера.

Шаг 5: храните изменения в сессии

Контроллер читает tasks из текущей сессии. Он создаёт случайный ID задачи, сохраняет проверенный заголовок и изначально помечает задачу как невыполненную:

$id = bin2hex(random_bytes(8));
$tasks[$id] = ['id' => $id, 'title' => $valid['title'], 'done' => false];
$request->getSession()->set('tasks', $tasks);

Переключение состояния и удаление работают только со списком этой сессии. ID, скопированный из списка другого браузера, не относится к задаче вашей сессии и возвращает 404. Изоляция сессий здесь полезна, но она не равнозначна аутентификации пользователя.

Шаг 6: защитите формы

Каждая форма содержит скрытое значение _token, созданное из текущей сессии:

<form action="/add" method="post">
  <input type="hidden" name="_token" value="{{ token }}">
  <label for="title">Task title</label>
  <input id="title" name="title" maxlength="100" required>
  <button type="submit">Add task</button>
</form>

Отсутствующий или неверный токен приводит к ответу 419 до изменения состояния обработчиком. Заголовки задач отображаются с автоматическим экранированием HTML в Twig. Не добавляйте |raw к пользовательским заголовкам.

Шаг 7: перенаправляйте после успеха

После успешного POST контроллер возвращает перенаправление 303 на доску. Браузер следует ему с GET. Поэтому обновление полученной страницы читает доску, а не повторно отправляет предыдущую форму.

При недопустимом вводе ошибки отображаются непосредственно, чтобы пользователь мог исправить заголовок. Так небольшой пример наглядно показывает разницу между переходом после успеха и обратной связью валидации.

Проверьте готовое поведение

  1. Добавьте «Прочитать руководство по маршрутизации». Задача должна появиться, а счётчик — показать единицу.
  2. Отметьте её выполненной. Текст должен стать зачёркнутым, а число выполненных задач — увеличиться.
  3. Удалите её. Должно вернуться пустое состояние.
  4. Отправьте пробельные символы как заголовок. Сервер должен показать ошибку валидации без добавления задачи.
  5. Откройте доску в отдельной приватной сессии браузера. В ней должен быть отдельный список.
  6. Изучите форму и убедитесь в наличии _token. Удаление его перед отправкой должно привести к ответу 419.

Эти шаги проверяют реальную серверную часть, а не только внешний вид страницы.

Частые вопросы

Почему список исчез? Сессия могла истечь, либо вы переключили профиль браузера или удалили cookie сессии. Это временное учебное состояние.

Почему не использовать базу данных сразу? Урок по базе данных отдельно знакомит с долговременной схемой и параметризованными запросами. Объединение всех понятий на первом шаге усложняет поиск сбоев.

Почему изменения используют POST? Добавление, переключение состояния и удаление меняют состояние приложения. Ссылка GET не должна изменять список задач.

Можно ли превратить это в настоящий многопользовательский продукт? Да, но для этого нужно проектировать новое приложение: добавьте долговременное хранилище, аутентификацию, проверки принадлежности, авторизацию и тесты доступа между пользователями, прежде чем считать его общим сервисом задач.

Следующее упражнение

Измените заголовок и текст пустого состояния, затем добавьте фильтр просмотра выполненных и невыполненных задач. Сохраните серверную валидацию и поведение CSRF. При сбое следуйте процессу отладки, а не меняйте сразу несколько уровней.

Нужна помощь?

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

Поиск по документации Elang

Начните вводить, чтобы найти следующий шаг.