Compile ThinkPHP 8 project (C:\git\php\typephp-think) into a standalone CLI binary using TPC (TypePHP Compiler), with a patched RunServer.php that provides a pure-PHP HTTP server for the embed SAPI.
Phase 10 (Complete - Audit Ignored Files & Patch to Compile)
- Locate TPC binary and understand build requirements
- Create build.bat with vcvarsall.bat + tpc.exe invocation
- Status: complete
- Fix
returnstatements in config files (moved config/ to ignore) - Fix stray code in route/app.php (moved route/ to ignore)
- Fix parent class chain issues (moved app/ to ignore, only compile main.php)
- Fix project.yml:
source->sourceskey name - Status: complete
- PHPX
Object.attr()bypasses__get()magic method - Solution: use
$app->make('console')->run()instead of$app->console->run() - Status: complete
- Detect embed SAPI (
PHP_SAPI === 'embed') - Implement pure-PHP socket HTTP server for embed mode
- Preserve original
passthru()for CLI mode - Static file serving with MIME mapping
- Set
$_SERVER,$_GET,$_POST,$_COOKIEsuperglobals - Create patches/ directory copy for Composer patch system
- Status: complete
- Set
SCRIPT_NAME=/index.php,PHP_SELF= URI path - Compute correct
PATH_INFO(strip/index.phpprefix) - Force new Request instance per request (
make('request', [], true)) - Verify route matching works (
/think,/hello/:name) - Verify controller dispatch works (
/index.php/index/hello) - Verify compat mode works (
?s=index/hello) - Status: complete
- Add
php_pdo_mysql.dllandphp_mysqli.dllto php.ini - User confirmed php.ini placement works
- Status: complete
- Benchmark CLI mode (
php think run) - Benchmark embed mode (
myapp.exe run) - APP_DEBUG=true: CLI 29.4 req/s vs Embed 157.9 req/s (5.4x)
- APP_DEBUG=false: CLI 34.1 req/s vs Embed 315.3 req/s (9.2x)
- Status: complete
- Create planning files (task_plan.md, findings.md, progress.md)
- Status: complete
- Add flysystem, flysystem-local, polyfill-mbstring, var-dumper to project.yml sources
- Patch TPC incompatibilities (interface constants, throw patterns, traits, switch/if, variadic refs)
- Ignore unfixable files (bootstrap, Caster/, Command/, Dumper/, VarCloner, traits)
- Root-cause
class '' is undefined500 error (TPC if/else static-call codegen bug in Str::studly) - Fix: inline str_replace/ucwords studly equivalent in Controller.php patch
- Remove all debug probes from patches
- Final build: 350 files, EXIT_CODE=0
- All routes 200 (/think, /, /hello/world, JSON)
- Benchmark: 275.99 req/s, 100% success (500 req, c=10)
- Status: complete
- Audit 43-item ignore list; classify clean/patchable/unpatcheable
- Patch trace Html/Console, flysystem-local LocalFilesystemAdapter, FinfoMimeTypeDetector, 8 Caster files, CliDumper/HtmlDumper, think-dumper Dumper/ServerDumper
- Bisect TPC internal crash (dumpEllipsis) + switch fall-through (dumpKey) via AST stubbing
- Add 3 Caster ignores (AmqpCaster/PgSqlCaster/RedisCaster — unresolvable ext constants)
- Build: 417 files, EXIT_CODE=0, 11.9MB
- Fix trace panel regression (include-scope: $GLOBALS bridge for page_trace.tpl)
- All routes 200 with trace panel rendering
- Final probe-free rebuild (11,897,344 bytes, 16:02:09; probe verified removed)
- Benchmark 417-file build: 274.16 req/s, 100% success (no regression vs 350-file build)
- Status: complete
- TPC can only compile
main()function code — all framework code runs at runtime via embed SAPI - PHPX
attr()doesn't trigger__get()— must use explicitmake()calls - Embed SAPI PHP_SAPI='embed' (not 'cli') — affects pathinfo resolution in ThinkPHP
- TPC codegen bug: static method calls in if/else branches may use uninitialized class tmp vars →
class '' is undefined— avoidStr::staticMethod()in if/else, inline instead
| Decision | Rationale |
|---|---|
| Only compile main.php | TPC cannot handle framework class hierarchies, config return stmts, or route definitions |
| Pure-PHP socket server | PHP built-in server needs passthru() + PHP_BINARY which don't work in embed SAPI |
| Patch via Composer system | patches/ dir + patch.php overrides vendor files non-destructively |
make('request', [], true) |
Request object caches pathinfo; must create fresh per HTTP request |
| Set PATH_INFO explicitly | embed SAPI PHP_SAPI='embed' skips the str_contains(PHP_SAPI, 'cli') fallback |
| Error | Attempt | Resolution |
|---|---|---|
| Unsupported statement: Stmt_Return | 1 | Moved config/ from sources to ignore |
| Stray code: Route::get() | 1 | Moved route/ from sources to ignore |
| Non-existent parent class | 1 | Moved app/ from sources to ignore |
$app->console null at runtime |
1 | Changed to $app->make('console')->run() |
| build dir missing | 1 | Created build/ directory manually |
| All URLs return welcome page (no routing) | 1 | Set PATH_INFO + SCRIPT_NAME + PHP_SELF correctly |
| Pathinfo computed as empty | 2 | Force new Request via make('request', [], true) |
| PDO: could not find driver | 1 | Added php_pdo_mysql.dll to php.ini |
Crash: Attempt to read property 'trait' on null |
3 | Interface public const unsupported → moved constants to classes |
| Build timeout 15 min (aggregate+link never reached) | 2 | 25-min timeout + manual link (C:\temp\link_only.bat) |
class '' is undefined (500 on /hello/world, server crash) |
4 | TPC if/else static-call bug: Str::studly() in else branch used uninitialized tmp class var → inlined studly |
Unsupported statement: Stmt_If (top-level if in dump.php) |
1 | Ignored file |
.obj size > 4GB / LNK errors |
1 | /bigobj cxx-flag + correct .rsp generation filtering build\php\typephp-think + phpx-misc |
Cannot re-assign typed object $dumper (HtmlDumper↔CliDumper) |
2 | Rewrote think-dumper createHandler with per-branch closures |
Declaration of dump() must be compatible with interface |
1 | Added : ?string to ServerDumper::dump() |
C2679 static_int_ref += php::Variant (cut += expr) |
3 | Split into separate +=/-= with literal operands (ReflectionCaster/SymfonyCaster/XmlReaderCaster) |
| C2026 string literal too big (27KB Sfdump JS nowdoc) | 2 | Split into 2 concatenated nowdocs (closing marker indent = min content indent) |
TPC internal crash Invalid callback getObjectPropertyTypeCheckDisplayName |
3 | dumpEllipsis $this->line .= $cut (int→string prop) → (string)$cut |
switch case must end with return/break/exit/throw (dumpKey) |
2 | Rewrote switch as if/elseif chain |
Undefined variable $trace in page_trace.tpl (all HTML 500) |
2 | include doesn't pass method locals → $GLOBALS['tp_page_trace'] bridge |
| Private method/property override/shadowing false positives | 1 | private→protected (getSourceLink, displayOptions) |
| Extension constants (AMQP_/PGSQL_/Redis::*) unresolvable | 1 | Ignored AmqpCaster/PgSqlCaster/RedisCaster (dead code, ext not loaded) |
| File | Purpose |
|---|---|
C:\git\php\typephp-think\main.php |
Entry point with main() function |
C:\git\php\typephp-think\project.yml |
TPC compilation config (sources + ignore lists) |
C:\git\php\typephp-think\build.bat |
Build script (vcvarsall + tpc) |
C:\git\php\typephp-think\vendor\topthink\framework\src\think\console\command\RunServer.php |
Patched HTTP server |
C:\git\php\typephp-think\patches\topthink\framework\src\think\console\command\RunServer.php |
Patch source for Composer |
C:\git\source\tpc_v1095_windows_x86_64\php.ini |
Added pdo_mysql + mysqli extensions |
C:\git\php\typephp-think\patches\topthink\framework\src\think\route\dispatch\Controller.php |
Str::studly() → inline (class '' fix), make('config') bypass |
C:\git\php\typephp-think\patches\topthink\framework\src\think\initializer\Error.php |
Inline assignment-in-condition fix |
C:\git\php\typephp-think\patches\league\flysystem\src\* |
Interface constants, throw patterns, trait removal |
C:\git\php\typephp-think\patches\symfony\polyfill-mbstring\Mbstring.php |
Switch/if, variadic ref, array_walk_recursive fixes |
C:\git\php\typephp-think\patches\symfony\var-dumper\* |
Fall-throughs, top-level require removal |
C:\git\php\typephp-think\patches\symfony\var-dumper\Caster\{ClassStub,DateCaster,ExceptionCaster,ReflectionCaster,SocketCaster,SymfonyCaster,XmlReaderCaster}.php |
Type-union renames, CLASS literal, out-params, cut += splits |
C:\git\php\typephp-think\patches\symfony\var-dumper\Dumper\{CliDumper,HtmlDumper}.php |
goto→flag, switch→if/elseif, heredoc split, protected overrides |
C:\git\php\typephp-think\patches\topthink\think-dumper\src\{Dumper,ServerDumper}.php |
createHandler closures, :?string |
C:\git\php\typephp-think\patches\topthink\think-trace\src\{Html,Console}.php + tpl\page_trace.tpl |
switch break, var_export, $GLOBALS trace bridge |
C:\git\php\typephp-think\patches\league\flysystem-local\LocalFilesystemAdapter.php |
17× throw split, generator restructure, $mode rename |
C:\git\php\typephp-think\patch.php |
Copies patches/ → vendor/ (overwrite only) |
- Location:
C:\git\php\typephp-think\build\myapp.exe(11,896,832 bytes, 417-file build, 2026/8/2 15:25:05) - Runtime DLLs:
C:\git\source\tpc_v1095_windows_x86_64\(php8ts.dll, phpx.dll, ext/*.dll) - Commands:
myapp.exe run,myapp.exe list,myapp.exe version,myapp.exe info