kavalhub / form-generator
Easily create, validate, and display forms.
Requires
- php: ^8.2
Requires (Dev)
- kavalhub/form-generator-blade: @dev
- kavalhub/form-generator-bootstrap: @dev
- kavalhub/form-generator-laravel: @dev
- kavalhub/form-generator-tailwind: @dev
- kavalhub/form-generator-twig: @dev
- phpunit/phpunit: ^10.2
Suggests
- kavalhub/form-generator-blade: Blade-декоратор с шаблонами в resources/Blade/
- kavalhub/form-generator-bootstrap: Bootstrap-декоратор с PHP-шаблонами
- kavalhub/form-generator-laravel: Адаптеры Request и Validator для Laravel
- kavalhub/form-generator-tailwind: Tailwind CSS декоратор с utility-классами
- kavalhub/form-generator-twig: Twig-декоратор с шаблонами .html.twig
README
PHP-библиотека для программного создания HTML-форм, привязки данных из запроса, валидации и рендеринга с опциональным Bootstrap-декоратором.
Требования
- PHP ^8.2
- Composer
Установка
composer require kavalhub/form-generator
Laravel (опционально)
composer require kavalhub/form-generator-laravel
Быстрый старт
use Kavalhub\FormGenerator\Html\Form; use Kavalhub\FormGenerator\Html\InputSubmit; use Kavalhub\FormGenerator\Html\InputText; use Kavalhub\FormGenerator\Request\ElementRequest; use Kavalhub\FormGenerator\Validator\ElementValidator; use Kavalhub\FormGenerator\Validator\Interface\ElementValidatorInterface; $form = (new Form('contact')) ->addElement( (new InputText('email'))->setRequired()->setPlaceholder('Email') ) ->addElement( (new InputSubmit('send'))->setDefaultValue('Отправить') ); /** @var ElementValidatorInterface $validator */ $validator = new ElementValidator(new ElementRequest()); $submit = $form->getByName('send'); if ($validator->checkSubmit($submit) && $validator->handle($form)) { // данные валидны } echo $form->render();
Точки расширения
Библиотека построена на интерфейсах — реализации можно подменять:
| Интерфейс | Назначение | Реализации в пакете |
|---|---|---|
RequestInterface |
Источник данных формы | ElementRequest, ArrayRequest, PostOnlyRequest |
ElementValidatorInterface |
Валидация и bind | ElementValidator |
DecoratorInterface |
Рендеринг с темой | AbstractDecorator; Bootstrap — пакет form-generator-bootstrap |
AjaxRenderStrategyInterface |
HTML/CSS для AJAX-патчей | NullAjaxRenderStrategy; Bootstrap — в bootstrap-пакете |
ElementEventDispatcher |
События элементов (связанные select, фильтры) | ElementChangedEvent, слушатели в приложении |
Подробнее: docs/element-events.md, docs/custom-templates.md.
flowchart LR
App[Приложение] --> RequestInterface
App --> ElementValidatorInterface
App --> DecoratorInterface
App --> AjaxRenderStrategyInterface
RequestInterface --> ElementRequest
RequestInterface --> PostOnlyRequest
RequestInterface --> LaravelRequestAdapter
ElementValidatorInterface --> ElementValidator
ElementValidatorInterface --> LaravelElementValidator
DecoratorInterface --> AbstractDecorator
AjaxRenderStrategyInterface --> NullAjaxRenderStrategy
DecoratorInterface --> BootstrapPackage[form-generator-bootstrap]
AjaxRenderStrategyInterface --> BootstrapPackage
Loading
Request: GET, POST и свои адаптеры
ElementRequest — по умолчанию ($_REQUEST)
Читает GET + POST + cookies. Подходит для фильтров и форм, отправляемых GET-запросом (см. demo-проект kavalhub/form-demo, каталог src/).
Demo-приложение
Примеры использования (Kavalhub\Example\) вынесены в отдельный проект и не входят в autoload при composer require kavalhub/form-generator. В репозитории библиотеки каталог example/ доступен только через autoload-dev для PHPUnit.
$request = new ElementRequest();
PostOnlyRequest — только POST
use Kavalhub\FormGenerator\Request\PostOnlyRequest; $request = new PostOnlyRequest();
ArrayRequest — для тестов и API
use Kavalhub\FormGenerator\Request\ArrayRequest; $request = new ArrayRequest(['contact_email' => 'a@b.c']);
Свой адаптер
Реализуйте RequestInterface::get(string $name): ?array — метод возвращает массив значений для поля с данным именем (getFormName()).
Validator
Контракт ElementValidatorInterface:
checkSubmit(InputSubmit $submit): bool— была ли отправлена формаhandle(ElementInterface $element): bool— bind из request, required, callbacks, CSRFisValid(): ?bool— результат последней проверки
Внедряйте интерфейс, а не конкретный класс:
public function __construct(private readonly ElementValidatorInterface $validator) {}
Callback-валидаторы
$input->addCallbackValidator(function (InputText $el): bool { if (!str_contains($el->getValue(), '@')) { $el->addError(['Некорректный email']); return false; } return true; });
Laravel-интеграция
Пакет kavalhub/form-generator-laravel — гибридный валидатор:
- Core (
ElementValidator) — bind, required, callbacks, CSRF - Laravel (
illuminate/validation) — правилаrequired|emailи т.д.
use Illuminate\Validation\Factory; use Kavalhub\FormGenerator\Html\Form; use Kavalhub\FormGenerator\Html\InputText; use Kavalhub\FormGenerator\Laravel\LaravelElementValidator; use Kavalhub\FormGenerator\Laravel\LaravelRequestAdapter; $request = new LaravelRequestAdapter($illuminateRequest); $validator = new LaravelElementValidator($request, app(Factory::class)); $validator->setRules([ 'contact_email' => 'required|email', ]); $form = (new Form('contact'))->addElement((new InputText('email'))->setRequired()); if ($validator->handle($form)) { // OK }
Ошибки Laravel автоматически попадают в addError() элементов через ElementDataCollector.
Bootstrap-декоратор
Пакет kavalhub/form-generator-bootstrap (с 3.3 вынесен из core):
composer require kavalhub/form-generator-bootstrap
use Kavalhub\FormGenerator\Bootstrap\BootstrapDecorator; use Kavalhub\FormGenerator\Decorator\Interface\DecoratorInterface; /** @var DecoratorInterface $decorator */ $decorator = new BootstrapDecorator($form); echo $decorator->getHtml();
Кастомные шаблоны для дизайнеров: docs/custom-templates.md.
Blade-декоратор
Пакет kavalhub/form-generator-blade — те же Bootstrap-стили, шаблоны {ClassName}.php в каталоге resources/Blade/:
composer require kavalhub/form-generator-blade
use Kavalhub\FormGenerator\Blade\BladeDecorator; echo (new BladeDecorator($form)) ->setTemplate(__DIR__ . '/resources/form-templates') ->getHtml();
Для AJAX: BladeAjaxRenderStrategy. Demo поддерживает переключатель HTML / Bootstrap / Blade и per-element шаблон для фасета «Бренд».
CSRF-защита (opt-in)
$form = (new Form('secure')) ->enableCsrf() ->addElement(/* ... */);
AJAX (3.1+)
Библиотека не навязывает JS-фреймворк. Сервер возвращает JSON с ключом REPLACE — массив патчей DOM. Два режима:
| Режим | Метод | Ответ |
|---|---|---|
| field | ElementAjaxHandler::handleField() |
ID, CLASS, ERROR (через AjaxRenderStrategyInterface) |
| form/block | ElementAjaxHandler::handleForm() / handleBlock() |
ID, HTML (через стратегию, напр. BootstrapAjaxRenderStrategy) |
Поиск элемента по DOM-id: ElementDataCollector::findById() или $form->getById().
Короткое имя поля — getByName(); для AJAX используйте getId() / getFormName().
Разметка AJAX на форме и полях
На Form, InputText, InputSubmit и других элементах с HtmlAttributes доступны:
$form->setMethod('get') ->setAjax(true) ->setUrlState('replaceState'); // 'pushState' | false — не менять URL $input = (new InputText('name'))->setAjax();
В HTML: data-fg-ajax="true", опционально data-fg-url-state="replaceState".
setAjax() на форме — перехват submit и (в demo) change на полях фильтра; на поле — field mode (action = getId()).
В demo URL state и AJAX POST используют одни и те же ключи, что collectPageData() (getFormName() из DOM): например demoSettings_decoratorFieldset_decorator, fl_gc_cat[], page. Decorator читается только по полному ключу demoSettings_decoratorFieldset_decorator (или из session).
Endpoint (пример)
use Kavalhub\FormGenerator\Ajax\AjaxRequest; use Kavalhub\FormGenerator\Ajax\ElementAjaxHandler; use Kavalhub\FormGenerator\Bootstrap\BootstrapAjaxRenderStrategy; use Kavalhub\FormGenerator\Request\ElementRequest; use Kavalhub\FormGenerator\Validator\ElementValidator; header('Content-Type: application/json; charset=utf-8'); if (!AjaxRequest::isXmlHttpRequest()) { http_response_code(400); exit; } $validator = new ElementValidator(new ElementRequest()); $handler = new ElementAjaxHandler($validator, new BootstrapAjaxRenderStrategy()); $form = /* ваша форма */; if ($targetId = AjaxRequest::readTargetId()) { echo $handler->handleField($form, $targetId)->jsonEncode(); exit; } if ($validator->checkSubmit($submit) && $validator->handle($form)) { echo $handler->handleBlock($table)->setMessage('Сохранено')->jsonEncode(); }
Параметр action (или target_id) = getId() поля, как в demo.
Клиент (минимальный пример, не входит в пакет)
function collectPageData() { const body = new FormData(); document.querySelectorAll('form').forEach((form) => { new FormData(form).forEach((value, key) => body.append(key, value)); }); return body; } function applyUrlState(form) { const mode = form?.dataset?.fgUrlState; if (!mode) return; const params = new URLSearchParams(); collectPageData().forEach((value, key) => params.append(key, value)); const url = `${location.pathname}?${params}`; (mode === 'pushState' ? history.pushState : history.replaceState).call(history, null, '', url); } document.querySelector('[data-fg-ajax="true"]').addEventListener('input', function () { const fd = collectPageData(); fd.set('action', this.id); fd.set(this.name, this.value); fetch('/ajax.php', { method: 'POST', body: fd, headers: { 'X-Requested-With': 'XMLHttpRequest' } }) .then(r => r.json()) .then(data => { data.REPLACE.forEach(patch => { const el = document.getElementById(patch.ID); el.classList.remove('is-valid', 'is-invalid'); if (patch.CLASS) el.classList.add(patch.CLASS); el.parentElement.querySelectorAll('.invalid-feedback').forEach(n => n.remove()); if (patch.ERROR) el.insertAdjacentHTML('afterend', patch.ERROR); if (patch.HTML) document.getElementById(patch.ID).outerHTML = patch.HTML; }); applyUrlState(this.closest('form[data-fg-url-state]')); }); });
Живой пример с переключателем «классика / AJAX», синхронизацией URL (setUrlState) и восстановлением фильтра из GET — demo-проект kavalhub/form-demo: главная демонстрация на ?page=filter (фильтр товаров, чекбоксы/радио на лету), также ?page=facet (добавление фасета).
JSON API (3.2+)
Структурированный обмен без HTML-патчей: валидация и сабмит формы через JSON.
| Класс | Назначение |
|---|---|
Request\JsonElementRequest |
Источник данных из JSON / массива |
Api\FormApiHandler |
handleField() / handleForm() → FormApiResponse |
Api\FormJsonSchemaExporter |
JSON Schema полей формы (интроспекция дерева) |
Api\OpenApiDocumentBuilder |
Сборка OpenAPI 3.0 из списка форм |
use Kavalhub\FormGenerator\Api\FormApiHandler; use Kavalhub\FormGenerator\Request\JsonElementRequest; use Kavalhub\FormGenerator\Validator\ElementValidator; $request = JsonElementRequest::fromArray(['contact_email' => 'user@example.com']); $handler = new FormApiHandler(new ElementValidator($request)); $response = $handler->handleForm($form); echo $response->jsonEncode(); // {"valid":true,"fields":{...},"data":{...}}
OpenAPI и наполнение БД через JSON — demo: GET /api.php, POST /api.php, Swagger UI на /api-docs.html.
Сбор данных из дерева элементов
use Kavalhub\FormGenerator\Util\ElementDataCollector; $data = ElementDataCollector::collectByFormName($form); // ['contact_email' => 'user@example.com', ...]
Поддерживаемые элементы
| Класс | Описание |
|---|---|
Html\Form |
Контейнер <form> |
Html\Group |
Группа полей с префиксом имени |
Html\InputText, Html\InputPassword, Html\InputNumber |
Текстовые поля |
Html\InputCheckbox, Html\InputRadio |
Переключатели |
Html\Select, Html\Option |
Выпадающий список |
Html\Textarea |
Многострочный ввод |
Html\InputHidden, Html\InputSubmit, Html\Button |
Скрытые и кнопки |
Html\Label, Html\Nav, Html\Link |
Разметка |
Html\Table\Table, Html\Table\Tr, Html\Table\Td, Html\Table\Th |
Таблицы |
Миграция 2.x → 3.x
- Namespace виджетов:
Kavalhub\FormGenerator\Form\*→Kavalhub\FormGenerator\Html\* - Таблицы:
Kavalhub\FormGenerator\Table\*→Kavalhub\FormGenerator\Html\Table\* - Рендеринг элементов:
getHtml()→render()(декораторы по-прежнему используютgetHtml()) Element— доменная модель без HTML; HTML-трейты и виджеты вsrc/Html/HtmlEscaperперенесён вKavalhub\FormGenerator\Html\Util\HtmlEscaper- Базовые HTML-классы:
HtmlElement,HtmlElementWithValue,HtmlCompositeElement— содержатtag,ClassList,Path - Доменный
Elementне имеетtag,getTag(),addClass()— только дерево, значения и валидация
Безопасность
- Значения полей, placeholder, href и сообщения об ошибках экранируются через
Html\Util\HtmlEscaper. Label::setAllowHtml()— явное разрешение HTML в подписи.ElementRequestиспользует$_REQUEST— удобно для GET-фильтров; для POST-only используйтеPostOnlyRequest.- CSRF включается явно через
Form::enableCsrf().
Тесты
composer install
composer test
Лицензия
MIT