pbStudio: Чистый контроллер или FetchIt — два способа обработки форм



В предыдущей статьи мы создали модальное окно с формой.
В этом уроке я покажу, как можно обработать эту форму двумя способами.

Навигация по урокам:

1. Обработка формы через чистый контроллер


В форме мы указали action="/form/submit", значит, нужно создать соответствующий маршрут для обработки:

1. Создаём новый маршрут в core/App/routes/web.php:
use PageBlocks\App\Http\Controllers\FormController;

Route::post('/form/submit', [FormController::class, 'handle']);

2. Создаём контроллер FormController:
core/App/Http/Controllers/FormController.php:
<?php

namespace PageBlocks\App\Http\Controllers;

use Boshnik\PageBlocks\Facades\Request;
use Boshnik\PageBlocks\Support\Mail;

class FormController extends Controller
{
    public function handle(Request $request)
    {
        // Валидация формы
        $validated = $request->validate([
            'honeypot' => 'empty', // антиспам: поле должно быть пустым
            'formname' => 'required|string',
            'name' => 'required|string|max:255',
            'email' => 'required|email',
            'file' => 'required|file|mimes:pdf,doc,docx,jpg|min:100|max:2048',
            'message' => 'nullable|string', // необязательное текстовое поле
        ]);

        // Сохраняем файл в папку assets/images/files
        $validated['file'] = $request->file('file')->store('assets/images/files');

        // Добавим IP
        $validated['ip'] = $request->ip();

        // Сохраняем форму в таблицу значений
        $form = $this->modx->newObject(\pbTableValue::class, [
            'model_type' => 'pbMenu', // Тип модели
            // 'model_type' => 'MODX\Revolution\modDocument', Если таблица на странице ресурса
            // 'model_type' => 'MODX\Revolution\modUser', Если таблица на странице пользователя
            'field_name' => 'forms', // Имя поля таблицы
            'createdon' => time(),
            'values' => json_encode($validated), // Сериализованные данные формы
        ]);

        if (!$form->save()) {
            $this->modx->log(modX::LOG_LEVEL_ERROR, 'Не удалось сохранить форму: ' . print_r($validated, true));
        }

        // Отправка письма менеджеру
        Mail::to('pageblocks@boshnik.com')
            ->subject('Отправка резюме')
            ->view('chunks/email/manager', $validated)
            ->attach($validated['file'])
            ->send();

        // Отправка письма пользователю
        Mail::send($validated['email'], 'Ваше резюме принято', 'Мы скоро с вами свяжемся!');
        // или через хелпер:
        // mail($validated['email'], 'Ваше резюме принято', 'Мы скоро с вами свяжемся!');

        // Если запрос был через AJAX — возвращаем JSON
        if ($request->expectsJson()) {
            return response()->json([
                'success' => true,
                'message' => 'Форма успешно отправлена',
                // 'redirect' => '/contacts', Если нужен редирект
            ]);
        }

        // В противном случае — редирект
        return response()->redirect('/contacts');
    }
}

3. Создаём таблицу для хранения данных (core/App/Models/MainMenu.php):
Menu::make('Forms')
    ->title('Формы')
    ->description('Сохраненные формы')
    ->fields([
        // Создаем таблицу с именем forms
        Field::make('Forms')
            ->type('table')
            ->fields([
                
                Field::make('formname')
                    ->type('hidden'),
                
                Field::make('createdon')
                    ->label('Date')
                    ->type('date')
                    ->width(50)
                    ->storage('timestamp')
                    ->dateFormat('Y-m-d, H:i')
                    ->readOnly(),
                
                Field::make('IP')
                    ->width(50)
                    ->readOnly(),
                
                Field::make('Name'),
                
                Field::make('Email'),
                
                Field::make('Message')
                    ->type('textarea')
                    ->height(50),
                
                Field::make('File')->type('file'),
            ])
            // Показываем колонки
            ->columns([
                Column::make('Name'),
                Column::make('Email'),
                Column::make('Createdon')
                    ->render('date')
                    ->format('Y-m-d, H:i'),
                Column::make('Editedon')
                    ->render('date')
                    ->format('Y-m-d, H:i'),
                Column::make('IP')
            ])
            // Добавляем фильтры
            ->filters([
                Filter::make('formName')
                    ->label('Form name')
                    ->type('select')
                    ->options([
                        'Job' => 'Job',
                    ]),
                Filter::make('createdon')
                    ->label('Date'),
            ]),
    ])

4. Шаблон письма для менеджера (core/App/elements/chunks/email/manager.tpl):
<ul>
    <li><b>Name</b>: {$name}</li>
    <li><b>Email</b>: {$email}</li>
    {if $message}
        <li><b>Message</b>: {$message}</li>
    {/if}
</ul>

5. Скрипты для обработки формы и уведомлений (в core/App/elements/chunks/head.tpl):
{*Form*}
<script src="/assets/components/pageblocks/js/web/pageblocks.js" defer></script>

{*SweetAlert2*}
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11" defer></script>

Для формы добавляем атрибут data-pbform.

6. Подключаем SweetAlert2 (core/App/elements/templates/base.tpl)
<script>
    document.addEventListener('DOMContentLoaded', () => {
        PageBlocks.Message = {
            success(message) {
                Swal.fire({
                    position: 'top-end',
                    title: message,
                    showConfirmButton: false,
                    timer: 1500
                });
            },
            error(message) {
                Swal.fire({
                    position: 'top-end',
                    title: message,
                    showConfirmButton: false,
                    timer: 1500
                });
            }
        };
        // Close modal
        document.addEventListener('pageblocks:success', (e) => {
            const modal = e.detail.form.closest('.modal');
            if (modal) {
                bootstrap.Modal.getInstance(modal)?.hide();
            }
        });
    });
</script>

CSRF-токен
Так как любые POST-запросы проходят проверку CSRF, мы в нашу форму добавили скрытое поле:
<input type="hidden" name="_token" value="{csrf_token}">

Таблица в админке:


2. Обработка формы через FetchIt


1. Устанавливаем компоненты FormIt и FetchIt.
2. Подключаем в head.tpl необходимые скрипты:
{*FetchIt*}
 <script src="/assets/components/fetchit/js/fetchit.min.js"></script>
 <script async src="/assets/components/fetchit/lib/notyf.min.js"></script>

{*Notyf*}
 <link rel="preload" href="/assets/components/fetchit/lib/notyf.min.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
 <noscript><link rel="stylesheet" href="/assets/components/fetchit/lib/notyf.min.css"></noscript>

3. Роут для FetchIt
Так как у нас включён полный контроль над маршрутами, запрос, который делает FetchIt к /assets/components/fetchit/action.php, будет блокироваться.
Поэтому создаём соответствующий маршрут в файле core/App/routes/web.php:
use PageBlocks\App\Http\Controllers\FetchItController;

Route::post('/assets/components/fetchit/action.php', [FetchItController::class, 'action']);

4. Создаем контроллер FetchItController
<?php

namespace PageBlocks\App\Http\Controllers;

class FetchItController extends Controller
{
    public function action()
    {
        $FetchIt = $this->modx->services->get('FetchIt');
        if (!isset($_POST)) {
            $site_start = $this->modx->makeUrl($this->modx->getOption('site_start'), '', '', 'full');
            redirect($site_start);
        } elseif (empty($_SERVER['HTTP_X_FETCHIT_ACTION'])) {
            return $FetchIt->error('fetchit_err_action_ns');
        } else {
            return $FetchIt->process($_SERVER['HTTP_X_FETCHIT_ACTION'], array_merge($_FILES, $_REQUEST));
        }
    }
}

5. Обновляем чанк modal.tpl:
<div class="modal fade" id="jobModal" tabindex="-1" aria-hidden="true">
     <div class="modal-dialog modal-dialog-centered">
         <div class="modal-content">
             <div class="modal-header">
                 <h1 class="modal-title fs-5">Присоединяйся к команде PageBlocks</h1>
                 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
             </div>
             {'!FetchIt' | snippet : [
                 'snippet' => 'FormIt',
                 'form' => 'jobForm',
                 'hooks' => 'FormItSaveForm,email',
 
                 'emailTpl' => 'jobEmail',
                 'emailSubject' => 'Тема письма',
                 'emailTo' => 'Superboshnik@gmail.com',
 
                 'formName' => 'Job',
                 'formFields' => 'name,email,message',
                 'fieldNames' => 'name==Name,email==Email,message==Message',
 
                 'validate' => 'name:required,email:required,file:required',
                 'validationErrorMessage' => 'В форме содержатся ошибки!',
                 'successMessage' => 'Сообщение успешно отправлено',
             ]}
         </div>
     </div>
 </div>
Саму форму помещаем в чанк jobForm, а шаблон письма в jobEmail (аналогичный нашему файловому manager.tpl)

Заключение:


Теперь у вас есть 2 способа обработки форм:

  • Собственный контроллер — максимум гибкости, отправка писем, валидация, сохранение файлов и данных в таблицу PageBlocks.
  • FetchIt + FormIt — быстро и просто, если нужно использовать уже знакомые компоненты.
Не забывайте ставить лайки!
Aleksandr Huz
18 апреля 2025, 08:42
modx.pro
366
+6
Поблагодарить автора Отправить деньги

Комментарии: 1

Баха Волков
Вчера в 10:34
+1
Жаль, что редко стал заходить на форум и не успел поставить лайк и поблагодарить. Отличные уроки!
    Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
    1