php-compiler

003-MiniWebApp

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).

Init template parity

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.

Layout

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

Lint

./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

Routes

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

AOT debug without serve (#774)

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

Production deploy (AOT dist + nginx)

Build, deploy, and set PHPC_DEPLOY_ROOT as above. For production nginx in front of bin/app:

Copy-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

Run matrix

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)

curl recipes (PATH_INFO)

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'

CI gate ladder

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

CI hooks

../../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