contenir / contenir-formbuilder-mezzio
Mezzio adapter for contenir/contenir-formbuilder: php-db form loader and entry store, PSR-15 submit handler, session CSRF and error stash, form renderer, and email, webhook and storage registrars.
Package info
github.com/contenir/contenir-formbuilder-mezzio
pkg:composer/contenir/contenir-formbuilder-mezzio
Requires
- php: ~8.3.0 || ~8.4.0 || ~8.5.0
- contenir/contenir-formbuilder: ^2.2
- laminas/laminas-form: ^3.20
- laminas/laminas-inputfilter: ^2.30
- laminas/laminas-validator: ^2.50 || ^3.0
- mezzio/mezzio: ^3.18
- mezzio/mezzio-router: ^3.17
- mezzio/mezzio-session: ^1.15
- mezzio/mezzio-template: ^2.10
- php-db/phpdb: ^0.6.0
- psr/clock: ^1.0
- psr/container: ^1.1 || ^2.0
- psr/http-factory: ^1.0
- psr/http-message: ^1.1 || ^2.0
- psr/http-server-handler: ^1.0.2
- psr/http-server-middleware: ^1.0.2
- psr/log: ^1.0 || ^2.0 || ^3.0
Requires (Dev)
- contenir/contenir-storage: ^2.2
- infection/infection: ^0.34.1
- laminas/laminas-diactoros: ^3.3
- laminas/laminas-servicemanager: ^3.22
- mezzio/mezzio-fastroute: ^3.12
- php-db/phpdb-qa-tools: 0.1.x-dev
- php-db/phpdb-sqlite: ^0.2.0
- phpunit/phpunit: ^11.5.42
- symfony/mailer: ^7.4.12 || ^8.0.12
Suggests
- contenir/contenir-storage: Enables the file field type: register a Contenir\Storage\StorageManager service and uploads are stored on its default profile.
- mezzio/mezzio-session-ext: Session persistence for the CSRF tokens and the validation-error stash (or any other mezzio-session persistence).
- symfony/mailer: Enables email notifications: register a Symfony\Component\Mailer\MailerInterface service (7.4.12 or later).
Provides
None
Conflicts
- symfony/mailer: <6.4.40 || >=7.0,<7.4.12 || >=8.0,<8.0.12
- symfony/mime: <6.4.40 || >=7.0,<7.4.12 || >=8.0,<8.0.12
Replaces
None
This package is auto-updated.
Last update: 2026-10-06 00:51:40 UTC
README
Mezzio adapter for contenir/contenir-formbuilder,
for Mezzio sites that use Contenir forms without running the full CMS. It is
the Mezzio counterpart of
contenir/contenir-formbuilder-laminas-mvc,
with the same features expressed with Mezzio plumbing:
Loader\PhpDbFormLoaderreads form definitions from the forms schema through a php-db adapter, one query per level.Handler\SubmitHandler, a PSR-15 handler onPOST /forms/submit/{slug}: CSRF check, validation, spam trap, file uploads, then a redirect or a JSON answer, with the visitor's values and errors stashed in the session for the next render when the submission is invalid.- Registrars store the entry (
StoreSubmissionRegistraroverPhpDbEntryRepository), send email notifications through Symfony Mailer (EmailNotificationRegistrar) and fire webhooks (the coreWebhookRegistrar). Render\FormPresenterbuilds, hydrates and renders a form for a page;Render\FormBlockRendererrenders it, or its success state, through theformbuilder::formtemplate and Mezzio'sTemplateRendererInterface.ConfigProviderwith factories for all of it, and a delegator that registers the submit route.
Requirements
- PHP 8.3, 8.4 or 8.5
contenir/contenir-formbuilder2.2+- mezzio/mezzio 3.18+, a Mezzio router (the default route path uses the
FastRoute syntax, see Configuration)
and PSR-17 factories registered as
ResponseFactoryInterfaceandStreamFactoryInterface(laminas-diactoros' ConfigProvider does this) - mezzio/mezzio-session with a persistence (for example mezzio/mezzio-session-ext): the CSRF tokens and the error stash live in the session
- php-db/phpdb 0.6 and a database with the forms schema
(
tests/install-forms.sqlite.sqlis the SQLite version) - A template renderer when you use
FormBlockRenderer(the bundled template is plain PHP: laminas-view and Plates render it) - Optional: symfony/mailer 7.4.12+ for email notifications, and
contenir/contenir-storage2.2+ for file uploads
Installation
The current release, 2.0.0-RC1, is a release candidate. The database layer
uses php-db/phpdb 0.6, which is still a dev branch (contenir-db-model
2.0.0-rc2 depends on it too). Composer only honours stability flags in the
root composer.json, so your site has to allow both itself:
"require": { "contenir/contenir-formbuilder-mezzio": "^2.0@RC", "php-db/phpdb": "0.6.x-dev@dev" }
Alternatively, set "minimum-stability": "dev", "prefer-stable": true in
your root composer.json and require contenir/contenir-formbuilder-mezzio
as usual. 2.0.0 final follows once php-db 0.6.0 is tagged.
With laminas-component-installer
the Contenir\FormBuilder\Mezzio\ConfigProvider is added to
config/config.php; without it, add the provider yourself.
Then make sure the session middleware runs before routing, as Mezzio sites that use sessions do:
// config/pipeline.php $app->pipe(Mezzio\Session\SessionMiddleware::class); $app->pipe(Mezzio\Router\Middleware\RouteMiddleware::class);
and register the services the package needs:
| Service | Purpose |
|---|---|
PhpDb\Adapter\AdapterInterface (or the id in formbuilder.db_adapter) |
Forms schema and entries |
Mezzio\Session\SessionPersistenceInterface |
Sessions, for CSRF and the error stash |
Symfony\Component\Mailer\MailerInterface |
Optional: enables email notifications |
Contenir\Storage\StorageManager |
Optional: enables file fields |
Psr\Log\LoggerInterface |
Optional: email and webhook failures are logged |
Psr\Clock\ClockInterface |
Optional: entry timestamps (the system clock otherwise) |
The submit route is registered for you at POST /forms/submit/{slug}, named
formbuilder.submit.
Usage
Render a form on a page from your own handler:
use Contenir\FormBuilder\Mezzio\Render\FormBlockRenderer; use Laminas\Diactoros\Response\HtmlResponse; use Mezzio\Template\TemplateRendererInterface; final readonly class ContactPageHandler implements RequestHandlerInterface { public function __construct( private FormBlockRenderer $forms, private TemplateRendererInterface $templates, ) {} public function handle(ServerRequestInterface $request): ResponseInterface { return new HtmlResponse($this->templates->render('app::contact', [ 'form' => $this->forms->render($request, 'contact'), ])); } }
$form is the form, wrapped in <div class="formbuilder" id="formbuilder-contact">,
or its success message after a successful submission. For your own markup
use FormPresenter::present(), which returns the definition, the built and
hydrated Laminas form and its rendered HTML. See Rendering.
The form posts to the submit route, which answers with a redirect back to the
page (or JSON for Accept: application/json). See Submitting.
| Class | Purpose |
|---|---|
Handler\SubmitHandler |
The PSR-15 submit endpoint |
Handler\SubmissionPipeline |
One submission through the core FormSubmissionService: CSRF, uploads, observers |
Render\FormPresenter |
Load, build (with the session's CSRF token), hydrate from the stash, render |
Render\FormBlockRenderer |
FormPresenter plus the formbuilder::form template |
Render\PresentedForm |
What the presenter returns |
Loader\FormLoaderInterface, Loader\PhpDbFormLoader |
Form definitions |
Repository\EntryRepositoryInterface, Repository\PhpDbEntryRepository |
Entry storage |
Registrar\StoreSubmissionRegistrar, Registrar\EmailNotificationRegistrar |
Submission observers |
Csrf\CsrfTokenManager, Csrf\CsrfFormFactory, Csrf\SessionCsrfFormBuilder, Csrf\CsrfElement, Csrf\CsrfTokenValidator |
Session CSRF tokens |
State\FormStateStash |
One-shot stash of an invalid submission's values and errors |
Http\UploadedFileStager, Http\StagedUploads |
PSR-7 uploads for the core service |
Http\Responder |
PSR-17 JSON and redirect responses |
Token\TokenReplacerBuilder |
Merge-tag replacers with the configured {site:*} values and namespaces |
Route\SubmitRouteDelegator |
Registers the submit route on Mezzio\Application |
ConfigProvider and the Factory\* classes |
Container wiring |
| Page | Covers |
|---|---|
| Configuration | The formbuilder keys, services and the submit route |
| Loading forms | PhpDbFormLoader and the schema |
| Submitting | SubmitHandler, responses, success modes, CSRF, the stash, uploads |
| Rendering | FormPresenter, FormBlockRenderer and the formbuilder::form template |
| Registrars | Entry storage, email notifications, webhooks, your own observers |
| Coming from laminas-mvc | Each laminas-mvc adapter feature and config key, and its Mezzio equivalent |
Security
- CSRF. Every submission must carry the token issued to the visitor's session for that form; a missing, wrong or foreign-session token is rejected before anything is validated, stored, mailed or uploaded, even when the honeypot marks the request as spam.
- Redirects stay on the site. The Referer is followed back only when it
is a local path (not
//hostor/\host) or anhttp(s)URL on the request's host; anything else redirects to/. A posted_anchormust look like an HTML id. - Notification emails. Whether a body is HTML is decided by the template
(markup or
{entry:fields}), never by submitted values; HTML bodies escape every merge-tag value withTokenReplacer::replaceForHtml(). Line breaks in an expanded subject become spaces. - Uploads. Only the request's PSR-7 uploads for the form's
filefields reach the storage layer, moved first to randomly named staging files that are removed after the submission. - JSON answers escape
<,>,&,'and".
See Submitting for the details.
Development
The QA toolchain is php-db/phpdb-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: in-memory sessions, containers and mailers, no I/O composer test-integration # integration suite: SQLite forms schema, a real Mezzio application, staged uploads composer test-coverage # both suites, clover.xml for Codecov composer mutation-test # Infection over both suites (needs Xdebug or PCOV)
License
MIT. See LICENSE.