PHPackages                             ochorocho/frankenphp - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. ochorocho/frankenphp

ActiveTypo3-cms-extension

ochorocho/frankenphp
====================

FrankenPHP worker mode support for TYPO3

0.0.3(2w ago)401[1 PRs](https://github.com/ochorocho/typo3-frankenphp/pulls)GPL-2.0-or-laterPHPCI failing

Since May 21Pushed 1w agoCompare

[ Source](https://github.com/ochorocho/typo3-frankenphp)[ Packagist](https://packagist.org/packages/ochorocho/frankenphp)[ RSS](/packages/ochorocho-frankenphp/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (3)Dependencies (12)Versions (5)Used By (0)

TYPO3 FrankenPHP integration
============================

[](#typo3-frankenphp-integration)

Provides one CLI command that generates everything needed to run TYPO3 under FrankenPHP:

```
vendor/bin/typo3 frankenphp:init
# or without prompts:
vendor/bin/typo3 frankenphp:init --no-interaction
# overwrite existing files:
vendor/bin/typo3 frankenphp:init --no-interaction --force
# production defaults (ports 80/443, TYPO3_CONTEXT=Production, larger worker pool):
vendor/bin/typo3 frankenphp:init --profile prod
# expose Caddy + FrankenPHP Prometheus metrics on localhost:METRICS_PORT (default 2019):
vendor/bin/typo3 frankenphp:init --prometheus

```

`--profile dev|prod` drives the defaults for ports, `TYPO3_CONTEXT`, worker count, and `max_requests`. `--prometheus`adds the `metrics` directive and an `admin localhost:METRICS_PORT` block to the Caddyfile and powers the live dashboard widget described below.

Composer also runs the command automatically on package install / update / `dump-autoload` via the TYPO3 installer-scripts mechanism, so users who don't touch the CLI still get a working setup. Without `--force`, the command preserves files that already exist (warns instead of overwriting), so a `composer update` won't clobber a hand-edited `Caddyfile`, `.env`, `php.ini`, or `public/worker.php`.

**The files:**

- Worker entrypoint: `public/worker.php` — long-running FrankenPHP worker for the full backend + frontend ( `HttpApplication`).
- Webserver config: `Caddyfile` — routes `?__typo3_install` queries to canonical `public/index.php` (which TYPO3 ships and which handles `Bootstrap::init($failsafe=true)` internally), and everything else through the worker.
- Environment config: `.env`
- PHP runtime config: `php.ini` — profile-aware (e.g. `display_errors`, `opcache.validate_timestamps`).

The install-tool recovery URL needs `Bootstrap::init` with `$failsafe=true` so the container exposes `InstallApplication` — mutually exclusive with the worker's always-on `HttpApplication` boot. Rather than shipping a duplicate entry-point, the Caddyfile routes those requests to TYPO3's existing canonical `public/index.php`, which already implements that branch. `index.php` is not registered as a FrankenPHP worker, so requests reach it as standard per-request PHP execution.

For users — install into an existing TYPO3
------------------------------------------

[](#for-users--install-into-an-existing-typo3)

```
composer require ochorocho/frankenphp

```

Then run FrankenPHP from the project root using the created config files (`Caddyfile`, `.env`):

```
frankenphp run -c Caddyfile -e .env

```

### Required: apply the FormProtectionFactory cache-key patch

[](#required-apply-the-formprotectionfactory-cache-key-patch)

Worker mode exposes a latent bug in TYPO3's `FormProtectionFactory`: its `cache.runtime` entry for the `BackendFormProtection` instance is keyed by request *type* only, so under FrankenPHP the cached BFP from one session gets served back to subsequent requests and form-token validation runs against the wrong session secret — "Validating the security token of this form has failed", with the user-visible symptom of a redirect loop between `/typo3/main` and `/typo3/login`.

This extension ships a small patch (`Patches/cms-core-form-protection-factory-session-aware-cache.patch`) that folds the BE\_USER session identifier into the cache key. Apply it via [cweagans/composer-patches](https://github.com/cweagans/composer-patches)by adding the following to **your project's** `composer.json` (the extension's own `composer.json` does not declare patches because `composer-patches` v1.7 cannot resolve sub-package patch paths from the consuming project):

```
{
    "require-dev": {
        "cweagans/composer-patches": "^1.7"
    },
    "config": {
        "allow-plugins": {
            "cweagans/composer-patches": true
        }
    },
    "extra": {
        "composer-exit-on-patch-failure": true,
        "patches": {
            "typo3/cms-core": {
                "FormProtectionFactory: session-aware cache key for worker-mode safety": "vendor/ochorocho/frankenphp/Patches/cms-core-form-protection-factory-session-aware-cache.patch"
            }
        }
    }
}
```

After adding this, run `composer update typo3/cms-core` (or delete `vendor/typo3/cms-core` and re-run `composer install`) so the patch is applied. Subsequent `composer install` / `composer update` runs re-apply automatically.

Diagnostics
-----------

[](#diagnostics)

The TYPO3 backend's **System Information** dropdown (the info icon in the topbar) shows a **Worker Mode** row — `Enabled` when the current request is being served by the long-running FrankenPHP worker, `Disabled` when served by per-request PHP execution (e.g. the install-tool recovery URL via `/index.php`). The row icon is the FrankenPHP mascot ( the skeleton elephant from `frankenphp.dev`). Use it to quickly verify that requests you expect to hit the worker actually do.

Prometheus metrics dashboard widget
-----------------------------------

[](#prometheus-metrics-dashboard-widget)

Run `vendor/bin/typo3 frankenphp:init --prometheus` (add `--force` to overwrite an existing Caddyfile / `.env`). This adds:

- `metrics` + `admin localhost:METRICS_PORT` to the Caddyfile global block.
- `METRICS_PORT=` (default `2019`) to `.env`.

A dashboard widget titled **Prometheus Metrics** then appears in the *FrankenPHP* widget group. It charts the metric you pick — FrankenPHP worker-pool gauges, Caddy HTTP counters/histograms, or Go runtime stats — by polling the backend AJAX route `ajax_frankenphp_metrics` (`Configuration/Backend/AjaxRoutes.php`), which proxies `http://127.0.0.1:METRICS_PORT/metrics`. The proxy exists because Caddy's admin endpoint rejects any browser request that ships an `Origin` header; only server-side scrapers (this proxy, Prometheus, `curl`) can reach it directly.

The curated metric list lives in `PrometheusMetricsWidget::METRIC_CHOICES`. Enumerate what your build actually exposes with:

```
curl http://localhost:2019/metrics | grep "^# TYPE"

```

Install Tool access
-------------------

[](#install-tool-access)

Two URLs reach the TYPO3 Install Tool, each routed differently:

- **`https://your-host/typo3/install`** — preferred for normal maintenance. Goes through the worker via the standard backend route. Requires a logged-in admin backend session.
- **`https://your-host/?__typo3_install`** — recovery URL. Caddy routes this to TYPO3's canonical `public/index.php` ( which boots with `$failsafe=true` and runs `InstallApplication`). Works without backend login but requires the unlock file `public/typo3conf/ENABLE_INSTALL_TOOL` (create via `touch public/typo3conf/ENABLE_INSTALL_TOOL`; auto-removed after one hour). Also accepts the standard controller-routing query parameters, e.g. `?__typo3_install&install[controller]=maintenance`.

If you ever change the Caddyfile manually and forget to keep the `@typo3_install` matcher, the recovery URL will 404 — `vendor/bin/typo3 frankenphp:init --force --no-interaction` regenerates a working config.

### Action URLs are AJAX-only

[](#action-urls-are-ajax-only)

URLs that carry both `install[controller]=…` and `install[action]=…` (anything other than `install[controller]=layout`) are designed for the install tool's own JS to call via `XMLHttpRequest`. They return a JSON envelope `{success: true, html: '…', buttons: [...]}` for the JS to inject into a modal. Pasting such a URL into a browser address bar shows the raw JSON, not a usable page.

To avoid that confusion, the Caddyfile's `@install_browser_ajax` matcher detects browser top-level navigation ( `Sec-Fetch-Mode: navigate` + `Sec-Fetch-Dest: document`, without `X-Requested-With: XMLHttpRequest`) to `?__typo3_install&install[action]=…` URLs and redirects (302) to the install tool dashboard at `/?__typo3_install`. From there, click into the relevant tile (Maintenance, Settings, Upgrade, Environment). The redirect is a Caddyfile-level concern; no extra PHP entry-point is involved.

For long-running maintenance like the reference index, the CLI alternative is usually preferable — the install tool's own UI literally points at this:

```
vendor/bin/typo3 referenceindex:update -c   # check only
vendor/bin/typo3 referenceindex:update      # rebuild
```

Repository layout
-----------------

[](#repository-layout)

This repository is the **extension package**, not a TYPO3 installation. A throwaway TYPO3 sandbox is materialized in `Build/` (gitignored) so the extension can be exercised end-to-end.

FolderPurpose`Classes/`Extension PHP source — `Command/` (`frankenphp:init`), `Controller/Backend/` (metrics AJAX proxy), `Service/` (`PrometheusTextParser`), `Widget/` (`PrometheusMetricsWidget`), `Worker/` (`StateSnapshotService` — survives singleton state across worker requests), `EventListener/`, `Middleware/`, `Event/`, `Composer/` (TYPO3 installer-scripts hook).`Configuration/`TYPO3 service wiring (`Services.yaml`, `Services.php`), `Backend/AjaxRoutes.php`, `Backend/DashboardWidgetGroups.php`, `Backend/DashboardPresets.php`, `JavaScriptModules.php`, `Icons.php`, `RequestMiddlewares.php`.`Resources/Private/`Fluid templates (`Templates/Widget/`), `Language/` XLF files, and `Php/worker.php` — the template `InitCommand` copies into the user's `public/`.`Resources/Public/`Frontend assets — `JavaScript/widget/` (Chart.js-backed Lit web component for the metrics widget), `Css/widget/`, `Icons/`.`Tests/``e2e/` Playwright suite (correctness) and `load/` k6 scenarios (performance + worker stability). Each has its own README.`scripts/`Developer bootstrap — `setup-typo3.sh` materializes the `Build/` sandbox.`Build/`**Gitignored.** Throwaway TYPO3 install for development. `Build/composer.json` requires this extension via a Composer path repository pointing at `../`, so edits to root `Classes/` / `Resources/` / `Configuration/` affect the sandbox immediately.Contributing
------------

[](#contributing)

### Prerequisites

[](#prerequisites)

- PHP 8.3+, Composer, `sqlite3` on `$PATH`.
- `frankenphp` binary on `$PATH` — see .
- ImageMagick is optional and auto-detected by `setup-typo3.sh`. Override with `MAGICK_BIN=/abs/path/to/magick`.

### Bootstrap the dev sandbox

[](#bootstrap-the-dev-sandbox)

```
git clone git@github.com:ochorocho/typo3-frankenphp.git
cd typo3-frankenphp
scripts/setup-typo3.sh                              # TYPO3 ^14.3 (default)
TYPO3_VERSION='^13.0' scripts/setup-typo3.sh        # or any Composer constraint
TYPO3_VERSION='15.*@dev' scripts/setup-typo3.sh
```

The script is **idempotent**. On a re-run with the same `TYPO3_VERSION` it skips work that's already done; with a different version it resets `Build/vendor/`, `composer.lock`, `config/system/`, and `var/cache` before re-installing — so switching TYPO3 majors is one command. It writes a `Build/composer.json` that requires the extension as a symlinked path repository (`"url": "../"`), so editing root files affects the sandbox immediately with no extra step.

Admin login (created by `typo3 setup`): `admin` / `Password.1`. Override via:

```
TYPO3_SETUP_ADMIN_USERNAME=foo TYPO3_SETUP_ADMIN_PASSWORD='S3cret!' scripts/setup-typo3.sh
```

### Run the dev server

[](#run-the-dev-server)

```
cd Build && frankenphp run
```

- Frontend:  /
- Backend:

To regenerate `Caddyfile` / `.env` / `php.ini` / `public/worker.php` after switching profiles or toggling `--prometheus`:

```
cd Build && vendor/bin/typo3 frankenphp:init --no-interaction --force
```

### Run with Docker (no native FrankenPHP / Composer / PHP needed)

[](#run-with-docker-no-native-frankenphp--composer--php-needed)

If you'd rather not install PHP, Composer, `sqlite3`, and the `frankenphp` binary on your host, a Docker Compose setup is provided that runs everything in containers, backed by **MariaDB** instead of SQLite.

The first boot is slow — it fetches the images, provisions the FrankenPHP image, downloads all of TYPO3, and runs `typo3 setup`.

```
# Run interactively
docker compose up --build

# Run detached and wait for FrankenPHP to become healthy
docker compose up -d --build --wait
```

Subsequent `docker compose up` runs skip every already-completed step and start immediately.

Once the `frankenphp-app` became healthy, open the app in your browser:

- **Backend**:  (Login with `admin` / `Password.1`) > Your browser will warn about the self-signed certificate. Click through ("Advanced → Proceed") or use `curl -k` to bypass it.
- **Frontend**: > The frontend won't have anything meaningful (e.g. site configuration) in this sandbox, yet.

Amend the configuration to your needs:

ConcernWhere to change itPorts`HTTP_PORT` / `HTTPS_PORT` in `docker-compose.yml` (or a Compose-level `.env`). Defaults: `8888` / `8885`.TYPO3 version`TYPO3_VERSION` in `docker-compose.yml` (any Composer constraint, e.g. `15.*@dev`).Worker pool`FRANKENPHP_WORKER_COUNT` / `MAX_REQUESTS` in `docker-compose.yml`.DB / admin creds`TYPO3_DB_*` / `TYPO3_SETUP_*` in `docker-compose.yml`.Added PHP extensions`docker/Dockerfile`.To rebuild from scratch (e.g. after changing `TYPO3_VERSION`), wipe the named volumes first:

```
docker compose down -v && docker compose up --build
```

### Static analysis &amp; code style

[](#static-analysis--code-style)

Dev dependencies are pinned in `Build/composer.json`, not the root package — run the tools from inside `Build/`:

```
cd Build && vendor/bin/phpstan analyse ../Classes
cd Build && vendor/bin/php-cs-fixer fix ../Classes
```

### Tests

[](#tests)

SuiteLocationWhat it coversEnd-to-end (Playwright)`Tests/e2e/`Backend correctness against the running sandbox. See `Tests/README.md`.Load / soak (k6)`Tests/load/`Throughput, tail latency, and (most importantly) the regression net for `Classes/Worker/StateSnapshotService.php`. See `Tests/load/README.md`.### Submitting changes

[](#submitting-changes)

Standard GitHub PR workflow against `main`. Please make sure `phpstan` and `php-cs-fixer` are clean and include a Playwright or k6 test when the change is behavior-visible.

###  Health Score

35

—

LowBetter than 77% of packages

Maintenance97

Actively maintained with recent releases

Popularity5

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity26

Early-stage or recently created project

 Bus Factor1

Top contributor holds 92.2% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~2 days

Total

3

Last Release

15d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/e63334d6fdbd8f65eeb10a8e8913dca96a07ec2caffebdb2381c7b573c128237?d=identicon)[ochorocho](/maintainers/ochorocho)

---

Top Contributors

[![ochorocho](https://avatars.githubusercontent.com/u/4623070?v=4)](https://github.com/ochorocho "ochorocho (59 commits)")[![mteu](https://avatars.githubusercontent.com/u/2636487?v=4)](https://github.com/mteu "mteu (5 commits)")

###  Code Quality

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/ochorocho-frankenphp/health.svg)

```
[![Health](https://phpackages.com/badges/ochorocho-frankenphp/health.svg)](https://phpackages.com/packages/ochorocho-frankenphp)
```

###  Alternatives

[friendsoftypo3/content-blocks

TYPO3 CMS Content Blocks - Content Types API | Define reusable components via YAML

101466.4k44](/packages/friendsoftypo3-content-blocks)[civicrm/civicrm-core

Open source constituent relationship management for non-profits, NGOs and advocacy organizations.

744284.3k34](/packages/civicrm-civicrm-core)[wazum/sluggi

TYPO3 extension for URL slug management with inline editing, auto-sync, locking, access control, and redirects

41515.2k](/packages/wazum-sluggi)[aoe/restler

A TYPO3-Extension, that integrates the popular PHP REST-framework Restler in TYPO3.

3079.2k1](/packages/aoe-restler)[netresearch/rte-ckeditor-image

Image support in CKEditor for the TYPO3 ecosystem - by Netresearch

611.0M6](/packages/netresearch-rte-ckeditor-image)[praetorius/vite-asset-collector

Use AssetCollector to embed frontend assets generated by vite

54299.7k1](/packages/praetorius-vite-asset-collector)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
