Current state (April 2026): 11,838 / 12,121 PHP 8.5.5 tests passing
(97.7%). 196 failures, 87 skipped in CI. All 20 vendored extensions
implemented in pure Go; CGO_ENABLED=0 go build ./... succeeds.
With the high-level features in place, remaining work is mostly a long
tail of targeted fixes: error-message format parity, a handful of
reflection/attribute edge cases, DST/DatePeriod subtleties, and a few
tests gated on language features we haven't implemented (goto, full
exec() subprocess support).
From the last CI run (commit 852b33af). The Other category is the
long tail — ~40 distinct areas, at most three failures each.
| Area | Failures | Notes |
|---|---|---|
| ext/date | 51 | DatePeriod serialization format, date_parse edge cases, DST fallback transitions |
| attributes | 20 | Reflection __toString formatting, delayed target validation, AST printing |
| exceptions | 9 | __toString error location, variance autoload, stream wrappers |
| closures | 9 | Closure const expressions, binding edge cases |
| clone | 6 | AST printing, clone-with edge cases |
| exit | 5 | Tests rely on exec() subprocess spawning, disabling exit |
| constexpr | 5 | New-in-defaults, array unpack in const, stack trace format |
| asymmetric_visibility | 5 | Static props, nested variations, indirect modification |
| assert | 5 | Callback exceptions, ??= in assert, AST pretty-printer |
| ext/mbstring | 4 | Encoding conversion edge cases |
| constants | 4 | Constant evaluation edge cases |
| ext/hash | 3 | PHP serialization format edge cases |
| ext/gmp | 3 | GMP unserialize with references |
| Other | ~67 | Scattered across ~40 areas |
Goro mirrors PHP's reference-counted object model: every ZObject
carries an int32 refcount (see core/phpobj/zobject.go,
IncRef/DecRef), object IDs are recycled via a free list in
core/phpctx/global.go (NextObjectID/ReleaseObjectID), and
destructors fire synchronously when refcount hits zero. We do NOT
rely on Go's tracing GC for observable PHP semantics.
That said, a few engine-level mismatches still bite:
- Function-argument lifetime differs — Go temporaries holding a
callee's arguments stay alive until the enclosing Go statement
finishes, which shifts object IDs by one step in tests that
interleave
newwith function calls. (~3 ext/gmp tests and a scattering elsewhere — search the skip list for "object ID timing".) goto— not implemented. (~5 tests.)debug_zval_dumprefcount display — PHP prints the exact refcount; some of ours are off by one because we don't reliably decrement for short-lived intermediates. (~handful of tests.)gmp_random_*seeding —mt_rand/randuse real MT19937 viagithub.com/goark/mt/mt19937and produce byte-identical output to PHP for a given seed. Butext/gmp/misc.goseeds Go's legacymath/rand(LCG-style) for its random helpers, which disagrees with libgmp's own MT state. One skipped test (gmp_random_seed.phpt). Switching gmp over to the same MT source would at least make it internally consistent, but still wouldn't byte-match PHP's libgmp.SensitiveParameterbacktrace redaction — not implemented.
The vendored PHP 8.5.5 test set is 12,121 tests. Ten extensions have
their tests imported (ctype, date, gmp, hash, json, mbstring, pcre, reflection, spl, standard), alongside Zend/tests (core language)
and tests/ (general).
| Area | Tests | Pass rate |
|---|---|---|
| ext/standard | 3,808 | high |
| ext/spl | 781 | ~82% |
| ext/date | 688 | ~93% |
| ext/reflection | 493 | ~75% |
| ext/mbstring | 416 | ~97% |
| ext/pcre | 163 | 67% (PCRE2 via gopcre2) |
| ext/gmp | 99 | 97% |
| ext/json | 88 | 98% |
| ext/hash | 80 | 94% |
| ext/ctype | 49 | 100% |
Not yet vendored: ext/{bz2, curl, gd, mbstring (some subdirs), mysqli, openssl, session, sockets, sqlite3, xml, zlib} tests. The
extensions themselves are implemented (see README), but the upstream
test suites aren't imported into the repo yet.
- ext/date, attributes, exceptions, closures — these four buckets account for ~50% of remaining failures. Each is a grab-bag of small fixes (error-message format, reflection output, AST printing).
- AST pretty-printer — multiple test areas (
assert,clone,attributes,constexpr) depend on it matching PHP's exact format. A focused pass on the printer would clear a dozen tests. - Reflection
__toString— the remaining attribute/reflection failures largely come from slight format differences.
- PDO — via
database/sql; the MySQL and SQLite drivers are already in the tree (go-sql-driver/mysql,glebarez/go-sqlite). iconv— maps cleanly ontogolang.org/x/text/transform.exec()subprocess support — would unlock theexit/tests.
- intl — full ICU is substantial; leave unless a concrete user needs it.
- Phar — PHP archive format; rarely needed outside legacy code.
- Deterministic object-id / destructor semantics — would require moving off Go's GC onto refcounting throughout the value system. Test impact is ~50 scattered failures; not worth the cost.
- April 2026 — PHP test suite refreshed to 8.5.5 (12,110 → 12,121 tests). All cgo removed; pure-Go bcrypt/SHA-crypt/MD5-crypt and a POSIX "C" locale emulation replaced the libc wrappers.
- March 2026 — 20th extension (gd, pure Go via gogd) added; pass rate crossed 97% on the full PHP suite.
- Earlier 2026 — New extensions (session, xml, curl, sockets, zlib, mysqli, sqlite3, bz2); library integrations (gopcre2, gotz, gobzip2, go-sql-driver/mysql, glebarez/go-sqlite).