dbxapp Knowledge Module manual

Module manual

On this page
  1. Module development: the complete working contract
  2. Semantic contract coverage
  3. 1. Start with the system boundaries
  4. 2. Reference module layout
  5. 3. Route and security contract
  6. 4. DD and FD are executable contracts
  7. 5. Keep the router small
  8. 6. Build forms through dbxForm
  9. 7. Build lists and nested lists through dbxReport
  10. 8. Keep templates structural
  11. 9. Ajax and confirmation reuse the system libraries
  12. 10. Make multi-table mutations atomic
  13. 11. Installation and fixtures
  14. 12. Required acceptance matrix
  15. 13. Common wrong turns
Golden Path · dbxapp 4.5.3

Module development: the complete working contract

Reference code last changed: August 18, 2026
Revalidated against dbxapp 4.5.3: August 22, 2026

The first date identifies the last domain-level change to the reference module. The second confirms that the unchanged code passed the complete acceptance suite with the current product release.

This manual describes the supported path for a data-backed dbxapp module. The reference module myInvoices combines routing, DD and FD definitions, dbxDB, dbxForm, dbxReport, dbxTPL, Ajax, permissions, transactions, and acceptance tests without creating a parallel framework.

Normative language: must and must not define product requirements. Should identifies the default design choice; deviations need a documented domain reason.

Semantic contract coverage

These stable contract IDs bind the German and English editions together. Publication fails if either language omits a contract.

ContractEvidence in this manual
routing / securityRoute matrix, permissions, and action tokens
dd-fd / form / reportExecutable data, form, and list contracts
ajax / transactionsOne PHP path and an atomic multi-table mutation
installation / acceptanceProtected synchronization and reproducible acceptance

1. Start with the system boundaries

A module owns a domain task. It does not own database drivers, global routing, template parsing, authentication, or shared browser infrastructure. Use the established system facades and keep domain decisions in a small router plus a service.

QuestionResponsible layerDo not place it in
Which route is requested?Module routerTemplates or SQL
Which domain operation runs?ServiceGlobal helpers
Where does data come from?dbxDB through a complete DDPDO, mysqli, SQLite3, or inline SQL
Which fields are displayed?FDA second field definition in PHP
How are forms and lists processed?dbxForm and dbxReportCustom request loops
How is output rendered?dbxTPLLarge PHP strings

2. Reference module layout

dbx/modules/myInvoices/
├── cfg/config.php
├── dd/invoice.dd.php
├── dd/invoiceItem.dd.php
├── fd/invoice-form.fd.php
├── fd/rpt-invoice-selection.fd.php
├── include/myInvoicesService.class.php
├── tpl/htm/invoice-form.htm
├── tpl/htm/invoice-report.htm
├── tpl/htm/invoice-items-report.htm
├── myInvoices.class.php
└── tests/

The DD files are the authoritative schema and permission definitions. The FD files define the fields and messages used by dbxForm and dbxReport. The router validates the requested route and delegates to the service. Templates contain structure; the service contains domain decisions.

3. Route and security contract

Classify every route before implementing it. Read-only navigation remains a normal GET. Forms use the submit protection provided by dbxForm. A state-changing GET action must use a route- and record-bound action token in addition to module and DD permissions.

RoutePurposeProtection
dbx_run1=reportList invoicesModule access and DD read permission
dbx_run1=positions&invoice_id=17Render one embedded line-item listModule access plus both DD read permissions
dbx_run1=form&rid=17Edit an invoicedbxForm submit protection and DD write permission
dbx_run1=delete&rid=17Delete an invoice and its itemsPermissions plus a record-bound action token
dbx_run1=installSynchronize DDs and fixturesAdministrator permission and a protected dbxForm POST

A confirmation dialog is a usability feature, not an authorization boundary. The server still checks the route, record binding, token, module access, and DD privileges.

4. DD and FD are executable contracts

Define tables, fields, indexes, relationships, owner behavior, and privileges explicitly in each DD. Use the standard export structure with TABLE, FIELDS, and INDEXES. Do not hide field definitions in local closures, and do not set audit fields such as creation date, creator, owner, or update date in domain code; dbxDB maintains them.

An FD selects the visible fields and provides labels, validation messages, and view-specific options. Keep German, English, and Spanish message keys structurally identical. The service loads messages from the FD instead of maintaining a second set of visible strings.

5. Keep the router small

public function run(): string
{
    $route = (string) dbx()->get_modul_var('dbx_run1', 'report');
    return match ($route) {
        'report'    => $this->service->report(),
        'positions' => $this->service->positions(),
        'form'      => $this->service->form(),
        'delete'    => $this->service->delete(),
        'install'   => $this->service->install(),
        default     => $this->service->notFound(),
    };
}

The same router handles a direct request and a server-side module inclusion. There is no separate internal HTTP API for the embedded position list.

6. Build forms through dbxForm

Initialize dbxForm with the DD, FD, record identifier, and template. Let it resolve submitted values, validate them, retain errors and messages, and persist through save_post(). After an insert, keep the returned RID in the form action so that a second submission updates the same record instead of inserting a duplicate.

Use callbacks only for genuine domain work. The normal callback owner and the {fid}_{event} naming convention already connect the service to the form. An explicit callback registration is needed only when the module intentionally departs from that convention.

7. Build lists and nested lists through dbxReport

The outer report lists invoices. Its record callback formats monetary snapshots, creates the signed row action, accumulates the visible-page total, and sets the marker for the matching line-item report. The inner report calculates quantity × unit price and its complete invoice total.

[modul=myInvoices]dbx_run1=positions&invoice_id=17[/modul]

The interpreter processes this marker on the server. It applies the protected module variables, runs the same module router in the current owner context, checks permissions, and replaces the marker with the resulting HTML. It does not create another browser request.

Use {rpt:col_count} when a row must span every report column and {rpt:colspan} when a footer label spans every column except the final value column. This keeps templates correct when report fields change.

8. Keep templates structural

Templates define the form shell, report rows, empty states, footers, and action controls. They may contain dbxTPL placeholders and inert module markers, but they must not contain domain queries or authorization decisions.

Give each form and report a stable 1 root. Multiple instances on the same page then keep their field state, callbacks, messages, and Ajax replacement targets separate. The root is an instance marker, not a literal value: the runtime replaces 1 with a unique ID for each rendered instance.

9. Ajax and confirmation reuse the system libraries

  1. confirm.js asks whether the user wants to continue.
  2. ajax.js submits the existing link or form and replaces the declared HTML target.
  3. The server repeats every permission, token, validation, and transaction check.
  4. The returned fragment uses the same template as a normal request.
  5. The runtime initializes required features again after replacement.

A form must still work without JavaScript. Ajax improves the interaction; it does not create a second business path.

10. Make multi-table mutations atomic

Invoice headers and items share one DD server, so deletion runs in a single dbxDB transaction. Start the transaction, delete through both DDs, commit only after every step succeeds, and roll back on any exception. The integration test must force a failure after the first mutation and prove that neither table changed.

11. Installation and fixtures

Do not synchronize schemas on ordinary requests. The protected installation route displays a dbxForm first; only a valid administrator POST starts DD synchronization and idempotent fixtures. Fixtures identify their own demo records, skip existing data, and never overwrite user records.

The same installer may be exposed to automation through a CLI adapter, but both entry points must call the same service. Do not create a second persistence path.

12. Required acceptance matrix

  • Direct and embedded reports return the same authorized records.
  • A known marker reveals no data when a required DD read permission is missing.
  • Count and select use the same filters; sorting uses fixed field and direction allowlists.
  • Record and footer calculations are correct, including rounding and dynamic column spans.
  • The form works with and without Ajax; insert switches cleanly to update.
  • Confirmation “No” causes no request; an invalid token causes no mutation.
  • A valid delete removes header and items together; a forced error rolls both back.
  • Two report instances keep independent IDs, state, callbacks, and totals.
  • DB3 and MySQL require no module-code change.
  • Browser console, files/dbxError.log, Missing counters, PHP logs, and system messages remain clean.

13. Common wrong turns

AvoidUse instead
PDO, mysqli, SQLite3, or inline SQLdbxDB with a complete DD
Manual audit fieldsAutomatic dbxDB system fields
HTML assembled in callbacksValues from callbacks, structure in dbxTPL
An internal fetch for a nested list[modul=...]...[/modul]
Custom table and pagination loopsdbxReport
Totals calculated in JavaScriptRecord callback plus add_rep()
Hard-coded column spans{rpt:col_count} or {rpt:colspan}
window.confirm() and custom fetch handlersdbxConfirm, dbxAjax, and the system libraries
Action tokens on every GETTokens only for state-changing GET actions
Continue with the runnable example Inspect the installed myInvoices module and run its versioned architecture and integration tests. Open the Module Quick Start