Skip to content

Releases: php/frankenphp

v1.13.3

Choose a tag to compare

@dunglas-release dunglas-release released this 23 Oct 12:43
ae3d5b9

FrankenPHP 1.13.3 is a bug fix release. It fixes worker lookup for symlinked document roots, crashes caused by PHP extensions that install asynchronous signal handlers, and SIGILL crashes of musl static binaries on CPUs without SHA-NI.

🐛 Bug Fixes

  • Worker mode: Workers are now matched when resolve_root_symlink false is set and the document root is a symlink. Since 1.13.0, requests ran on a regular thread and failed with "FrankenPHP must be in worker mode". Workers are now also indexed under their declared path by @dunglas in #2739.
  • Extensions: SA_ONSTACK is now re-applied to the signal handlers that extensions install at startup, for every signal and not only SIGSEGV, SIGBUS and SIGFPE. Asynchronous handlers without the flag, such as the SIGVTALRM handler of dd-trace-php, overflowed the goroutine stack and crashed the Go runtime later during garbage collection by @mstegmeyer in #2746.
  • Static builds: musl binaries are now built for the baseline CPU of the architecture instead of the build host's CPU. Release binaries used SHA-NI and AVX-512 without runtime checks, so hash('sha256') died with SIGILL on CPUs without SHA-NI. Set SPC_TARGET=native-native-musl to opt back into host-tuned builds by @dunglas in #2749.

📖 Documentation

💖 New Contributors

Full Changelog: v1.13.2...v1.13.3

v1.13.2

Choose a tag to compare

@dunglas-release dunglas-release released this 21 Oct 18:19
0ad8a40

FrankenPHP 1.13.2 is a security release. Its Docker images and static binaries are built with the PHP versions that fix PHP2Shell (CVE-2026-109618 and CVE-2026-109619), two critical vulnerabilities allowing unauthenticated remote code execution. It also fixes 4 vulnerabilities in FrankenPHP itself, including a high-severity one that let a local user get their PHP code executed by a service running an embedded app. It also makes frankenphp_log() available in php-cli, stops hot reload from serving stale pages, and moves static builds to static-php-cli v3. Static builds have a few changes worth reading in the upgrade notes below. Upgrading is strongly recommended.

🔒 Security

  • Critical, PHP2Shell: Docker images are built with PHP 8.2.35, 8.3.36, 8.4.27 and 8.5.12, and static binaries with PHP 8.5.12. These versions fix CVE-2026-109618 and CVE-2026-109619, which allow unauthenticated remote code execution and have no complete mitigation besides upgrading (PHP security advisory).
  • High: Embedded apps were extracted to a predictable directory in the shared temp dir, and existing files were reused. A local user could precreate that directory and get their PHP code executed by the service. Apps are now extracted atomically to a per-user directory, which is only reused when the current user owns it (GHSA-6q3j-62j9-f9hh).
  • Requests waiting for a PHP thread are now dropped when their client disconnects. Before this fix, an unauthenticated client could queue requests, disconnect and keep the thread pool busy for everyone else (GHSA-fq24-r53r-xcx3).
  • Cyclic arrays are now rejected when PHP values are converted to Go. A self-referencing array passed to frankenphp_log(), returned by a worker callback or given to the extension API overflowed the stack and crashed the whole process (GHSA-ccgp-ch9h-533f).
  • Worker mode: The errors returned by error_get_last() and libxml_get_errors() are now cleared at the end of each request. Before this fix, they could leak fragments of a previous client's input and server paths into the next request served by the same thread (GHSA-4gfw-9v29-gp9h).

⚠️ Upgrade Notes

Static builds now use static-php-cli v3 by @henderkes in #2618:

  • macOS: Binaries are only published for Apple Silicon. There is no frankenphp-mac-x86_64 asset anymore. On Intel Macs, build from source with build-static.sh.
  • glibc builds: The -gnu binary now requires glibc 2.28 or later, instead of 2.17.
  • Static builder images: Run them with docker run and pass PHP_EXTENSIONS, PHP_EXTENSION_LIBS, XCADDY_ARGS and the other options as environment variables instead of Docker build args. The binary is written to the mounted /output directory, and the defaults now live in craft.yml. See the static build docs.

✨ New Features

  • CLI: frankenphp_log() and the FRANKENPHP_LOG_LEVEL_* constants are now available in php-cli, so code shared between web requests and commands can log the same way. Entries use the server's format and are written to stderr. The Caddyfile isn't loaded, so its log directives don't apply by @dunglas in #2719.
  • Extensions: frankenphp_thread_index() is exported again for C extensions such as frankenstate and frankenasync by @nickchomey in #2717.

🐛 Bug Fixes

  • Hot reload: opcache.revalidate_freq now defaults to 0 when hot_reload is configured. With the default of 2, the page fetched right after a change often came back unchanged. A value set in php.ini, in PHP_INI_SCAN_DIR or with the php_ini directive still takes precedence by @dunglas in #2680.
  • Caddy: Thread and request counts are now parsed as int. On 32-bit platforms (linux/386, linux/arm/v6 and linux/arm/v7), out-of-range values wrapped to negative numbers, so max_threads 4294967295 meant auto. They now return a range error by @dunglas in #2722.
  • Static builds: types.h is renamed to frankenphp_types.h, because libwebp's header with the same name shadowed it and broke the build by @alexandre-daubois in #2728.

📖 Documentation

  • Explain how to publish Mercure updates from php-cli through the hub's POST endpoint, and why detecting mercure_publish() while warming caches disables it in the web server too by @dunglas in #2718.
  • Fix the Russian translation by @chubakur in #2712.

💖 New Contributors

Full Changelog: v1.13.1...v1.13.2

v1.13.1

Choose a tag to compare

@dunglas-release dunglas-release released this 16 Oct 21:55
99d4e11

FrankenPHP 1.13.1 fixes the regressions reported since 1.13.0. Nextcloud and other apps that read $_SERVER['REQUEST_URI'] while booting can run in worker mode again, php-cli exposes $argv like the native CLI again (fixing Symfony Console), and Caddy workers retry failed boots as documented instead of giving up on the first error. It also fixes two process crashes and 500 errors during graceful shutdown. Static builds now include the deepclone extension, which speeds up Symfony's cache. Upgrading is recommended for all 1.13.0 users.

✨ New Features

  • Static builds: Add deepclone to the default extensions. It is the native implementation of Symfony's DeepCloner, used by VarExporter::export() (and so the cache.system files) and ArrayAdapter since symfony/var-exporter 8.1, and is several times faster than the PHP polyfill on large object graphs by @soyuka in #2699.

🐛 Bug Fixes

  • Workers: Set $_SERVER['REQUEST_URI'] to the script name while booting workers, as in 1.12.7. Nextcloud redirected and exited during boot, so its worker failed with "too many consecutive failures" by @dunglas in #2701. Fixes #2697.
  • Workers: max_consecutive_failures now defaults to 6 for workers declared in a Caddyfile or JSON config, as documented. Previously, any boot failure aborted startup without retrying. An explicit 0 still disables retries by @dunglas in #2704.
  • CLI: php-cli sets $argv[0] to the script path (or Standard input code with -r) like the native CLI SAPI, instead of the binary name followed by a shifted script path. This broke Symfony Console and other tools reading $argv by @henderkes in #2691. Fixes #2690.
  • Graceful shutdown: Keep PHP running until Caddy has stopped all apps, so requests served during shutdown_delay and in-flight requests no longer get HTTP 500 errors by @nesl247 in #2645. Fixes #2644.
  • Threads: Fix a segfault of the whole process when restarting an unhealthy thread, a regression since 1.12.4 by @BastienClement in #2696. Fixes #2688.
  • Request body: Don't read the request body after the request has finished. With fastcgi_finish_request() or frankenphp_finish_request() over HTTP/2, PHP's shutdown drained the body after the handler returned, which could crash the process by @dunglas in #2702. Fixes #2694.
  • Forked processes: Only tie direct children of FrankenPHP to its lifetime, so processes that double-fork no longer kill their grandchildren. This broke the gnupg extension ("could not init keylist"), a regression since 1.12.5 by @dunglas in #2703. Fixes #2689.
  • Mercure: Exclude the hub from encode in the sample Caddyfile and document the pattern. Since Caddy 2.11.7 compresses server-sent events, each subscriber held a compression encoder (a few MiB with zstd), and compressing private updates next to attacker-influenced data enables BREACH-style attacks. php-server --mercure isn't affected by @dunglas in #2692.

💖 New Contributors

Full Changelog: v1.13.0...v1.13.1

v1.13.0

Choose a tag to compare

@dunglas-release dunglas-release released this 04 Oct 14:01
233f793

FrankenPHP 1.13.0 is a big one. It upgrades to Caddy 2.11.7, bringing Slowloris protection, the new url_pattern matcher, the Incremental header for streaming apps and many reverse proxy fixes. It ships with Mercure 1.0, the stable version of the real-time protocol. Configuration reloads are now safe: an invalid configuration is rejected before it replaces the running one, instead of leaving the server answering 500. This release also fixes 5 security vulnerabilities, 2 of them rated high. Upgrading is strongly recommended, and the upgrade notes below are worth a read first. Need help with the upgrade or with sizing threads and workers? Les-Tilleuls.coop, the company behind FrankenPHP, provides professional support, performance audits, custom development and training: contact@les-tilleuls.coop.

🔒 Security

  • High: Fix a document-root escape in SCRIPT_FILENAME resolution on Windows (GHSA-868c-7h7m-mmj9).
  • High: Fix header spoofing through dot-form header names: PHP maps Foo.Bar to HTTP_FOO_BAR like Foo-Bar. Caddy 2.11.7 now drops such headers by default, and the NewRequestWithContext() documentation warns library users (GHSA-qcrp-8483-f2f2).
  • Require a path segment boundary when splitting the CGI path, so /uploads/a.php.txt/b.php no longer executes uploads/a.php (GHSA-xxjp-cjxr-2x6m).
  • putenv() no longer writes to the process-wide OS environment, which leaked values across requests and threads (GHSA-996f-w38m-f574).
  • Fix a crash of the whole server process when frankenphp_log() receives a crafted array (GHSA-4prg-hv4r-g6mv).

🌐 Caddy 2.11.7

FrankenPHP 1.12.7 shipped Caddy 2.11.4. This release includes everything from Caddy 2.11.6 and 2.11.7, including:

📡 Mercure 1.0

FrankenPHP embeds Mercure 1.0 by @dunglas in #2611:

  • mercure_publish() now throws a ValueError for invalid updates (topic in the reserved /.well-known/mercure namespace, ID starting with #, control characters, invalid UTF-8, negative retry, no topic) and a RuntimeException carrying the hub's message when the dispatch fails.
  • Publisher and subscriber keys are bound to a trusted issuer with the new issuer block. The sample Caddyfile and php-server --mercure use it.

⚠️ Upgrade Notes

  • num_threads now counts the threads for requests no worker serves; worker threads come on top. A configuration that sets num_threads with workers declared starts more threads than before. If max_threads is below num_threads plus the worker threads, startup now fails with an error naming the three numbers. Defaults are unchanged (#2660).
  • Mercure: setting publisher_jwt or subscriber_jwt without protocol_version_compatibility is now a configuration error. Bind keys to a trusted issuer with the new issuer block instead (see #2611 and the sample Caddyfile). Hot reloading follows the protocol: $_SERVER['FRANKENPHP_HOT_RELOAD'] now advertises /.well-known/mercure?match=<topic>, so update any URL hardcoded with ?topic=.
  • Caddy: request headers are now limited to 16 KiB by default (raise it with max_header_size), stalled reads and writes are aborted after 1 minute, headers containing . are dropped like those containing _ already were, a wildcard site's client_auth no longer applies to more specific sites, and some invalid configurations are now rejected. See the Caddy 2.11.6 breaking changes.
  • Building PHP without --disable-zend-signals (ZTS) now makes FrankenPHP refuse to start with an explicit error, instead of hanging while memory grows (#2639).

✨ New Features

  • Extensions: Expose a thread API for Go-based PHP extensions: Thread(index) to get the request bound to a PHP thread, PHPThread.Pin() to keep Go objects alive, PHPThread.IsRequestDone(), and the frankenphp_thread_index() C function by @johanjanssens in #2305.
  • Config reloads: Validate a configuration before it replaces the running one. A missing worker file, a duplicated worker name or an inconsistent thread budget is now rejected and the running configuration keeps serving. Library users get frankenphp.Validate() by @nicolas-grekas in #2661.
  • Threads: num_threads counts the threads left for regular requests, and the startup log reports total_threads and worker_threads by @nicolas-grekas in #2660.
  • Opcache: Log opcache shared-memory restarts and count them in the new experimental frankenphp_opcache_restarts{reason} metric (PHP 8.4+). These restarts are unsafe under ZTS, so the counter should stay at zero by @nicolas-grekas in #2634.
  • php_server: Mirror php_server blocks on the FrankenPHP side, so requests and workers are properly scoped to their block by @AlliBalliBaba in #2499.
  • phpinfo: phpinfo() now has a FrankenPHP section listing the Caddy version, and Go code can add rows with frankenphp.AddPHPInfoEntry() by @henderkes in #2578.
  • CLI: php-cli uses PHP's built-in CLI SAPI on PHP 8.5+ by @withinboredom in #1757.
  • PHP 8.6: Compatibility with PHP 8.6 by @henderkes in #2600.
  • Upgrade to Go 1.27 by @alexandre-daubois in #2626.

🐛 Bug Fixes

  • Startup: Don't hang requests that arrive before regular threads are ready, which showed up as random 499/504 errors on the first requests after a container start by @ptondereau in #2612.
  • Workers: Log worker crashes at warn level with their exit status, instead of debug like a clean restart by @luminalpark in #2602.
  • Shutdown: Fix Shutdown() hanging forever on a worker that gave up during boot past max_consecutive_failures by @nicolas-grekas in #2662 and #2664.
  • Static builds: Stop the bundled parallel extension from turni...
Read more

v1.12.7

Choose a tag to compare

@dunglas-release dunglas-release released this 07 Aug 07:49
a765b08

FrankenPHP 1.12.7 fixes a bug where output written after fastcgi_finish_request()/frankenphp_finish_request() was silently discarded in classic (non-worker) mode: with the default ignore_user_abort, echoing anything after finishing the request incorrectly marked the connection as aborted and bailed out mid-script. It also quiets noisy debug-level logging for routine client disconnects and fixes an incorrect thread-count metric.

🐛 Bug Fixes

  • Classic mode: Fix output written after fastcgi_finish_request()/frankenphp_finish_request() being silently discarded. With the default ignore_user_abort, echoing anything after finishing the request incorrectly marked the connection as aborted and bailed out mid-script by @dunglas in #2569. Fixes #2548.
  • Metrics: Report frankenphp_total_threads as a gauge instead of accumulating it, so the metric reflects the current thread count instead of growing unbounded by @ousamabenyounes in #2550. Fixes #2481.
  • Opcache: Remove the opcache-invalidation restart hook again; restarting on every opcache reset could cascade into recursive restarts for frameworks that invalidate the cache heavily by @AlliBalliBaba in #2564.
  • Logging: Stop logging routine client-disconnect write/flush errors at warn level; they're now debug, matching Caddy and Mercure's convention, and no longer mislabeled as "not a flusher" by @dunglas in #2589.
  • Build: Take the PHP_CONFIG environment variable into account in go.sh by @henderkes in #2557.

📖 Documentation

  • Explain why the post-force-kill reboot wait must stay unbounded by @dunglas in #2577.

💖 New Contributors

Full Changelog: v1.12.6...v1.12.7

v1.12.6

Choose a tag to compare

@dunglas-release dunglas-release released this 21 Jul 13:45
4397463

FrankenPHP 1.12.6 fixes a crash that could take down the whole process on HTTP/2. If a PHP script calls frankenphp_finish_request() (or closes its context early) and then lazily reads php://input — common with enable_post_data_reading=Off — FrankenPHP set a read deadline on an already-finalized HTTP/2 stream and segfaulted. HTTP/1 was unaffected. Anyone serving over HTTP/2 should upgrade.

🐛 Bug Fixes

  • HTTP/2: Don't set a request body read deadline after the request is finished. Reading php://input after frankenphp_finish_request() dereferenced the nil'd-out HTTP/2 stream state and crashed the process; the deadline is now skipped once the context is done by @dunglas in #2538. Fixes #2535.

📖 Documentation

Full Changelog: v1.12.5...v1.12.6

v1.12.5

Choose a tag to compare

@dunglas-release dunglas-release released this 20 Jul 12:45
e41b848
image

FrankenPHP 1.12.5 is a security release. It fixes two disclosed vulnerabilities — an information leak in the default Docker welcome page and a cross-request session ID leak in worker mode — plus a batch of crash and hang fixes in worker mode and the CGI layer. Every user should upgrade, especially anyone running the official Docker images or the session extension as a shared module.

🔒 Security & Hardening

  • Docker images no longer serve phpinfo() by default. The default /app/public/index.php ran phpinfo(), so any derived image that didn't ship its own index leaked environment variables (often holding secrets), php.ini directives, versions, and system paths. Both the Debian and Alpine images now ship the same static, script-free welcome page used by the RPM/deb packages by @dunglas in #2524. See GHSA-cj57-c655-p798.
  • Session ID no longer leaks across worker requests when the session extension is a shared module. PS(id) wasn't reset between requests when HAVE_PHP_SESSION was undefined at compile time, so session_start() could reuse the previous request's session id and hand one client another client's session. Statically-linked builds were unaffected by @dunglas in #2523. See GHSA-v3ph-cgqh-r8p5.
  • Slow-POST protection by default. A client that stalls a request body indefinitely used to hold a PHP thread for the life of the connection, exhausting the thread pool under max_threads. php_server/php deployments now enforce a 60s idle timeout on body reads out of the box (matching nginx's client_body_timeout), configurable via the new request_body_timeout directive or 0 to disable. Library users get the same control via WithRequestBodyTimeout by @iliaal in #2465.

🐛 Fixes

  • Fix a heap-buffer-overflow read when parsing a short header("HTTP/...") status line by assuming the code from PHP's own sapi_update_response_code() instead of reparsing it by @iliaal in #2464.
  • Guard against a panic (and process crash) from a malformed RemoteAddr when stripping IPv6 brackets in server variable registration by @iliaal in #2463, and a related panic on malformed RemoteAddr in the CGI variable builder by @alexandre-daubois in #2484.
  • Fix queued requests getting stuck on replaced channels by reusing created-once channels by @henderkes in #2470.
  • Fix long-running CLI scripts hanging when a pcntl signal reached the Go runtime instead of the PHP handler by @henderkes in #2445.
  • Fix hanging reboots on threads that were upscaled and then downscaled by @AlliBalliBaba in #2510, following up on the thread-reboot-on-opcache_reset/watcher-change fix by @AlliBalliBaba in #2364.
  • Deduplicate registration-time duplicates produced by match directives by @henderkes in #2487.
  • php_server's environment variables are now available in the sandboxed environment by @henderkes in #2451.
  • extgen: correct a C parameter cast in generated class method wrapper calls by @alexandre-daubois in #2436.

⚡ Performance Improvements

  • Fold a 3-instruction TLS load into a single mrs on aarch64 by @henderkes in #2512.

📖 Documentation

💖 New Contributors


Need help hardening a worker-mode deployment against session leaks and slow-POST DoS, or auditing your Docker images before they ship secrets to the public? Les-Tilleuls.coop — the team behind FrankenPHP — provides professional support, consulting, custom development, and training. Get in touch: contact@les-tilleuls.coop.

Full Changelog: v1.12.4...v1.12.5

v1.12.4

Choose a tag to compare

@dunglas-release dunglas-release released this 04 Jun 10:29
8c9e331

FrankenPHP 1.12.4 is a hardening and stability release. It pulls in upstream security fixes from Caddy 2.11.4 and Mercure 0.24.2, closes a class of HTTP header spoofing, and fixes several crashes and data races in worker mode. Every user should upgrade.

The headline is defense-in-depth against underscore-header spoofing. CGI maps dashes to underscores (Foo-Bar becomes HTTP_FOO_BAR), so a client-supplied Foo_Bar header is indistinguishable from a legitimate Foo-Bar in $_SERVER and can spoof any header an app or upstream proxy trusts (forwarded-for, auth, etc.). The bundled Caddy 2.11.4 now ignores header fields containing underscores at the server layer, and FrankenPHP documents the risk for code using the Go API directly.

🔒 Security & Hardening

  • Underscore header spoofing blocked at the server layer. The bundled Caddy 2.11.4 now ignores HTTP header fields whose name contains an underscore, preventing collisions with the dash-to-underscore CGI mapping (reported by @Vincent550102, patched by @dunglas upstream). NewRequestWithContext now documents the risk for direct Go API users by @dunglas in #2460.
  • Caddy 2.11.4 security patches bundled: TLS client-auth fix, Windows backslash normalization in the path matcher, rewrite placeholder re-expansion fix, and a patch for GHSA-vcc4-2c75-vc9v. See the Caddy 2.11.4 release notes.
  • Mercure 0.24.2 security hardening bundled for the Mercure Caddy module: SSE field injection via id/type (CWE-93) now rejected, reserved /.well-known/mercure topic forgery blocked, Last-Event-ID metadata disclosure fixed, and DoS amplification caps added. See the Mercure 0.24.2 release notes.
  • Security model documentation describing FrankenPHP's trust boundaries and what qualifies as a security issue by @alexandre-daubois in #2455.

🐛 Fixes

  • Fix ext-parallel crashes by correctly propagating the parent thread index via SG(server_context) by @henderkes in #2438.
  • Clear in_save_handler state that blocked the subsequent close handler by @henderkes in #2443.
  • Fix a data race in metrics by replacing the mutex with a read-write mutex by @alexandre-daubois in #2450 and removing redundant shutdown assignments by @henderkes in #2452.
  • Report headers_sent() as false under CLI emulation by @henderkes in #2453.

⚡ Internal Improvements

  • Drop the unreachable space-to-underscore replacement in header names (Go's net/http already rejects spaces) by @dunglas in #2441.
  • Make UPX packing opt-in via the COMPRESS env var by @dunglas in #2429.
  • Dependency updates including Caddy 2.11.4 and Mercure 0.24.2 in #2454 and #2462.

📖 Documentation

💖 New Contributors


Need help adopting FrankenPHP, hardening a PHP application against header-spoofing and real-time security issues like these, or auditing your worker setup for races? Les-Tilleuls.coop — the team behind FrankenPHP — provides professional support, consulting, custom development, and training. Get in touch: contact@les-tilleuls.coop.

Full Changelog: v1.12.3...v1.12.4

v1.12.3

Choose a tag to compare

@github-actions github-actions released this 15 May 23:09
86fb32e

This release fixes CVE-2026-45062 (high, CVSS 8.1): unsafe Unicode handling in CGI path splitting let an attacker have a non-.php file executed as PHP via a crafted URL, in any deployment where attacker-controlled file names land on the served filesystem. All users on v1.11.2 through v1.12.2 should upgrade.

It also brings a ~7-8% Hello World throughput bump from a refreshed PGO profile, configurable per-thread request limits, persistent-zval helpers for sharing state across threads, a cross-platform force-kill primitive for stuck PHP threads, correct SCRIPT_NAME / PHP_SELF / PATH_INFO server variables, and a long series of frankenphp extension-init (extgen) generator fixes by @alexandre-daubois.

Released binaries now carry SLSA build-provenance attestations — verify with gh attestation verify <binary> --owner php or gh attestation verify oci://docker.io/dunglas/frankenphp@sha256:... --owner php.

🔒 Security

🚀 Features

🐛 Fixes

  • Set $_SERVER variables SCRIPT_NAME, PHP_SELF, and PATH_INFO correctly by @henderkes in #2317
  • Fix dead forked pthread_fork children by @henderkes in #2332
  • Fix upstream BC break on INI_INT() macro by @zeriyoshi in #2387
  • Caddy: reject invalid split_path at provision time by @alexandre-daubois in #2350
  • extgen parser hardening by @alexandre-daubois: better error handling (#2370), emit warnings to stderr (#2374), reset iota per const block (#2375), escape control chars in C string literals (#2377), extract Go function bodies via go/ast (#2379), symmetric Go type compatibility check (#2380)

⚡ Performance and Internal Improvements

📝 Documentation

💖 New Contributors


Need help adopting FrankenPHP, hardening a PHP application against issues like CVE-2026-45062, or squeezing more performance out of your workers? Les-Tilleuls.coop — the team behind FrankenPHP — provides professional support, consulting, custom development, and training. Get in touch: contact@les-tilleuls.coop.

Full Changelog: v1.12.2...v1.12.3

v1.12.2

Choose a tag to compare

@dunglas dunglas released this 09 Apr 15:43
v1.12.2
dbc09d2

This release includes several new features, bug fixes, and performance improvements. It also addresses a critical cache key collision vulnerability in Mercure (GHSA-hwr4-mq23-wcv5).

🚀 Features

🐛 Fixes

⚡ Performance and Internal Improvements

  • Extend table on environment startup instead of letting zend_hash_copy do it by @henderkes in #2272
  • Hoist LoaderFunc to package-level variable in phpheaders by @dunglas in #2053

📝 Documentation

💖 New Contributors

Full Changelog: v1.12.1...v1.12.2