Reference web app: skeleton #67 closed (#246); VM/runtime tracker #539; routing #210. phpc serve and lint are green; PATH_INFO URLs in #489; AOT link ✅ (#752); native execute ✅ (#764 closed). VM/JIT/AOT matrix for PATH_INFO, deploy includes, and CGI: capabilities-syntax.md § Web north-star (#655).
phpc init --profile miniwebapp scaffolds from templates/init-miniwebapp/. Key app files (public/index.php, src/Router.php, config.php, phpc.json, templates, assets/style.css) must stay byte-identical to this directory (#695).
./script/check-init-miniwebapp-parity.sh # wired into ci-fast inventory checks
Update both trees in one PR when changing routes or templates. Rare intentional drift: use // miniwebapp-parity: intentional divergence — <reason> in both files.
examples/003-MiniWebApp/
README.md
phpc.json # entry public/index.php, includes[] (#452)
config.php
public/index.php # PATH_INFO + ?route= fallback (#489)
src/Router.php # class dispatch (VM/JIT/AOT)
templates/ # layout + partials (__DIR__ includes)
assets/style.css
./phpc lint --all examples/003-MiniWebApp
Exits 0 (class methods, includes, break, and superglobals are accepted). make web-smoke fails when lint regresses (gate on by default — #621). Skeleton debugging only:
MINIWEBAPP_LINT_GATE=0 make web-smoke
| Method | URL | Behavior |
|---|---|---|
| GET | /index.php or / |
Home |
| GET | /index.php/hello?name= |
Greet |
| POST | /index.php/contact |
Form thank-you (name required, max 200 chars — #697) |
| GET | /index.php/api/status |
JSON status |
Deprecated query dispatch (still supported):
| Method | URL |
|---|---|
| GET | /index.php?route=home |
| GET | /index.php?route=hello&name= |
| POST | /index.php?route=contact |
| GET | /index.php?route=api/status |
After phpc build --project . (LLVM), run the native binary with CGI env — no TCP:
../../phpc run --project . --cgi-env QUERY_STRING=route=home --cgi-env REQUEST_METHOD=GET
../../phpc run --project . --cgi-env-file ../../test/fixtures/cgi-env/miniwebapp-home.env
../../phpc run --project . --cgi-env-file ../../test/fixtures/cgi-env/miniwebapp-home.env --require-nonempty-stdout
--require-nonempty-stdout exits 2 when stdout is empty. With phpc deploy -o /tmp/dist, add --deploy-root /tmp/dist.
Deploy + execute smoke (stage 4d, #745):
../../phpc build --project .
../../phpc deploy . -o /tmp/miniwebapp-dist
PHPC_DEPLOY_ROOT=/tmp/miniwebapp-dist eval "$(../../script/miniwebapp-cgi-env.php --export shellQueryRouteHome)" ../../phpc run --project . --deploy-root /tmp/miniwebapp-dist
DEPLOY_SMOKE_003_EXECUTE=1 ../../script/deploy-smoke.sh --example 003
DEPLOY_SMOKE_ONLY=003 DEPLOY_SMOKE_003_EXECUTE=1 make deploy-smoke
Build, deploy, and set PHPC_DEPLOY_ROOT as above. For production nginx in front of bin/app:
/index.php, PATH_INFO, ?route=) → CGI/FastCGI to the native binary/assets/style.css) → nginx alias to dist assets/ — the AOT binary does not serve CSS in v1templates/ — not web-exposed; copied for runtime includes onlyCopy-paste nginx snippets, dist tree, and local file smoke: docs/deploy-web-aot.md § Static assets (#696). Full production guide: #445.
../../phpc build --project .
../../phpc deploy . -o /tmp/miniwebapp-dist
test -f /tmp/miniwebapp-dist/assets/style.css
| Mode | Status | Command |
|---|---|---|
| Lint | ✅ | ./phpc lint --all . |
| VM serve | ✅ | ./phpc serve 127.0.0.1:8080 . from this directory |
| Shell smoke | ✅ | ../../script/examples-web-smoke.sh (after lint green) |
| Shell smoke (ci-local) | ✅ | ../../script/ci-local.sh (MINIWEBAPP_WEB_SMOKE_GATE=1 default; =0 to skip — #664) |
| PHPUnit serve | ✅ | ServeTest @group miniwebapp (#470) |
| JIT | partial | #207 |
| AOT link | ✅ | ../../phpc build --project . when LLVM ready (MINIWEBAPP_AOT_LINK_GATE=1 default — #754) |
| AOT execute | ✅ | MiniWebAppAotExecuteTest + ExamplesCompileTest execute methods (MINIWEBAPP_AOT_EXECUTE_GATE=1 default — #747, #764 closed) |
| AOT preflight | ✅ | phpc doctor --aot-project-probe — build + home-route execute needle (#746) |
cd examples/003-MiniWebApp
../../phpc serve 127.0.0.1:8080 .
curl -s 'http://127.0.0.1:8080/index.php'
curl -s 'http://127.0.0.1:8080/index.php/hello?name=Dev'
curl -s -X POST -d 'name=PostDev' 'http://127.0.0.1:8080/index.php/contact'
curl -s 'http://127.0.0.1:8080/index.php/api/status'
Query fallback:
curl -s 'http://127.0.0.1:8080/index.php?route=home'
Progressive stages from script/miniwebapp-gates.sh / make miniwebapp-gates (full ladder table: docs/miniwebapp-gates.md, #472):
| Stage | Check | Status |
|---|---|---|
| 1 | phpc lint --all |
✅ green |
| 1b | MINIWEBAPP_VM_CLI_GATE=1 in ci-fast |
✅ default on |
| 2 | ServeTest @group miniwebapp |
✅ default on |
| 3 | examples-web-smoke.sh 003 curls |
✅ wired |
| 3b | MINIWEBAPP_WEB_SMOKE_GATE=1 shell smoke |
✅ default on |
| 4a | phpc build --project --dry-run |
probe (LLVM) |
| 4c | EXAMPLES_AOT_SMOKE_ONLY=003 smoke slice |
✅ when MINIWEBAPP_AOT_EXECUTE_GATE=1 (default) (#683) |
| 4d | DEPLOY_SMOKE_003_EXECUTE=1 deploy-smoke --example 003 |
✅ when gated (#745) |
| 4b | ExamplesCompileTest::test003MiniWebAppBuildLinks |
✅ link gate (#754) |
| 4b2 | test003MiniWebAppExecutesWithCgiEnv |
✅ default on (#747, #791) |
| 4b2 bisect | script/miniwebapp-aot-bisect.sh ordered PHPT ladder |
opt-in MINIWEBAPP_AOT_BISECT_GATE=1 (#879, #764) |
Stage 4c runs only the 003 block of script/examples-aot-smoke.sh (same pass/skip/fail UX as 4a). Full examples smoke: make examples-aot-smoke.
Ordered #764 AOT fixture bisect (smallest failing step first):
./script/miniwebapp-aot-bisect.sh --list
./script/miniwebapp-aot-bisect.sh
./script/miniwebapp-aot-bisect.sh --from nested_include_two_tier
MINIWEBAPP_AOT_BISECT_INCLUDE_APP=1 ./script/miniwebapp-aot-bisect.sh
MINIWEBAPP_AOT_BISECT_GATE=1 make miniwebapp-gates
../../phpc doctor --gates # same ladder as miniwebapp-gates.sh (#657)
make miniwebapp-gates
../../script/examples-web-smoke.sh
MINIWEBAPP_VM_CLI_GATE=1 ../../script/ci-fast.sh --filter 'MiniWebApp.*VmCli'
../../script/ci-local.sh --filter ServeTest
MINIWEBAPP_SERVE_GATE=0 ../../script/ci-local.sh # skip miniwebapp ServeTest while iterating
MINIWEBAPP_WEB_SMOKE_GATE=0 ../../script/ci-local.sh # skip 003 shell PATH_INFO curls (#664)
MINIWEBAPP_AOT_LINK_GATE=0 ../../script/ci-local.sh --filter ExamplesCompileTest # skip 003 link gate (#754)
../../script/ci-local.sh --filter test003MiniWebAppExecutesWithCgiEnv
../../script/miniwebapp-aot-bisect.sh --list # bisect ladder (#879)
MINIWEBAPP_AOT_BISECT_GATE=1 ../../script/miniwebapp-gates.sh
Fast CI runs MiniWebAppVmCliTest and MiniWebAppPathInfoVmCliTest when MINIWEBAPP_VM_CLI_GATE=1 (default). Set MINIWEBAPP_VM_CLI_GATE=0 to skip the VM CLI matrix during iteration.
Full CI runs ServeTest @group miniwebapp with --fail-on-skipped when MINIWEBAPP_SERVE_GATE=1 (default on in ci-local.sh / ci-fast.sh; set MINIWEBAPP_SERVE_GATE=0 to skip during iteration — #641).
Full CI runs script/examples-web-smoke.sh --miniwebapp-only after serve PHPUnit when MINIWEBAPP_WEB_SMOKE_GATE=1 (default on in ci-local.sh; set MINIWEBAPP_WEB_SMOKE_GATE=0 to skip during iteration — #664). Not run in ci-fast.sh. Skips when PHP_COMPILER_SKIP_SERVE_TESTS=1 or loopback bind fails (same as ServeTest).
Oversized POST body limit (stage 3, #705): after PATH_INFO curls, examples-web-smoke.sh starts phpc serve with PHP_COMPILER_MAX_BODY=1024 (override via env) and POSTs a body larger than the limit to /index.php/contact; the step fails if HTTP status is 200 (expect 413 or connection reset). Manual probe:
PHP_COMPILER_MAX_BODY=1024 ../../script/examples-web-smoke.sh --miniwebapp-only
MINIWEBAPP_VM_CLI_GATE in ci-fast.sh?route= VM CLI matrixExamplesCompileTest AOT trackerexamples-web-smoke.sh curlsServeTest @group miniwebappMINIWEBAPP_SERVE_GATE in ci-local.shMINIWEBAPP_SERVE_GATE=1 in full/fast CIMINIWEBAPP_WEB_SMOKE_GATE in ci-local.shMINIWEBAPP_WEB_SMOKE_GATE=1 in full CIexamples-web-smoke.shexamples-aot-smoke 003 slice probedeploy-smoke 003 execute E2E