Skip to content

AD Form Architecture

Identity

KeyValue
NameAD Form
Slugad-form
PHP namespaceDingfelder\AdForm
Text domainad-form
REST namespacead-form/v1
VendorDingfelder Digital

Decisions

Custom tables instead of CPTs

Forms and submissions live in dedicated tables. CPT overhead (revisions, term relationships, wp_posts scans) does not scale to hundreds of thousands of entries. Queryable field values are stored in submission_meta (value_text, value_num) plus a JSON payload snapshot on the submission row for full reconstruction.

Application-level relations, no InnoDB foreign keys

WordPress sites change prefixes, import dumps and run hosts that skip FK checks. Referential integrity is enforced in repositories. Indexes cover the same columns a FK would use.

REST instead of admin-ajax.php

Admin builder and frontend submit share /wp-json/ad-form/v1. Cookie + X-WP-Nonce for admin. Public submit uses a per-form nonce (ad_form_submit_{id}). Logged-in AJAX submits still send X-WP-Nonce (wp_rest) so WordPress cookie auth accepts the request.

Lightweight container, no Symfony

A small singleton/transient container keeps the plugin installable without Composer runtime packages. Add-ons bind via ad_form_service_providers and ad_form_register_services.

Optional modules stay optional

The Gutenberg provider registers ad-form/form only when register_block_type exists and compiled block assets are present. The Elementor provider registers widget ad-form only after elementor/loaded and Widget_Base exist. Frontend CSS/JS enqueue only after a form is marked present.

Boot flow

  1. ad-form.php defines constants, checks PHP, registers the autoloader.
  2. Activation runs Migrator::upgrade() and grants capabilities to administrators.
  3. plugins_loaded boots Plugin.
  4. Providers register() then boot().
  5. ad_form_loaded fires for add-ons.

Services

ServiceResponsibility
ContainerDI bindings
MigratorVersioned schema upgrades
SchemadbDelta-safe CREATE TABLE SQL
WpOptions / SettingsPlugin settings
CapabilitiesCustom caps
LoggerChannel logs with redaction
Menu / View / AssetsAdmin UI
FormService / FormRepositoryForm CRUD, slugs, optimistic locking
FieldRegistryBuilder field types and inspector schema
FormRenderer / ShortcodeFrontend HTML for [ad_form], Gutenberg and Elementor
StepEngineMulti-step grouping (≥ 2 steps)
SubmitServicePublic submit, validation, persistence
ConditionEngineShow/hide and action rules (client + server)
CalculationEngineSafe formula parser for calculation fields (no eval)
ActionRegistry / ActionRunnerPost-submit actions (email + merge tags)
SubmissionServiceEntry list, status, spam/trash, search
Frontend\AssetsConditional frontend enqueue
ElementorIntegrationWidget ad-form
GutenbergIntegrationBlock ad-form/form

Add-on extension

php
add_filter( 'ad_form_service_providers', function( array $providers ) {
    $providers[] = new MyAddon\ServiceProvider();
    return $providers;
} );

add_action( 'ad_form_register_services', function( \Dingfelder\AdForm\Core\Container $container ) {
    $container->singleton( MyService::class, fn() => new MyService() );
} );

Core must not type-hint optional module classes.

Digitale Lösungen. Persönlich. Zukunftssicher.