Search by

jdz / form

jdz

Framework-agnostic form building, filtering and validation library

1.2.0 2026-10-11 11:53 UTC

This package is auto-updated.

Last update: 2026-10-11 11:53:13 UTC


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 &amp;, &lt;, &gt;, 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 the StringFilter ever ran, and filter() never attached a field's own filters unless init() was called. Now, with no filter set by hand, a field gets its own on filter(): emails are lowercased, phones keep 0-9 + . - ( ) only, a bare domain gets http:// (a /path, ?query or #anchor stays as typed), colours become #RRGGBB (empty stays empty), numbers are cast (an empty one is null), dates are stored in their storage format (a month as its first day, a week as its monday, a datetime-local as Y-m-d H:i:s).
  • Raw values (see above): InputField no longer escapes its value.
  • Empty is not a value: an empty Number is null (a required one fails; 0 is a value; min / max no longer turn an empty number into a bound). An empty or unreadable Time is null โ€” it used to be 00:00 โ€” and 9:30 reads as 09: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) and Time check their format; date fields check their stored format, anchored (a Week used to fail on valid data, 2024-06-15xyz used to pass); a textarea is cut to its maxlength server 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() carries prefix, csrf and captcha.
  • FormRow::onPrepare() keeps a row made static with withStatic().
  • A clone is an independent deep copy (filters, rules, select options); it used to reset placeholder, pattern, value, options.
  • CallbackFilter instances are named one per instance, so several can sit on one field.
  • StringFilter no 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(); empty Number / Time are null; Url / Time / anchored date validation; Inputgroup parts filled, filtered and validated; fieldset positions; prefix / csrf / captcha in toData(); deep clones; StringFilter crash and warnings fixed. See Upgrading to 1.2.0.

Testing

composer test

License

MIT โ€” see LICENSE.