aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe-php-rpc/php/worker.php
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-08-07 20:51:59 +0900
committernsfisis <nsfisis@gmail.com>2026-08-07 20:51:59 +0900
commitae365645730b95d5b01b0df7382b91668d5fa24e (patch)
tree962e3abb51132097ff11a79d453e058945ae699c /crates/shirabe-php-rpc/php/worker.php
parentf749a47804cd296a3059cd3f8079c62dbaa5fdc0 (diff)
downloadphp-shirabe-ae365645730b95d5b01b0df7382b91668d5fa24e.tar.gz
php-shirabe-ae365645730b95d5b01b0df7382b91668d5fa24e.tar.zst
php-shirabe-ae365645730b95d5b01b0df7382b91668d5fa24e.zip
refactor(platform-repository): fetch the PHP runtime in one RPC call
The RuntimeInterface seam asked the worker one question at a time: a round trip per loaded extension, per ReflectionExtension::info() output and per constant, so a single `show --platform` cost 70 to 100 of them. A `platform` dispatch entry now answers all of it as one PHP array, which shirabe-php-rpc decodes into a OnceLock-cached PlatformInfo, the way the diagnose command already works. Composer\Platform\Runtime therefore has no Rust counterpart any more. Its work belongs to the running interpreter, and invoke()/construct() could only be ported as a whitelist that panicked on anything unlisted; it is ported as PHP into the worker instead, and PlatformRepository reads the answers off PlatformInfo. Accessors panic on a name the payload does not carry, so the worker and its consumers cannot drift apart unnoticed. The tests describe the runtime as payload data where they used to mock the seam, with the datasets unchanged. The one loss is the call-count assertion of test_inet_pton_regression: the payload reports the result of `@inet_pton('::')` rather than answering a call. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe-php-rpc/php/worker.php')
-rw-r--r--crates/shirabe-php-rpc/php/worker.php134
1 files changed, 124 insertions, 10 deletions
diff --git a/crates/shirabe-php-rpc/php/worker.php b/crates/shirabe-php-rpc/php/worker.php
index 15b38770..578c30d0 100644
--- a/crates/shirabe-php-rpc/php/worker.php
+++ b/crates/shirabe-php-rpc/php/worker.php
@@ -455,6 +455,77 @@ ShirabeRpcRuntime::$stubAutoloader = static function (string $class): void {
};
spl_autoload_register(ShirabeRpcRuntime::$stubAutoloader, true, true);
+/**
+ * Port of Composer\Platform\Runtime, whose runtime queries feed the `platform` payload. Its
+ * parseHtmlExtensionInfo() has no counterpart here: the worker is always the CLI SAPI, so
+ * getExtensionInfo() never takes the branch that reformats phpinfo()'s HTML output.
+ */
+final class ShirabePlatformRuntime
+{
+ /** The constants the `platform` payload reports, named ltrim($class.'::'.$constant, ':'). */
+ public const CONSTANTS = [
+ 'PHP_VERSION',
+ 'PHP_DEBUG',
+ 'PHP_ZTS',
+ 'PHP_INT_SIZE',
+ 'AF_INET6',
+ 'GD_VERSION',
+ 'GMP_VERSION',
+ 'ICONV_VERSION',
+ 'INTL_ICU_VERSION',
+ 'LIBXML_DOTTED_VERSION',
+ 'MB_ONIGURUMA_VERSION',
+ 'OPENSSL_VERSION_TEXT',
+ 'PCRE_VERSION',
+ 'PGSQL_LIBPQ_VERSION',
+ 'RD_KAFKA_VERSION',
+ 'SODIUM_LIBRARY_VERSION',
+ 'LIBXSLT_DOTTED_VERSION',
+ 'ZipArchive::LIBZIP_VERSION',
+ 'ZLIB_VERSION',
+ ];
+
+ /** The extensions whose info() output the `platform` payload reports when they are loaded. */
+ public const EXTENSION_INFO = [
+ 'amqp',
+ 'bz2',
+ 'curl',
+ 'date',
+ 'fileinfo',
+ 'gd',
+ 'intl',
+ 'ldap',
+ 'mbstring',
+ 'memcached',
+ 'mongodb',
+ 'mysqlnd',
+ 'pcre',
+ 'pdo_mysql',
+ 'pdo_pgsql',
+ 'pdo_sqlite',
+ 'pgsql',
+ 'pq',
+ 'sqlite3',
+ 'ssh2',
+ 'xsl',
+ 'yaml',
+ 'zlib',
+ ];
+
+ /** The classes the `platform` payload reports the existence of. */
+ public const CLASSES = ['ResourceBundle', 'IntlChar'];
+
+ public static function getExtensionInfo(string $extension): string
+ {
+ $reflector = new ReflectionExtension($extension);
+
+ ob_start();
+ $reflector->info();
+
+ return (string) ob_get_clean();
+ }
+}
+
// Port of Composer\XdebugHandler\XdebugHandler::setXdebugDetails(), which the diagnose payload
// reports as `xdebug_active`.
$xdebug_active = static function (): bool {
@@ -491,9 +562,6 @@ $xdebug_active = static function (): bool {
ShirabeRpcRuntime::$dispatch = [
'constant' => static fn($args) => defined($args[0]) ? constant($args[0]) : null,
- 'inet_pton' => static fn($args) => @inet_pton($args[0]),
- 'curl_version' => static fn($args) => function_exists('curl_version') ? (curl_version()['version'] ?? null) : null,
- 'get_loaded_extensions' => static fn($args) => get_loaded_extensions(),
'get_all_ini_files' => static function ($args) {
$paths = [(string) php_ini_loaded_file()];
$scanned = php_ini_scanned_files();
@@ -502,14 +570,60 @@ ShirabeRpcRuntime::$dispatch = [
}
return $paths;
},
- 'extension_info' => static function ($args) {
- if (!extension_loaded($args[0])) {
- return '';
+ 'platform' => static function ($args) {
+ $extensions = get_loaded_extensions();
+
+ $extension_versions = [];
+ foreach ($extensions as $extension) {
+ $version = phpversion($extension);
+ $extension_versions[$extension] = $version !== false ? $version : '0';
}
- $re = new ReflectionExtension($args[0]);
- ob_start();
- $re->info();
- return (string) ob_get_clean();
+
+ $extension_info = [];
+ foreach (ShirabePlatformRuntime::EXTENSION_INFO as $extension) {
+ if (in_array($extension, $extensions, true)) {
+ $extension_info[$extension] = ShirabePlatformRuntime::getExtensionInfo($extension);
+ }
+ }
+
+ // Only the defined constants carry a value; `constant_names` tells the Rust side which
+ // names were looked up, so a name it reads but this list omits is an error rather than a
+ // silently undefined constant.
+ $constants = [];
+ foreach (ShirabePlatformRuntime::CONSTANTS as $constant) {
+ if (defined($constant)) {
+ $constants[$constant] = constant($constant);
+ }
+ }
+
+ $classes = [];
+ foreach (ShirabePlatformRuntime::CLASSES as $class) {
+ $classes[$class] = class_exists($class, false);
+ }
+
+ // The values below stand in for the PHP objects and calls PlatformRepository reaches
+ // through Composer\Platform\Runtime::invoke()/construct(), reduced to the entries it reads.
+ $resource_bundle = null;
+ if ($classes['ResourceBundle']) {
+ $bundle = ResourceBundle::create('root', 'ICUDATA', false);
+ if ($bundle !== null) {
+ $resource_bundle = ['Version' => $bundle->get('Version')];
+ }
+ }
+
+ return [
+ 'extensions' => $extensions,
+ 'extension_versions' => $extension_versions,
+ 'extension_info' => $extension_info,
+ 'constant_names' => ShirabePlatformRuntime::CONSTANTS,
+ 'constants' => $constants,
+ 'classes' => $classes,
+ 'curl_version' => extension_loaded('curl') ? curl_version() : null,
+ 'inet_pton_ipv6' => @inet_pton('::'),
+ 'resource_bundle' => $resource_bundle,
+ 'intl_char_unicode_version' => $classes['IntlChar'] ? IntlChar::getUnicodeVersion() : null,
+ 'imagick' => extension_loaded('imagick') ? (new Imagick())->getVersion() : null,
+ ];
},
'diagnose' => static function ($args) use ($xdebug_active) {
$extensions = [];