This file is the entry point when working on the YForm addon itself (not when consuming it from a project). It collects what's not obvious from grepping: the architectural model, the lifecycle, the cache contracts, the do-not-touch zones.
YForm ist ein REDAXO-AddOn mit zwei Aufgaben:
- Frontend-Formularbau — Pipe-Syntax oder PHP-API, validierung, Aktionen (DB-Insert, Mailversand, Redirect).
- Tabellenverwaltung im Backend — visuell zusammengeklickte Tabellen + Backend-UI + ORM (
rex_yform_manager_dataset/ YOrm).
Beides nutzt denselben Form-Pipeline-Runtime (rex_yform). Der Backend-Datasatz-Save geht durch den gleichen Field/Validate/Action-Loop wie ein Frontend-Submit, nur „headless".
Es gibt bereits gut gepflegte Skills für die Anwender-Sicht. Bei Aufgaben, die nur das „Konsumieren" von YForm betreffen (Form bauen, Dataset abfragen, REST-Endpoint registrieren), erst dort nachschauen:
Skill (im redaxo-yform-Plugin) |
Wofür |
|---|---|
redaxo-yform (Top-level Skill) |
Schnellreferenz quer durch alle Themen |
yform-tables |
Tabellen in install.php, Schema-Migrationen, Tablesets |
yform-fields |
Alle Value/Validate/Action-Typen + Pipe-Syntax |
yform-frontend |
Public Forms, CSRF, Uploads, Spam-Schutz, objparams |
yform-datasets |
YOrm-Queries, Joins, Pagination, Relations |
yform-email-templates |
Email-Templates, Platzhalter, programmatischer Versand |
yform-rest-api |
REST-Routen, Token-Auth, CORS, Field-Whitelists |
Bei Arbeit am AddOn selbst (interne Klassen ändern, Field-Typen hinzufügen, EPs erweitern, Schema migrieren) reichen die Skills nicht. Dafür gibt es die references/ unten.
HTTP-Request (Frontend/Backend/REST)
│
▼
boot.php
├── lädt Template-Pfade
├── injiziert Backend-Assets (CSS, JS, Inputmask, Daterangepicker)
├── kompiliert SCSS bei `compile: 1`
├── PACKAGES_INCLUDED → rex_yform_rest::handleRoutes() (nur Frontend)
├── registriert complex_perms `yform_manager_table_{view,edit}`
├── baut Backend-Pages für jede aktive Tabelle
├── verdrahtet MEDIA_IS_IN_USE, YFORM_SAVED (History), Cron
│
▼
Pages (pages/manager.*.php) ──► rex_yform_manager ──► rex_yform_manager_dataset
│
▼
interner rex_yform (headless)
│
▼
executeFields → executeActions
│
▼
action|db (INSERT/UPDATE)
│
▼
YFORM_SAVED EP
│
▼
History-Snapshot (wenn aktiv)
System-Tabellen (alle in install.php per rex_sql_table::ensure()):
rex_yform_table— Tabellen-Metadatenrex_yform_field— Field/Validate/Action-Zeilenrex_yform_history+rex_yform_history_field— Snapshotsrex_yform_email_template— Email-Templatesrex_yform_rest_token+rex_yform_rest_token_access— REST-Auth
Alle gemanagten User-Tabellen werden separat (per setTable() + manuelles rex_sql_table::ensure()) angelegt. YForm besitzt sie nicht — es kennt nur ihre Konfiguration.
rex_yform::getForm() ruft executeFields() → executeActions():
initializeFields()— CSRF-Feld prepend, dann jedeform_elements-Zeile alsrex_yform_value_*/rex_yform_validate_*/rex_yform_action_*instanziieren.loadParams()bindet dieobjparamsper Referenz, d.h. jedes Feld kann den shared State mutieren.- Value-Population: REQUEST → sql_object (bei
getdata=true) → objparams.data → fixdata. Spätere Quellen überschreiben frühere. preValidateAction→ Validate-Phase (nur beisend=1) →postValidateAction.- Value-
enterObject— rendert HTML inform_output[$id], schreibt invalue_pool.emailundvalue_pool.sql(wennsaveInDb()). postValueAction(nur send=1) →postFormAction.- Action-Phase (nur send=1 und keine warnings):
preAction→executeAction→postAction.action|dbzieht ausvalue_pool.sql,action|tpl2emailausvalue_pool.email.
Detaillierter Ablauf siehe .claude/references/02-form-pipeline.md.
ytemplates/bootstrap/*.tpl.php— wird bei jedem Update überschrieben. Override stattdessen in Projekt-AddOns viarex_yform::addTemplatePath().vendor/— vom Top-Level redaxo/redaxo Composer-Setup gemanagt.assets/inputmask/,assets/daterangepicker/— Third-Party-JS; nur durch saubere Upstream-Updates ersetzen.composer.lock— explizit per.gitignoreausgeschlossen.- Direktes SQL auf
rex_yform_table/rex_yform_fieldohne anschließendesrex_yform_manager_table::deleteCache()— siehe Cache-Disziplin unten. - Reordering von
getDefinitions()['values']-Slots in bestehenden Field-Typen — dief1-f9positional columns ausrex_yform_fieldmappen positional auf diese Reihenfolge. Insert in der Mitte = stille Daten-Korruption.
Fünf Cache-Layer (siehe 13-pitfalls-and-conventions.md):
rex_yform_manager_table::$cache(Filecachecache/addons/yform/manager/tables.cache) — komplette Tabellen-Metadaten. Bust:rex_yform_manager_table::deleteCache(). MUSS nach jedem Write aufrex_yform_table/rex_yform_field. Die*_api-Methoden machen es automatisch — bei rohen SQL-Writes selbst.rex_yform_manager_datasetInstance-Pool (rex_instance_pool_trait) — keyed[table, id]. Bust:self::clearInstance([$t, $id])(machtdelete()automatisch).rex_yform_value_be_manager_relation::$yform_list_values— Relations-Lookups. Bust:clearCache($table).rex_yform_manager_table_api::$cacheColumnsByTable—SHOW COLUMNS-Cache für eine install.php-Runtime.- REDAXO-Autoload-Cache — bei neuen Field-Klassen
rex_autoload::removeCache()(oder Reinstall).
Das eigene update.php ist:
$this->includeFile(__DIR__ . '/install.php');Jeder Reinstall durchläuft die komplette install.php. Die 4.x→5.x Field-Migration (Zeilen ~174–344) ist deshalb idempotent geschrieben — jeder Switch-Case prüft den aktuellen Zustand vor dem Mutate. Wenn du etwas in install.php ergänzt, gilt das gleiche: idempotent oder per rex_version::compare() gaten.
Für nachgelagerte AddOn-Versionen (1.0 → 1.1) wird typischerweise ein update.php mit rex_version::compare($installed, 'X.Y.Z', '<') verwendet — siehe 04-table-manager.md.
Größere Umbauten werden vor der Umsetzung als ausgearbeitete Pläne unter .claude/plans/ abgelegt. Diese Pläne lesen, bevor du am betreffenden Code arbeitest — sie definieren Phasen, Test-Voraussetzungen und Rollback-Punkte.
| Plan | Status | Worum geht's |
|---|---|---|
plans/01-json-schema-migration.md |
Draft | rex_yform_table + rex_yform_field durch JSON-Definitionen unter data/addons/yform/tables/*.json ablösen. Fünf Phasen, jede einzeln releasebar. |
plans/02-test-strategy.md |
Draft | Was getestet wird: Test-Pyramide (Unit/Integration/E2E), ~100 konkrete Test-Cases, Coverage-Ziel ≥ 80 % auf Storage-Klassen. |
plans/03-upgrade-process.md |
Draft | Migrations-Routine SQL → JSON für bestehende Installationen. Detection, Backup, ParityChecker, 10 Edge-Cases mit Tests, Rollback-Optionen. |
plans/04-test-commands.md |
Draft | Wie getestet wird: Tests als REDAXO Console Commands (yform:test:*). Runner-Architektur, Assertion-Library, Fixture-Management, CI-Workflow. |
Übersicht: plans/README.md |
Status aller Pläne, Reihenfolge der Umsetzung |
Wichtig: Pläne sind lebende Dokumente. Wenn du den Code änderst, halte den entsprechenden Plan synchron (Status, offene Fragen, neu entdeckte Risiken). Wenn du am Code-Stand etwas siehst, das einem Plan widerspricht, ist der Plan zu aktualisieren — nicht zu ignorieren.
Beim Schreiben der Test-Suite (siehe tests/README.md → Findings) wurden mehrere Bugs in der yform-5.0.1-Implementation und Abweichungen zwischen dem redaxo-yform-Skill und der echten Implementation gefunden. Kurzfassung:
setTableField()überschreibttable_nameaus dem Field-Row → renamed-Import verliert FeldersetTable()-Whitelist unvollständig:history/mass_deletion/mass_editwerden silent verworfenimportTablesets()wirft keine Exception bei kaputtem JSONrex_yform_validate_in_tablehat keinegetDefinitions()→ nur Pipe-Syntax-fähigcompare/compare_valueOperator-Semantik ist INVERS:==heißt "Fehler wenn gleich"manage_dbmacht keine Existenz-Erkennung — entscheidet rein per where-ParamYFORM_DATA_UPDATED.params['old_data']ist NICHT der Pre-Update-DB-Stand, sondern der In-Memory-State nachsetValue()rex_yform_manager_table_authorization-Cache ist cross-user sticky (statisch pro Prozess); Footgun in long-running PHP-Workern- Diverse Param-Namen falsch in der Skill-Doku — siehe Tabellen in
tests/README.mdundreferences/03-field-types.md
Die Bugs sind als markSkipped() Tests dokumentiert. Fixes nicht jetzt — erst wenn die volle Test-Suite gegen unverändertes Verhalten grün ist (Plan 02 §Rollout).
Wenn du tiefer ins AddOn-Interna gehst, sind die folgenden Dateien unter .claude/references/ die ehrliche Quelle:
| Datei | Wenn du… |
|---|---|
01-architecture.md |
Boot/Install-Lifecycle, Verzeichnis-Layout, Request-Flow Frontend/Backend/REST verstehen willst |
02-form-pipeline.md |
Wissen willst, was executeFields()/executeActions() Schritt für Schritt machen, und wie value_pool aufgebaut ist |
03-field-types.md |
Einen neuen Value/Validate/Action-Typ schreiben willst, getDefinitions()/enterObject() ausbalancieren musst, oder verstehst wie getElement() positional vs. keyed funktioniert |
04-table-manager.md |
Tabellen programmatisch in install.php anlegst, schema_overwrite einstellst, setTable()/setTableField() API benutzt, oder Tablesets ex-/importierst |
05-dataset-orm.md |
YOrm-Modellklassen baust (setModelClass), CRUD-Lifecycle inkl. EP-Reihenfolge verstehen willst, History/Snapshots managst |
06-query-builder.md |
Komplexe WHERE/JOIN/HAVING/OR-Nested-Queries baust, joinRelation über alle 6 Relation-Typen (0–5) hin sauber haben willst |
07-email-templates.md |
tpl2email debuggst, REX_YFORM_DATA-Platzhalter inkl. _LABELS/_LIST benutzt, programmatisch Mails versendest, Anhänge aus Uploads route st |
08-rest-api.md |
REST-Routen registrierst, preFunc/postFunc/getItemFunc/getAttributeFunc Hooks brauchst, Token-Auth und CORS richtig setzt |
09-extension-points.md |
Einen EP wie YFORM_DATA_UPDATED/YFORM_DATA_LIST_QUERY/YFORM_EMAIL_SEND registrierst — vollständige EP-Liste mit Subject/Params/Timing |
10-frontend-templates.md |
Eigene ytemplates ausspielst, cascading-Fallback verstehst, form.tpl.php overridest |
11-rex-var-widgets.md |
REX_YFORM_DATA[field=...] und REX_YFORM_TABLE_DATA[id=N table=...] einsetzt — Kontextregeln, Widgets, CSRF |
12-testing-and-tooling.md |
PHPUnit-Suite yorm laufen lässt, composer cs-fix/composer cs-dry machst, Debug-Knöpfe (debug objparam, setDebug() auf SQL) drehst |
13-pitfalls-and-conventions.md |
Die wirklich gemeinen Fallen — Element-Offset-Inkonsistenzen, Cache-Vergessen, REST-Kontext, deprecated EPs |
14-class-map.md |
Schnell nachschlagen willst, in welcher Datei welche Klasse lebt |
- EPs mit Subject/Param-Tabelle: 09-extension-points.md hat sie alle inkl. Trigger-Position und Cancel-Semantik.
getElement()-Mapping-Offset (1 für Value, 2 für Validate, 1 für Action): 03-field-types.md.- Die fünf Cache-Layer und wann sie invalidiert werden müssen: 13-pitfalls-and-conventions.md.
- Wie
setModelClassmittableToModel/modelToTablezusammenspielt: 05-dataset-orm.md. joinRelationüber alle Relations-Typen 0-5 inkl. junction-table-Pfad: 06-query-builder.md.replaceVars()Stream-Hack für PHP in Email-Templates: 07-email-templates.md.- REST
getItemFunc/getAttributeFuncHooks (neu in 5.0): 08-rest-api.md.
- PHP 8.1+, PSR-12 via redaxo/php-cs-fixer-config.
composer cs-fixvor jedem Commit. declare(strict_types=1);in neuen Dateien — Altbestand teils ohne.- Klassennamen, Methodennamen, Code-Kommentare auf Englisch. Backend-Strings in
lang/-Files (deutsch default). Dokumentation indocs/undCHANGELOG.mdauf Deutsch. - Neue Field-Typen: Slot-Reihenfolge in
getDefinitions()['values']ist API-Vertrag. Slots nur anhängen, nie einfügen oder umordnen, ohne Migration fürf1-f9-Positionsdaten. - EP-Namen:
YFORM_*für formbuilder-allgemein,YFORM_DATA_*für Dataset-Lifecycle,YFORM_MANAGER_*für Backend-UI,YFORM_EMAIL_*für Mail-Pipeline. - Cache nach Writes flushen. Wenn unsicher:
rex_yform_manager_table::deleteCache()aufrufen ist nie falsch (nur teuer). - Keine
composer.lockcommitten (gitignored).
# Test-Suite laufen lassen (aus diesem Verzeichnis)
composer install
composer test
# Code-Style fixen
composer cs-fix
# Vom REDAXO-Core-Verzeichnis aus volle Checks
cd ../../../..
composer check # cs + phpstan + psalm + phpunit
composer phpstan
composer psalm
composer taint # Psalm-Taint-Analyse
# Lokal via Docker testen
docker-compose up -d # localhost:80
REDAXO_PORT=8080 docker-compose up -d # alternativer PortNach Schema-Änderungen an install.php: AddOn im Backend (System → AddOns → YForm) deinstallieren + reinstallieren, oder den Cache leeren (System → Cache → Generieren). Für reine lib/-Änderungen reicht ein Cache-Clear.
- Neuer Value-Field-Typ —
lib/Field/value/<name>.phpschreiben, dabeienterObject()mit Value-Pool-Disziplin (saveInDbCheck,needsOutput/isViewablefür Render-Skip),getDefinitions()slot-stabil,getDescription()pipe-syntax-doc. Optional eigenevalue.<name>.tpl.phpunterytemplates/bootstrap/. Tests intests/. CHANGELOG-Eintrag. - Neuer Validator —
lib/Field/validate/<name>.php. InenterObject()pergetValueObject()Zielfeld auflösen,params['warning'][$id]+params['warning_messages'][$id]setzen bei Fehler. - Neue Action —
lib/Field/action/<name>.php.executeAction()macht den Side Effect;preAction/postActionfür Vor-/Nacharbeit.getDescription()für Pipe-Syntax-Hilfe. - REST-Hook erweitern — Anpassungen meist in
lib/Rest/route.php; bei Hook-API-Erweiterung auch08-rest-api.mdaktualisieren. - Backend-UI-Änderung —
pages/,fragments/yform/,assets/manager.js/assets/widget.js. SCSS inscss/, Build durchcompile: 1inpackage.ymloder manuell viarex_scss_compiler.
Die globalen Repo-Regeln (siehe ~/.claude/CLAUDE.md und redaxo/CLAUDE.md):
- Keine persönlichen Daten in tracked Files. Pfade projekt-relativ, keine Heim-Verzeichnis-Strings.
- Keine
.claude/settings.local.jsonins Repo (das hier ist Dokumentation, nicht Tool-Config — die ist okay). composer.locknicht committen (gitignored auf AddOn-Ebene).- Code-Stil via
composer cs-fix, Tests viacomposer test. - CHANGELOG-Eintrag auf Deutsch in neuestem Release-Block (oben), mit Kontributor-Erwähnung wenn extern.