pbQuiz — гибкий компонент квизов на контроллерах PageBlocks
pbQuiz — это наглядный пример того, как с помощью PageBlocks можно построить гибкую многошаговую форму-квиз с пошаговой валидацией и хранением прогресса в сессии.
Вы можете использовать готовый бесплатный компонент или создать всё вручную — чтобы полностью понять, как работает структура под капотом.

Вы можете быстро запустить квиз двумя способами:
pbQuiz — это готовый бесплатный компонент, который можно установить через PageBlocks.
Что для этого нужно:
Если вы хотите глубже понять, как работает квиз внутри PageBlocks, или адаптировать его под свои задачи — ниже приведён детальный пример, который показывает:
У каждого шага свой заголовок, описание и набор полей.
Режим менеджера
1.1 Создайте необходимые таблицы

1.2 Добавьте меню

Режим разработчика
Для каждого шага квиза добавьте новую строку с полями:

Что они делают?
Этот метод:
Метод getData() — получить данные шага
Достаёт из таблицы все поля для текущего шага.
Метод prev() — шаг назад
Когда пользователь жмёт «Назад»:
Метод next() — шаг вперёд
Когда пользователь жмёт «Далее»:
Метод getStep() — сердце квиза
Здесь всё происходит:
1. Основной шаблон формы
Вот базовый пример формы, которая уже готова к работе через AJAX с PageBlocks:
Как это работает:
pb-form
Этот атрибут подключает автоматическую обработку формы через AJAX
_token
Защищает от CSRF — каждый запрос проходит проверку безопасности.
honeypot
Простая защита от ботов. Если поле заполнено — запрос отклоняется.
pb-success-message
Этот атрибут указывает PageBlocks, куда вставить HTML ответа, который вернёт контроллер. В вашем случае контроллер возвращает сгенерированный чанк step для следующего шага квиза. Поэтому старый шаг автоматически заменяется на новый — без полной перезагрузки страницы.
2. Чанк step для рендеринга шагов
Чанк отвечает за то, как выводятся заголовок, описание и поля для текущего шага. Пример базового чанка:
3. Чанк nav для кнопок «Назад» и «Далее»
Что в итоге
Теперь у вас есть два удобных варианта:
Вы можете использовать готовый бесплатный компонент или создать всё вручную — чтобы полностью понять, как работает структура под капотом.

Что умеет pbQuiz
- Создавать квиз из любого количества шагов.
- Гибко задавать поля: radio, checkbox, текстовые и email.
- Проверять введённые данные на каждом шаге.
- Хранить введённые данные между шагами.
- Отправлять результаты менеджеру и пользователю.
- Полностью управляться через таблицы PageBlocks
Как настроить
Вы можете быстро запустить квиз двумя способами:
1. Использовать готовый бесплатный компонент
pbQuiz — это готовый бесплатный компонент, который можно установить через PageBlocks.
Что для этого нужно:
- Установите компонент PageBlocks, если он ещё не установлен.
- Установите pbQuiz — он автоматически создаст:
- все необходимые контроллеры,
- меню с таблицей,
- базовую структуру квиза.
2. Сделать квиз вручную
Если вы хотите глубже понять, как работает квиз внутри PageBlocks, или адаптировать его под свои задачи — ниже приведён детальный пример, который показывает:
- как шаг за шагом собрать таблицу,
- какие маршруты подключить,
- как написать свой QuizController,
- как настроить шаблоны.
Шаг 1. Создайте таблицу для квиза
В этой таблице вы будете создавать шаги квиза.У каждого шага свой заголовок, описание и набор полей.
Режим менеджера
1.1 Создайте необходимые таблицы

1.2 Добавьте меню

Режим разработчика
Menu::make('pbquiz')
->title('pbQuiz')
->description('Quiz for PageBlocks')
->position(5)
->parent('pageblocks')
->fields([
Field::make('pbquiz')
->label('Quiz')
->type('table')
->fields([
Field::make('title')
->label('Title')
->required(),
Field::make('description')
->label('Description')
->type('textarea'),
Field::make('Image')
->label('Image')
->type('image')
->source(1)
->sourcePath('/assets/components/pbquiz/images/'),
Field::make('fields')
->label('Fields')
->type('table')
->fields([
Field::make('name')
->label('Name')
->width(50)
->required(),
Field::make('label')
->label('Label')
->width(50),
Field::make('type')
->label('Field type')
->type('select')
->options([
'radio' => 'Radio',
'checkbox' => 'Checkbox',
'text' => 'Text',
'email' => 'Email',
])
->width(50)
->required(),
Field::make('placeholder')
->label('Placeholder')
->width(50),
Field::make('options')
->label('Values')
->type('keyvalue')
->keyLabel('Value')
->valueLabel('Label'),
Field::make('validation')
->label('Validation')
->width(50)
->help('Docs: https://pageblocks.boshnik.com/docs/validation#available-validation-rules'),
Field::make('validation_error')
->label('Field Validation Error')
->width(50),
])
->columns([
Column::make('Name'),
Column::make('Label'),
Column::make('Validation'),
])
])
->columns([
Column::make('Title'),
])
]);
Шаг 2. Заполните данные квиза
После создания таблицы в админке откройте созданное меню pbQuiz.Для каждого шага квиза добавьте новую строку с полями:
- Title — заголовок шага.
- Description — описание или пояснение.
- Image — иллюстрация (если нужно).
- Fields — список вопросов или полей на шаге.
- системное имя (name),
- подпись для пользователя (label),
- тип поля (Field type) — radio, checkbox, text или email,
- опции значений (для radio/checkbox),
- плейсхолдер (для текстовых полей),
- правила валидации,
- сообщение об ошибке валидации.
- первым шагом указать обложку (приветственный экран),
- последним — финальный экран (сообщение о том, что данные успешно отправлены).

Шаг 3. Добавьте маршруты
Для работы квиза нужны три маршрута:Route::get('/', 'Quiz\QuizController@index')->name('pageQuiz');
Route::prefix('/pbquiz')->group(function () {
Route::post('/prev', 'Quiz\QuizController@prev')->name('quizPrev');
Route::post('/next', 'Quiz\QuizController@next')->name('quizNext');
});
Что они делают?
- / Основной маршрут. Вызывает метод index в QuizController. Именно он выводит первый шаг квиза (или обложку).
- /pbquiz/prev POST-запрос на предыдущий шаг. Если пользователь решил вернуться назад — этот маршрут откатит шаг и покажет предыдущий экран.
- /pbquiz/next POST-запрос на следующий шаг. Пользователь заполнил поля, нажал «Далее» — данные валидируются и записываются в сессию. Если всё ок — грузится следующий шаг.
Шаг 4. Напишите контроллер
Метод index() — стартЭтот метод:
- Очищает старые данные из сессии.
- Получает первый шаг из таблицы.
- Передаёт данные в шаблон квиза.
public function index()
{
$_SESSION['pbquiz'] = []; // сбрасываем всё
$data = $this->getData(); // берём первый шаг
return view('file:quiz/templates/quiz', $data);
}
Метод getData() — получить данные шага
Достаёт из таблицы все поля для текущего шага.
public string $className = \pbTableValue::class;
public function getData(int $offset = 0, int $limit = 1)
{
$query = $this->modx->newQuery($this->className);
$query->select($this->modx->getSelectColumns($this->className, $this->className));
$query->where([
'field_name' => 'pbquiz',
'published' => 1,
'deleted' => 0,
]);
$query->limit($limit, $offset);
$query->sortby('menuindex', 'ASC');
$query->prepare();
$query->stmt->execute();
if ($limit === 1) {
$result = $query->stmt->fetch(\PDO::FETCH_ASSOC);
$params = json_decode($result['values'], true) ?? [];
$params['step'] = $offset;
$params['total'] = $this->getTotal() - 2; // -2, потому что не считаем обложку и финал
} else {
$results = $query->stmt->fetchAll(\PDO::FETCH_ASSOC);
$params = [];
foreach ($results as $result) {
$params[] = json_decode($result['values'], true) ?? [];
}
}
return $params;
}
public function getTotal()
{
return $this->modx->getCount($this->className, [
'field_name' => 'pbquiz',
'published' => 1,
'deleted' => 0,
]);
}
Метод prev() — шаг назад
Когда пользователь жмёт «Назад»:
- Шаг уменьшается на 1.
- Из сессии удаляются поля, введённые для этого шага.
- Сбрасывается валидация.
public function prev(Request $request)
{
$step = $_SESSION['pbquiz']['step'] ?? 0;
if ($step) {
$step -= 1;
}
if ($_SESSION['pbquiz']['validation']['rules'] ?? false) {
$_SESSION['pbquiz']['fields'] = array_diff_key(
$_SESSION['pbquiz']['fields'],
array_flip(array_keys($_SESSION['pbquiz']['validation']['rules']))
);
}
$_SESSION['pbquiz']['validation'] = [];
return $this->getStep($step, $request);
}
Метод next() — шаг вперёд
Когда пользователь жмёт «Далее»:
- Берутся правила валидации из текущего шага.
- Сохраняются в сессию.
- Передаётся управление в getStep — он проверит данные, сохранит их и покажет следующий шаг.
public function next(Request $request)
{
$step = $_SESSION['pbquiz']['step'] ?? 0;
$data = $this->getData($step);
$_SESSION['pbquiz']['validation'] = [];
foreach ($data['fields'] ?? [] as $field) {
$name = $field['name'];
$_SESSION['pbquiz']['validation']['rules'][$name] = $field['validation'];
$_SESSION['pbquiz']['validation']['errors'][$name] = $field['validation_error'];
}
return $this->getStep($step + 1, $request);
}
Метод getStep() — сердце квиза
Здесь всё происходит:
- Валидация данных по правилам.
- Сохранение введённого в сессию.
- Получение следующего шага.
- Если шаг последний — отправка писем менеджеру и пользователю.
public function getStep(int $step, Request $request)
{
if ($_SESSION['pbquiz']['validation']['rules'] ?? false) {
$rules = ['honeypot' => 'empty|exclude'];
foreach ($_SESSION['pbquiz']['validation']['rules'] as $name => $rule) {
$rules[$name] = $rule;
}
$errors = $_SESSION['pbquiz']['validation']['errors'] ?? [];
$field_errors = [];
foreach ($errors as $name => $error) {
$field_errors[$name . '.required'] = $error;
}
$validator = validate($request->all(), $rules, $field_errors);
if ($validator->fails()) {
return response()->error('', $validator->errors());
}
$_SESSION['pbquiz']['fields'] = array_merge(
$_SESSION['pbquiz']['fields'] ?? [],
$validator->validated()
);
}
$prevStep = $_SESSION['pbquiz']['step'] ?? 0;
$_SESSION['pbquiz']['step'] = $step;
$data = $this->getData($step);
$html = view('file:quiz/chunks/step', $data);
if ($step > $data['total']) {
$emails = config('quiz.email.manager');
if (!empty($emails)) {
foreach (explode(',', $emails) as $email) {
Mail::to(trim($email))
->subject(lang('quiz.email_manager_subject'))
->view('file:quiz/chunks/email_manager', $_SESSION['pbquiz']['fields'])
->send();
}
}
if (!empty($_SESSION['pbquiz']['fields']['email'])) {
Mail::to($_SESSION['pbquiz']['fields']['email'])
->subject(lang('quiz.email_user_subject'))
->view('file:quiz/chunks/email_user', $_SESSION['pbquiz']['fields'])
->send();
}
}
return response()->append([
'prevStep' => $prevStep,
'step' => $step,
'total' => $data['total'],
])->success($html);
}
Шаг 5. Настройте шаблоны формы и шагов
После того как таблицы созданы, маршруты подключены, а контроллер написан — нужно сделать так, чтобы данные красиво отображались пользователю. Для этого используются шаблоны и чанки.1. Основной шаблон формы
Вот базовый пример формы, которая уже готова к работе через AJAX с PageBlocks:
html...
<form action="{route 'quizNext'}" method="post" class="quiz-form w-100 h-100" pb-form>
<input type="hidden" name="_token" value="{csrf_token}">
<input type="hidden" name="honeypot" value="">
<div id="quiz-step" pb-success-message>
{insert 'file:quiz/chunks/step'}
</div>
</form>
...
Как это работает:
pb-form
Этот атрибут подключает автоматическую обработку формы через AJAX
_token
Защищает от CSRF — каждый запрос проходит проверку безопасности.
honeypot
Простая защита от ботов. Если поле заполнено — запрос отклоняется.
pb-success-message
Этот атрибут указывает PageBlocks, куда вставить HTML ответа, который вернёт контроллер. В вашем случае контроллер возвращает сгенерированный чанк step для следующего шага квиза. Поэтому старый шаг автоматически заменяется на новый — без полной перезагрузки страницы.
2. Чанк step для рендеринга шагов
Чанк отвечает за то, как выводятся заголовок, описание и поля для текущего шага. Пример базового чанка:
{if $title}
<h1>{$title}</h1>
{/if}
{if $description}
<p>{$description}</p>
{/if}
{if $fields}
<div class="quiz-fields">
{foreach $fields as $field}
{if $field['options']}
<!-- Radio или Checkbox -->
{foreach $field['options'] as $option}
<label>
<input type="{$field['type']}"
name="{$field['name']}{if $field['type']=='checkbox'}[]{/if}"
value="{$option['key']}">
{$option['value']}
</label>
{/foreach}
{else}
<!-- Текстовое или Email -->
<label>{$field['label']}</label>
<input type="{$field['type']}"
name="{$field['name']}"
placeholder="{$field['placeholder']}">
{/if}
{/foreach}
</div>
{/if}
<!-- Навигация -->
{if $step <= $total}
{include 'file:quiz/chunks/nav'}
{/if}
3. Чанк nav для кнопок «Назад» и «Далее»
<div class="form-nav d-flex gap-3 justify-content-between justify-content-md-start mt-5 mt-md-auto">
{if $step == 0}
<div class="col-12">
<button type="button" class="btn btn-lg" pb-post="{route 'quizNext'}" pb-target="#pb-quiz" pb-expect="json">{lang 'quiz.start'}</button>
</div>
{/if}
{if $step > 1}
<div class="col-auto">
<button type='button' class='btn btn-lg btn-nav btn-prev' pb-post="{route 'quizPrev'}" pb-target="#pb-quiz" pb-expect="json">{lang 'quiz.prev'}</button>
</div>
{/if}
{if $step && $step < $total}
<div class="col-auto">
<button type='submit' class='btn btn-lg btn-nav btn-next'>{lang 'quiz.next'}</button>
</div>
{/if}
{if $step == $total}
<div class="col-auto">
<button type='submit' class='btn btn-lg btn-nav btn-next'>{lang 'quiz.submit'}</button>
</div>
{/if}
</div>
Что в итоге
- Один шаблон формы — для всего квиза.
- Один чанк — для рендеринга шагов и полей.
- Один чанк навигации — для управления переходами.
- Контроллер возвращает HTML шагов, валидацию и сохраняет прогресс.
- Всё хранится в сессии — пользователь может возвращаться назад.
Заключение
Теперь у вас есть два удобных варианта:
- Если вам нужно быстро запустить квиз без лишней разработки — используйте готовый бесплатный компонент pbQuiz. Он сразу создаст все необходимые таблицы, контроллеры и структуру квиза. Вам останется только заполнить данные через меню и запустить квиз на вашем сайте.
- Если вы хотите гибко настроить всё под свои задачи или лучше разобраться в работе PageBlocks — используйте пошаговую инструкцию из этого руководства. Она показывает, как создать таблицу, подключить маршруты, написать контроллер и настроить шаблоны формы.
Поблагодарить автора
Отправить деньги
Комментарии: 3
Ветвления есть?
Статью читали? В контроллерах вы сами указываете какой шаг вам нужно показывать, да что угодно, вы можете на любом шаге сделать редирект в зависимости от ответа.
Не хотите писать код, вот готовый компонент — Quiz
Не хотите писать код, вот готовый компонент — Quiz
Вся экосистема PageBlocks вызывает огромное впечатление
Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.