contenir / formbuilder
Framework-agnostic form-builder engine for Contenir CMS — runtime-editable form definitions, builder-form construction, server-side submission validation, and their storage with php-db on MySQL or SQLite.
Requires
- php: ~8.3.0 || ~8.4.0 || ~8.5.0
- ext-fileinfo: *
- laminas/laminas-filter: ^2.30
- laminas/laminas-form: ^3.20
- laminas/laminas-inputfilter: ^2.30
- laminas/laminas-session: ^2.16
- laminas/laminas-validator: ^2.50 || ^3.0
- peptolab/phpdb-migration: ^0.2
- php-db/phpdb: ^0.6.0
- psr/clock: ^1.0
- psr/log: ^1.0 || ^2.0 || ^3.0
Requires (Dev)
- contenir/contenir-qa-tools: 0.1.x-dev
- contenir/contenir-storage: ^2.2
- infection/infection: ^0.34.1
- php-db/phpdb-mysql: 0.5.x-dev
- php-db/phpdb-sqlite: 0.3.x-dev
- phpunit/phpunit: ^11.5.42
Suggests
- contenir/contenir-storage: Required for the file field type. Pass a Contenir\Storage\StorageManager into FormSubmissionService when uploads need to land on a configured storage profile.
- php-db/phpdb-mysql: The MySQL driver for php-db, for the form_* tables on a site's MySQL database.
- php-db/phpdb-sqlite: The SQLite driver for php-db, for the form_* tables on SQLite.
Provides
None
Conflicts
- laminas/laminas-stdlib: <3.21
Replaces
- contenir/formbuilder: v2.0.1
- v2.2.0
- v2.1.1
- v2.1.0
- dev-main / 2.0.x-dev
- v2.0.1
- v2.0.0
- v2.0.0-RC3
- v2.0.0-RC1
- 0.x-dev
- v0.1.5
- v0.1.4
- v0.1.3
- v0.1.2
- v0.1.1
- v0.1.0
- dev-release/2.0.1
- dev-release/2.0.0
- dev-feat/log-sends
- dev-chore/mago-1.57.0
- dev-feat/persistence-schema
- dev-chore/contenir-qa-tools
- dev-chore/tidy
- dev-archive/v2.2.0-original
- dev-chore/rename-package
- dev-ci/infection
- dev-archive/v2.1.1-original
- dev-security/backport-0.x
- dev-fix/integer-field-names
- dev-archive/v2.1.0-original
- dev-security/html-safe-tokens
- dev-ci/contenir-qa-tools
- dev-archive/v2.0.0-original
- dev-fix/upload-name-storage-2
- dev-v2/qa-tools
This package is auto-updated.
Last update: 2026-10-21 20:12:15 UTC
README
Formerly contenir/formbuilder; the old package is abandoned in favour of this one.
Framework-agnostic form-builder engine for Contenir CMS.
It turns a runtime-editable form definition (sections, groups, rows and
fields, usually stored in a database) into a working Laminas\Form\Form,
validates submissions against it, and renders the markup:
- Definitions. Immutable value objects describing a form, its layout, notifications and webhooks.
- Field types. 17 built-in types behind a registry you can extend.
- Validators. A small curated vocabulary mapped onto Laminas validators.
- Conditional logic. Show/hide rules evaluated on the server (and mirrored by the client).
- Submission. CSRF, a honeypot spam trap, conditional gating, optional
file uploads through
contenir/contenir-storage, and observers (registrars) that persist or forward the result. - Rendering. Framework-free HTML for single-page or stepped forms.
- Merge tags.
{field:name},{form:title},{entry:fields}and custom namespaces for notification templates and redirect URLs. - Storage. The
form_*tables as a php-db migration, a form loader, form and entry repositories, a streamed CSV export and retention-based redaction, on MySQL or SQLite. - Delivery. Registrars that store entries and send notification emails and signed webhooks, through a delivery seam a host can put a queue behind.
This is the framework-free core, shared by the CMS and the site adapters.
contenir/contenir-formbuilder-laminas-mvc
wires it into a laminas-mvc application.
Requirements
- PHP 8.3, 8.4 or 8.5
- laminas-form 3.20+, laminas-inputfilter, laminas-validator, laminas-filter
- laminas-session (backs the CSRF token)
- php-db 0.6 and peptolab/phpdb-migration 0.2, with php-db's MySQL or SQLite driver, for storage
ext-curlfor webhooks- Optional:
contenir/contenir-storagefor thefilefield type
Installation
composer require contenir/contenir-formbuilder
The 0.x releases, which support PHP 8.1, remain available from the 0.x
branch and v0.* tags; see UPGRADE-2.0.md.
Usage
use Contenir\FormBuilder\Definition\FieldDefinition; use Contenir\FormBuilder\Definition\FormDefinition; use Contenir\FormBuilder\Definition\GroupDefinition; use Contenir\FormBuilder\Definition\RowDefinition; use Contenir\FormBuilder\Definition\SectionDefinition; use Contenir\FormBuilder\FieldType\FieldTypeRegistry; use Contenir\FormBuilder\Registrar\WebhookRegistrar; use Contenir\FormBuilder\Render\FormMarkup; use Contenir\FormBuilder\Service\FormBuilderService; use Contenir\FormBuilder\Service\FormSubmissionService; use Contenir\FormBuilder\Validator\ValidatorFactory; $definition = new FormDefinition( id: 1, slug: 'contact', title: 'Contact us', submitLabel: 'Send', sections: [new SectionDefinition(id: 1, key: 'main', groups: [ new GroupDefinition(id: 1, rows: [new RowDefinition(id: 1, fields: [ new FieldDefinition(id: 1, type: 'text', name: 'name', label: 'Name', required: true), new FieldDefinition(id: 2, type: 'email', name: 'email', label: 'Email', required: true), ])]), ])], ); $builder = new FormBuilderService(new FieldTypeRegistry(), new ValidatorFactory()); // GET: render the form $form = $builder->build($definition); echo (new FormMarkup())->render($definition, $form); // POST: validate and dispatch $service = new FormSubmissionService($builder); $service->attach(new WebhookRegistrar($logger)); $result = $service->submit($definition, $_POST, $_FILES, ['ip' => $_SERVER['REMOTE_ADDR'] ?? null]); if ($result->valid) { // $result->values, $result->entryId } elseif ($result->isSpam) { // answer as if it succeeded } else { echo (new FormMarkup())->render($definition, $result->form); // with errors }
The full public API is described in docs/:
| Page | Covers |
|---|---|
| Definitions | FormDefinition, sections, groups, rows, fields, validators, notifications, webhooks |
| Field types | The built-in types, FieldTypeRegistry, writing your own type |
| Validators | ValidatorFactory vocabulary and options |
| Conditional logic | RuleEvaluator, ConditionalRulesParser |
| Building and submitting | FormBuilderService, FormSubmissionService, SubmissionResult, BuilderForm, observers, uploads |
| Rendering | FormMarkup, FormContentSanitizer |
| Merge tags | TokenReplacer |
| Storage | The form_* tables and migration, PhpDbFormLoader, the form and entry repositories, CSV export, redaction, status changes |
| Notifications and delivery | StoreSubmissionRegistrar, EmailNotificationRegistrar, DeliveryInterface, sending through a queue |
| Webhooks | WebhookRegistrar |
Development
The QA toolchain is contenir/contenir-qa-tools.
Mago is a standalone binary, installed
separately (brew install mago).
composer check # everything below composer cs-check # mago format --check && mago lint composer static-analysis # mago analyze composer test # unit suite: no I/O, no session, no network composer test-integration # integration suite on SQLite, then MySQL: in-memory session, temp files, a local HTTP server composer test-coverage # both suites, clover.xml for Codecov composer mutation-test # Infection over both suites (needs Xdebug or PCOV)
The webhook integration tests start PHP's built-in web server on a free
loopback port. The database tests run on in-memory SQLite, then on MySQL when
CONTENIR_TEST_DB_PLATFORM=mysql (connection settings in
CONTENIR_TEST_DB_HOST, _PORT, _NAME, _USER and _PASSWORD; the
database defaults to contenir_formbuilder_test). A MySQL that cannot be
reached skips those tests.
License
MIT. See LICENSE.