Changelog¶
All notable changes to ephraitech/ci4-forms are documented in this file.
The format follows Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased¶
0.5.0 - 2026-10-29¶
Documentation site with tested examples, themes that extend other themes, and genuine PUT, PATCH and DELETE request bodies. No breaking changes.
Added¶
- Documentation site, built from
docs/with Zensical (readingmkdocs.yml) and published to GitHub Pages on release tags by a new Docs workflow that builds in strict mode on every push and pull request. Code examples are real files indocs/examples/app/, included into the pages and run by a newtests/Docssuite, so published examples always work. CONTRIBUTING.mdwith instructions for running the tests and previewing the documentation.- Getting started tutorial: appointment booking with a two-column layout, a business rule and editing, built from tested example files run over HTTP against a database.
- Guides for layout, multiple choices, file uploads, custom field types and customising views,
each with tested examples (a real
app/Config/Forms.phpexample is tested in separate processes so it cannot affect other tests). - Themes can extend another theme (
'extends' => 'bootstrap5'): templates missing from a theme's views folder are taken from the theme it extends, so a custom theme only needs the templates it changes. The parent must be a registered theme; loops are guarded against. - Guides for option providers, forms written by admins, showing submissions, testing your
forms, and JavaScript and APIs, with tested example apps (an admin form editor and public
form pages, a submissions screen, a JSON API) and example tests that use CodeIgniter's
standard
$migrate = true. tests/bootstrap.phptreatsdocs/examples/appas part of the test app, so example migrations and views work as they would in a real app.- Reference section: Form, Fields, Schemas, Configuration, and Helpers and commands, written
by hand and checked by
ApiReferenceTest(every public method, setting, theme class key, helper and command option must be documented, and everything documented must exist), plus a Schema keys page generated fromFormSchema::describe()withcomposer docs:referenceand checked bySchemaReferenceTest. - Concepts section explaining values, validation, security and architecture, with every
statement checked by
ConceptsClaimsTest(each test is named after the page section that makes the claim); Upgrading and Changelog pages (the changelog page includes this file).
Fixed¶
validate()ignored the data of genuinePUT,PATCHandDELETErequests (from JavaScript or API clients), whose URL-encoded body PHP does not put in$_POST. It now reads the raw body for those methods when$_POSTis empty. Browser forms, which sendPOSTwith a hidden_methodfield, were not affected.
0.4.0 - 2026-10-29¶
Showing stored data, a forms helper, and FormSchema::describe() as groundwork for visual
builders. No breaking changes.
Added¶
Form::present($data)turns stored data, such as a saved submission, into readable rows: field labels, option labels instead of values (including groups and provider options), Yes/No for checkboxes and lists for multi-value fields. Values no longer among the options are shown as stored. Passwords, buttons and uploads are never included.Form::renderPresented($data)renders them with the newpresentedtheme view.Contracts\PresentableFieldInterface(isPresentable(),presentValue()), implemented by every field; custom fields can overridepresentValue(), for example to format amounts.- Config
$presentYes,$presentNo,$presentEmptyand theme classespresented,presented_label,presented_value,presented_empty. - A
formshelper (helper('forms')) withforms_load(),forms_check()andforms_present(). Functions are prefixedforms_so they never clash with CodeIgniter'sformhelper. FormSchema::describe($policy)describes what a schema may contain under a policy, as JSON-ready data: every allowed field type with its label, accepted keys, the kind of value each key takes and a one-line explanation, which keys are required (requires) or cannot be combined (exclusive), plus the allowed rules with their parameter kinds, attributes, option providers, upload types, size limits and forbidden names. Groundwork for visual builders and admin help pages; a test loads every described key through the loader under each policy, so the description and the loader cannot disagree.SchemaLoader::keysFor()(the loader's own key lists, shared withdescribe()) andSchemaPolicy::getRules(),getAttributes(),getForbiddenNames(),getLimits().
0.3.0 - 2026-10-28¶
Forms from schemas: load forms from arrays or JSON written by developers or admins, with a restricted policy by default, option providers, admin checks and export. No breaking changes.
Added¶
- Schema loading:
Form::fromSchema(),Form::fromJson()and$form->loadSchema()build forms from arrays or JSON (schema version 1): every built-in field type and option, rows and fieldsets, form settings, and registered custom types. Loading is all or nothing: the schema is first built on a scratch form and every error is collected with its location (fields[2].children[0].max_size: ...), including typo suggestions and hints for HTML habits such asmaxlength. Keys are applied in schema order. Schema\SchemaExceptionwithgetErrors()andgetMessages();Schema\SchemaLoader::errors()checks a schema without loading it.Contracts\SchemaConfigurableInterfacelets custom field types accept extra schema keys.Schema\SchemaPolicy: every schema is checked against a policy, restricted by default (Form::fromSchema(),fromJson()andloadSchema()all take an optionalpolicy).SchemaPolicy::restricted(), for schemas written by admins or clients, blocks event-handler and behaviour attributes (on*,formaction,style,data-*), rules outside an allow-list with checked parameters (nois_unique,regex_matchor custom rules), a schema-set form action, uploads withoutmax_size(capped at 10 MB) or without an explicitmimeslist from safe types (no SVG or HTML), protected field names such asroleandis_admin,passwordandhiddenfields, unlisted custom types, and oversized schemas.SchemaPolicy::trusted()is for developer-written schemas. Policies are immutable and can be customised (allowTypes(),allowRules(),allowAttributes(),allowMimes(),forbidNames(),allowNames(),withLimits(),withMaxUploadSize(),allowAction()); unsafe allowances such ason*attributes or SVG uploads are refused.- Option providers: named sources of options registered in
$optionProviders([ClassName::class, 'method']for static methods, CodeIgniter models or plain classes,'ClassName::method', or a Closure). Schemas use"options_from": "services"; code uses->optionsFrom('services'). Providers run lazily, once per field, and never while a schema is loaded or checked; strict option checking uses their options.SchemaPolicycan narrow which providers admin schemas may use (onlyProviders(),disallowProviders()).Support\OptionProviderschecks registrations;getOptionProvider()on choice fields. Form::toSchema()andForm::toJson()export any form, including form classes, as a version 1 schema that loads back into an identical form (same schema, same HTML, same validation). Provider options are exported asoptions_from; attributes with their own key (autocomplete,rows,step,min, ...) are exported under it. Closure rules and unregistered custom field classes make the export fail with their location.Schema\FormSchema::check()for admin screens: checks array or JSON input without throwing and returns aSchemaCheck(isValid(),getErrors(),getMessages(),getErrorsFor(),getSchema(),toArray()for JSON responses); restricted by default.FormSchema::load()andFormSchema::export()shortcuts.php spark forms:schema check <file> [--trusted]andphp spark forms:schema export <FormClass> [--output <file>].SchemaConfigurableInterface::schemaValues()so custom fields can export their extra keys;Field::getExplicitId(),Field::getOwnAttributes(),Form::getExplicitId(),File::isUploadRequired(),File::getExtraRules().
Fixed¶
- Forms could not be created on the command line (spark commands, queue workers, seeders):
checking reserved field names called
csrf_token(), which starts the Security service and needs a web request. The CSRF token name is now read from the Security config. - A source-integrity test that fails when a docblock missing its closing
*/hides class members.
0.2.0 - 2026-10-28¶
Multi-value fields, strict option checking, file uploads, layout, new inputs and custom field types. Read "Upgrading from 0.1" in the README before upgrading.
Added¶
- Multi-value fields:
select()->multiple()(withsize()) and a newcheckboxes()group field. Both submit asname[]andvalidated()returns a list, empty when nothing is chosen. eachRules()for rules checked against every item of a multi-value field, using CodeIgniter'sname.*wildcard.permit_emptyis added automatically so an empty list passes; userules('required')to require at least one item.- New
field_checkboxesview; views receive$inputName,$valuesand$multiple. - Strict option checking: submitted values for selects, multi-selects, radios, checkbox
groups and single checkboxes must be among the field's options (for a checkbox, its checked
value). Controlled by
$strictOptionsand$strictOptionsMessagein config, per field with->strictOptions(bool)and->messages(['options' => '...']). Errors surviveredirect()->withInput(). Contracts\ChoiceFieldInterface(getAllowedValues(),getStrictOptions()) for custom fields with a fixed set of values;Form::usesStrictOptions().- File uploads:
file()field withrequired(),multiple(),maxSize()(kilobytes),extensions(),mimes(),image()andmaxDimensions(), mapped to CodeIgniter's file rules, plus a generatedacceptattribute. Optional file fields are only validated when a file is uploaded. Uploads are never part ofvalidated(); get them withForm::getFile(),Form::getFiles()andForm::hasUploadedFile(). - Forms with a file field are sent as multipart automatically.
- After a failed submission, file fields show a "choose the file again" hint
(
$fileReselectMessage, theme classreselect), since browsers cannot refill them. Contracts\FileFieldInterfacefor custom upload fields; newfield_fileview.- Theme templates missing from a theme's views folder fall back to the package's bundled copy
of that theme, so views published before an upgrade keep working with new field types.
php spark forms:publishadds the missing templates without touching customised ones. - Layout:
row()places fields side by side andfieldset()groups them under a titled section (description(),id()); both nest and takeaddClass(). Fields and fieldsets getwidth(1-12)for their column inside a row; fields without a width share the row. Layout is defined in the form class and rendered by<?= $form ?>andfields(); fields themselves stay a flat list, so values, validation andfield()are unaffected. Form::getLayout(),Layout\Row,Layout\Fieldset; newlayout_rowandlayout_fieldsetviews and theme classesrow,column({width}placeholder),column_auto,section,section_legend,section_description.- New inputs:
time(),datetimeLocal(),color()(values lower-cased) andsearch().date(),time()anddatetimeLocal()sharemin(),max()andstep()through the newFields\DateTimeInputFieldbase class. - Custom field type registry:
$fieldTypes = ['money' => MoneyField::class]in config makes$this->money('price')a builder method, including inline chaining and inside groups. Registrations are checked: camelCase names, no clashes with built-in types orFormmethods, and classes must implementFieldInterface. Form::addField($type, $name, $label)creates any built-in or registered field by type key;Form::hasFieldType();Support\FieldTypes::BUILT_INlists the built-in keys.- Custom fields can render their own view: a
VIEWcontaining a path (fields/moneyorVendor\Package\Views\money) is loaded as given instead of from the theme. Field views can ask$class()for any theme class key.
Changed¶
- Behaviour change: strict option checking is on by default. A form that accepted values
outside a field's options in 0.1 now rejects them. Set
$strictOptions = falseinapp/Config/Forms.phpto restore the 0.1 behaviour, or opt single fields out with->strictOptions(false). Existingin_listrules keep working; they are now optional for option fields. FieldInterfacehas three new methods:getEachRules(),isMultiple()andgetWidth(). Custom fields extendingFields\Fieldget both automatically; classes implementing the interface directly must add them.getOptionId()moved fromRadiointo the sharedHasOptionstrait.fields()with no arguments now renders the form's layout (rows and fieldsets) instead of a flat list.fields('a', 'b')with names is unchanged. Publishedform.phpviews get layouts automatically.- The
field_selectview uses$inputNameand$values. Re-publish the views (php spark forms:publish --force) or apply the same change if you customised it.
Fixed¶
- An array posted to a single-value field (for example
nickname[]=x) could pass validation on fields without rules and reachvalidated()as an array. Single-value fields now treat a posted array as no value, and an unticked checkbox's array counts as unticked.
0.1.0 - 2026-10-27¶
First release. Requires PHP 8.2+ and CodeIgniter 4.7+ (every 4.6 release has published security advisories).
Added¶
- Package skeleton: Composer manifest, PSR-4 autoloading (
Ephraitech\Forms\→src/), PHPUnit configuration on the CodeIgniter 4 test bootstrap, and environment smoke tests. Config\Forms: theme registry (Bootstrap 5 by default) with view paths, CSS classes and required-field marker; behaviour switches for CSRF, method spoofing,novalidate, rule-derived HTML attributes, error summary, old input, id prefixing, string trimming, empty-to-null conversion and unchecked checkbox values.Exceptions\FormExceptionfor configuration and definition errors.- Field layer:
Contracts\FieldInterface, baseFields\Field(fluent setters, rules with custom messages, safe attribute handling, CSS classes, help text, ids prefixed by form name, required detection, rule-derived HTML attributes, value normalisation, chaining back to the parent form) and field typesText,Email,Password,Number,Tel,Url,Date,Hidden,Textarea,Select(with option groups),Radio,CheckboxandSubmit. - Lazy, cached option closures for
SelectandRadio. Support\RuleParserfor bracket-aware splitting and reading of CodeIgniter rule strings.Formclass: class-based forms viadefine()and inline forms viaForm::make()withend(); names derived from class names; action, method (with PUT/PATCH/DELETE spoofing), id, classes, attributes and multipart settings; typed builders for every field type plusadd()for custom fields;remove(),has(),getField(),getSubmittableFields();fill()from arrays, Entities and objects; value resolution in priority order (submitted, old input, filled, default) with correct unticked-checkbox and password handling; old input scoped to the form that produced it via a hidden_formkey.- Validation:
validate()from the current request, anIncomingRequest(POST, GET or JSON) or an array, through CodeIgniter's shared Validation service with field labels and custom messages; values trimmed before validation;prepareForValidation()hook;getValidationRules()export;isValid(). validated()returning only defined, submittable fields, normalised per config, with unchecked checkbox values and optional excluded fields.- Errors:
getErrors(),getError(),hasError(),hasErrors(),clearErrors()andsetError()for manual errors; errors (including manual ones) surviveredirect()->withInput()and are scoped to the form that produced them. - Rendering:
Rendererplus Bootstrap 5 theme views for every field type, the whole form and an error summary.Formgainsrender()/__toString(),open()(hidden form key, CSRF,_methodspoofing, multipart,novalidate),close(),field(),fields(),control()for custom layouts anderrorSummary(). Output is escaped likeesc(), with error and help text linked througharia-describedbyandaria-invalid. php spark forms:publishcommand (--theme,--config,--force) to copy the theme views and an app config file into the application; existing files are kept unless forced.- GitHub Actions workflow testing PHP 8.2 to 8.5 against CodeIgniter 4.7.