jdz / form
Framework-agnostic form building, filtering and validation library
Requires
- php: >=8.2
- jdz/data: ^2.0
- jdz/htmlrenderer: ^1.0
- nesbot/carbon: ^2.0 || ^3.0
Requires (Dev)
- phpunit/phpunit: ^11.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
A framework-agnostic PHP library for building, filtering and validating forms.
Features
- ๐งฑ Rich field set: text, email, url, tel, password, search, number, date/datetime/time/month/week/year, color, file, image (media-library picker), hidden, checkbox, radio, select, textarea
- ๐งน Filters: normalize input before validation (string, int, decimal, bool, array, email, url, tel, date, time, color, or a custom callback)
- โ Rules: declarative validation with typed error handling
- ๐๏ธ Structure: group fields into fieldsets and form rows (including boolean, checkboxes, input-group and hidden rows)
- ๐ CSRF & captcha: opt-in per form
- ๐ Fluent API: chainable configuration, framework-agnostic rendering via
toData() - โ Well tested: 761 tests
Installation
composer require jdz/form
Requirements
- PHP 8.2 or higher
Quick start
use JDZ\Form\Form; use JDZ\Form\FormData; $form = new Form('contact'); $fieldset = $form->makeFormFieldset('main'); // add fields / rows to the fieldset, each with its filters and rules // bind submitted values and run the lifecycle $form->init(new FormData($_POST)); if ($form->submit()) { // $form->filter(); normalize // $form->validate(); apply rules if (!$form->getErrors()) { // persist $form->getData() } }
Forms expose their state through toData(), so rendering is left entirely to the
consuming application (Twig, plain PHP, an admin UI layer, etc.).
Every value in toData() is raw โ value, textarea content, placeholder, pattern:
the renderer escapes each once. Note that the StringFilter every field runs stores
&, < and > as &, <, >, so a renderer that escapes stored values
should not double-encode existing entities (htmlspecialchars(..., double_encode: false)).
Upgrading to 1.2.0
1.2.0 changes what forms accept and store. Check one edit form per field type after the update.
- Every field's own filter runs. The filters all shared the name
raw, so only theStringFilterever ran, andfilter()never attached a field's own filters unlessinit()was called. Now, with no filter set by hand, a field gets its own onfilter(): emails are lowercased, phones keep0-9 + . - ( )only, a bare domain getshttp://(a/path,?queryor#anchorstays as typed), colours become#RRGGBB(empty stays empty), numbers are cast (an empty one isnull), dates are stored in their storage format (a month as its first day, a week as its monday, a datetime-local asY-m-d H:i:s). - Raw values (see above):
InputFieldno longer escapes its value. - Empty is not a value: an empty
Numberisnull(a required one fails;0is a value;min/maxno longer turn an empty number into a bound). An empty or unreadableTimeisnullโ it used to be00:00โ and9:30reads as09:30; an out-of-range time is no time (it was clamped to another one). Time columns must be nullable. - New validation:
Url(an http(s) URL with a host, or a link inside the site) andTimecheck their format; date fields check their stored format, anchored (aWeekused to fail on valid data,2024-06-15xyzused to pass); a textarea is cut to itsmaxlengthserver side. - Inputgroup fills, filters and validates its field parts (defaults, filters, rules, the row's required flag); a required part left empty now fails on the server.
- Fieldsets honour their position (positioned first, then the others in order).
Form::toData()carriesprefix,csrfandcaptcha.FormRow::onPrepare()keeps a row made static withwithStatic().- A clone is an independent deep copy (filters, rules, select options); it used to reset placeholder, pattern, value, options.
CallbackFilterinstances are named one per instance, so several can sit on one field.StringFilterno longer dies on a second<script>block nor warns on a bare&.
Changelog
- 1.2.0 โ every field's own filter runs; raw values in
toData(); emptyNumber/Timearenull;Url/Time/ anchored date validation; Inputgroup parts filled, filtered and validated; fieldset positions;prefix/csrf/captchaintoData(); deep clones;StringFiltercrash and warnings fixed. See Upgrading to 1.2.0.
Testing
composer test
License
MIT โ see LICENSE.