aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe-php-rpc
AgeCommit message (Collapse)Author
2026-08-06feat(plugin): run plugin-provided installers through the RPC workernsfisis
A plugin can now hand an InstallerInterface implementation to InstallationManager::addInstaller across the wire, and a legacy composer-installer package is loaded as one; both are backed by a PhpInstallerProxy forwarding the whole installer contract to the entity in the PHP worker. An installer returning a real promise is an explicit error until promises can cross the boundary. InstallationManager takes installers as shared handles instead of boxes, so the object identity removeInstaller and PluginManager's registeredPlugins compare against survives registration, and holds them in a RefCell: Installer::run keeps a shared borrow of the manager for the whole run, and a plugin activated inside it registers its installer from there. The type cache keys on the installer itself, like upstream, so re-entrant registration cannot leave a stale index behind. InstallerInterface::supports is fallible for the same reason getCapabilities and getCommands are: it answers over RPC. Cloning a proxy stub clones the Rust-side entity and rebinds the copy to the fresh handle. Previously only the classes declaring __clone got a throwing body, and the rest let two stubs share (and twice release) one handle. Package entities answer with AnyPackage::dup, which already carries BasePackage::__clone and the RootAliasPackage override; the others are an explicit error. The package proxy covers the whole PackageInterface surface; only the link maps and the release date still lack a wire image for their value objects. PluginManager gains a test-only seam for the reported Plugin API version, and the three PluginInstallerTest cases that need it are ported. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05feat(plugin): run plugin-provided commands in a worker-side applicationnsfisis
A same-FQCN Composer\Console\Application, hand-written under the new php/runtime/ tree, hosts CommandProvider commands inside the PHP worker: PhpCommandProxy overrides run() and forwards the stringified input, so the real Symfony machinery binds, validates and executes against the live command object, while help/list render Rust-side from a definition read back at construction. Reverse \Shirabe\RustCommandStub rows let a plugin command invoke built-in commands back in the Rust process, keeping every command on the side whose helper set it was written for. Composer\EventDispatcher\Event moves from a generated stub to a dual-mode runtime class: the real BaseCommand::initialize constructs a PreCommandRunEvent natively in the worker, which a proxy-only constructor guard rejected. Its PRE_COMMAND_RUN dispatch reaches a new EventDispatcher stub whose dispatch supports the observably-no-op no-listener case and fails explicitly otherwise. The stub generator now accepts runtime-provided classes as stub bases (never as targets) and cross-checks the Application handoff property table against the real class. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04feat(plugin): generate proxy stubs for the package and repository graphnsfisis
Emit the eleven stubs a subscriber plugin's object-graph calls reach (BasePackage through RootPackage, the installed-repository hierarchy, RepositoryManager, InstallationManager). The generator learns the member kinds these classes need: builtin interfaces contribute no closure entries, public static properties are materialized, public instance properties forward through __get/__set, __toString forwards like a plain method, __clone throws (proxy clone semantics are still an open design question), and a subclass stub may add interfaces to its inherited surface. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04feat(plugin): dispatch plugin event subscribers through the RPC workernsfisis
Wires the addPlugin subscriber branch end to end: EventSubscriberInterface and Capable become fallible and dyn-compatible (their sole implementor is the PHP plugin proxy, which answers getSubscribedEvents over RPC), listeners register as Callable::PhpMethod and are invoked with a per-call event handle, and the R table now drops entries when a child-side stub destructs. The R table keeps its IndexMap with monotonically increasing handles, so released handles are never reused and no generation counter is needed. Upstream has no subscriber-plugin test, so the path is covered by a Shirabe-owned fixture exercising all three getSubscribedEvents shapes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04feat(plugin): generate the worker proxy stubs from the Composer sourcesnsfisis
Replace the hand-written proxy stubs under crates/shirabe-php-rpc/php/stubs with output of scripts/plugin-stub-generator, a deterministic emitter that derives every stub from the Composer checkout and the classifier report. Anything it cannot faithfully proxy (by-ref/variadic parameters, magic methods, public properties, diverging omitted overrides, stale stub files, a STUB_FILES entry missing in lib.rs) fails generation instead of degrading silently, so future Composer releases surface new members as explicit errors rather than silent gaps. Regenerating the stubs also normalizes the hand-written inconsistencies (uniform guarded constructors, import-based type spellings) and fixes real gaps the review of the generated diff uncovered: the BaseIO authentication methods now carry the real class's untyped signatures, and the previously missing ConsoleIO::sanitize is materialized together with its private static helper. A cargo test runs generate-stubs --check to keep the committed stubs, the generator and the embedded list from drifting apart. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04fix(plugin): resolve review findings in the plugin activation flownsfisis
InstallationManager::execute now takes &self (the mock recorder moved into a RefCell) and its callers hold only shared borrows: plugin registration inside a batch re-enters the same manager handle through Composer::getInstallationManager()->getInstallPath(), which panicked on the RefCell re-borrow under the &mut shape — the same re-entrancy the repository side already fixed, unreachable from the ported tests because they call PluginInstaller::install directly like PHPUnit does. The worker-side InstalledVersions mirror now matches the full tail of FilesystemRepository::write: unconditional reload plus the reflection-based selfDir/installedIsLocalDir restore. The previous class_exists(false) guard rested on a lazy-load assumption that does not hold in the worker (its real ClassLoader only knows the Composer checkout's vendor dir, so a later lazy load would read the checkout's installed.php, not the project's); the mirror is now skipped only when the class is not autoloadable at all, i.e. no plugin runtime and hence no observer code. Boot-time seeding stays TODO(plugin). Also from the review: registered_plugins entries are removed only after the deactivate/uninstall loop (PHP unsets last, and a throw must leave the entry observable); extra.class keeps associative-array values and fails loudly on non-strings instead of silently dropping them; the two discarded write() results now propagate (they carry the reload-push failure); register_package's allow-plugins skip message is DEBUG like the addPlugin side; the loader-eviction divergence of REGISTERED_LOADERS and the lossy UTF-8 spots carry searchable markers; the test-only proxy downcast follows the __ naming rule; the R-table dispatch clones the entity out instead of holding the table borrow across the handler; the IO/PartialComposer stubs turn a plugin-side `new NullIO()` into an explicit error instead of an ArgumentCountError; and the empty() emulation covers float 0.0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04feat(plugin): activate plugins through the PHP RPC workernsfisis
Implement the remainder of PluginManager::registerPackage: the plugin autoload map is built by the ported createLoader/parseAutoloads and served to the worker over the existing reverse-RPC autoloader, files entries go through a composerRequire-equivalent glue call, and already-defined classes take the upstream _composer_tmp rename/eval path. Instantiation uses the new NewObject/CallPhpMethod lanes backed by a P table in the worker; PhpPluginProxy adapts the resulting handle to PluginInterface, with $composer/$io exposed to plugin callbacks via an R table (unsupported methods stay explicit errors). Hand-written proxy stubs cover Composer, PartialComposer and the IO hierarchy, and the stub autoloader is re-prepended after loading the Composer PHP runtime so its vendor autoloader cannot shadow proxied FQCNs. FilesystemRepository::write now mirrors InstalledVersions::reload into a running worker (class_exists-guarded, so an unloaded class keeps its upstream lazy-load behavior), removing the previously undefined observation window. The installer pipeline passes the installed repository as a shared handle instead of a long-lived `&mut dyn`: plugin registration runs inside InstallationManager::execute and re-enters the same local repository through the RepositoryManager, which would panic on the RefCell re-borrow under the old shape. PluginInterface lifecycle methods now take an owned ComposerHandle (plugins retain $composer past the call) and return anyhow::Result (PHP plugin code may throw); the plugin list uses shared ownership so the identity comparison of removePlugin survives the dual storage in registeredPlugins, matching PHP reference semantics. Ports the activate/upgrade/uninstall tests of PluginInstallerTest, serialized across the shared worker process whose persistent class table is exactly what exercises the rename path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03feat(php-rpc): rework the RPC channel into the tagged plugin protocolnsfisis
Replace the name\0arg framing with the plugin wire protocol: tagged frames with corr_id multiplexing, a MAX_FRAME_LEN bound, a thread-ID based reentrant SessionLock, and the PluginValue codec (encoder plus the first recursive decoder, iterative with a 512-level depth cap). Float formatting is ported from php-src into shirabe-php-src so the encoder is byte-compatible with serialize() under serialize_precision=-1, which the spawned worker now pins. The worker gains a standing dispatch loop, CallRustMethod reentrancy, hand-written Event proxy stubs, and explicit-error answers for everything not implemented yet. The public query API (get_php_version and friends) is unchanged and now rides the new protocol; the codec is verified against real PHP by roundtrip oracle tests covering floats, non-UTF-8 bytes and deep nesting. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02chore(todo): consolidate TODO comments into the five fixed marker tagsnsfisis
Retag every Shirabe-authored TODO comment to one of the fixed tags: phase-c, phase-d, plugin, php-runtime, phase-e. Upstream-authored TODO comments from Composer/Symfony are left untouched to preserve the ported code shape. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26feat(diagnose-command): report the real curl versionnsfisis
The line was a "TODO: curl_version()" placeholder. Extend the diagnose payload with curl_version() and the CURL_* constants getCurlVersion() consults, so the libz/brotli/zstd/ssl/HTTP details come from the PHP runtime instead of being guessed. curl_version() is only reachable while the extension is loaded, mirroring the ioncube_loader_* entries. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26feat(diagnose-command): query the real PHP runtime in one RPC callnsfisis
diagnose used to read hardcoded shim stubs, so it described a fictional runtime: OPENSSL_VERSION_NUMBER was always 0 and tripped the TLSv1.1/1.2 check, PHP_BINARY and OPENSSL_VERSION_TEXT were empty, and the extension, function and ini probes answered from a fixed table. The PHP worker gained a `diagnose` entry that returns every fact the command needs as one PHP array, cached in a OnceLock so the several call sites share a single round trip. Reading it back needed array support in the serialize() parser, which in turn lets get_loaded_extensions and get_all_ini_files return real lists instead of comma-joined strings. Also fixes the openssl_version message, which dropped strstr()'s before_needle argument during the port, and check_connectivity's allow_url_fopen test, which did not follow PHP string truthiness. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-24fix(dependency-resolver): query real PHP for ext-* version/loaded checksnsfisis
Two shim gaps made the extension-related branches of solver-problem messages wrong: - phpversion($ext) with a non-empty extension can't be known statically (it's shirabe-php-shim's todo!()); Problem::get_missing_package_reason now calls shirabe_php_rpc::phpversion, the same RPC bridge platform::runtime::Runtime::get_extension_version already uses. - extension_loaded's hardcoded allowlist was missing "pcre", a mandatory always-compiled-in PHP extension, so ext-pcre was misreported as "missing from your system" instead of "disabled by your platform config" whenever a platform override disabled it. Also fix XdebugHandler::getAllIniFiles() always returning `[""]` (a php-runtime stub): create_extension_hint()'s early-return guard (`paths[0] empty && len==1`) fired unconditionally, silently dropping the entire "To enable extensions..." hint from every solver-problem message that mentions missing extensions. shirabe-external-packages can't depend on shirabe-php-rpc (shirabe-php-rpc already depends on shirabe-external-packages), so IniHelper::get_all() queries a new get_all_ini_files RPC command directly instead of going through the stub. This exposed that XdebugHandler is never constructed with a name because bin/composer's restart-without-Xdebug bootstrap was never ported to main.rs, making COMPOSER_ORIGINAL_INIS-driven behavior unreachable; documented with a TODO(phase-c) and updated ini_helper_test.rs's ignore reasons (and ignored test_with_no_ini, which only passed before by coincidence with the old stub's constant output) to match.
2026-07-20feat(php-rpc): attach worker process state to request I/O errorsnsfisis
A dead worker and a live one hitting a framing bug both surface as a raw socket I/O error (e.g. "Broken pipe"), which doesn't say whether the child crashed, was signaled, or is still running. Query the child's exit status via try_wait() and attach it as anyhow::Context so the panic message shows the root cause directly.
2026-07-20feat(php-rpc): implement phpinfo() capture over RPCnsfisis
DiagnoseCommand::check_platform needed phpinfo() output but shirabe-php-shim's ob_start()/phpinfo()/ob_get_clean() are unmodeled todo!()s. Add a phpinfo dispatch entry to the PHP worker (capturing output the same way extension_info already does) and a get_phpinfo() wrapper, then switch check_platform to call it directly. This unblocks the phpinfo-related panic in diagnose; the ignored tests now hit a separate RefCell double-borrow bug in check_http, so their ignore reasons are updated to point at that instead.
2026-07-04fix(php-rpc): panic on RPC failure instead of silently swallowing itnsfisis
Worker spawn failure, socket I/O errors, and unparseable responses used to fall back to plausible-looking defaults (empty string, false, None), making real failures indistinguishable from legitimate PHP-side results. Panicking is not the final design, but replaces silent data corruption with a loud failure until proper error propagation is implemented.
2026-07-04fix(php-rpc): wire Runtime::invoke/get_extension_info/extensionsnsfisis
Route the platform-repository seams that need real PHP introspection through php-rpc instead of todo!()/hardcoded shims: - Runtime::invoke now handles the two dynamic callables PlatformRepository actually reaches (inet_pton, curl_version) via new php-rpc calls; other callables remain unsupported. - Runtime::get_extension_info uses a new `extension_info` php-rpc call (ReflectionExtension::info() + output buffering) instead of todo!(). - Runtime::get_extensions/get_extension_version now query the real PHP process (get_loaded_extensions, phpversion) instead of the shirabe-php-shim's hardcoded "standard CLI environment" model, so platform requirement checks see the extensions actually installed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-02feat(php-rpc): implement Runtime::has_constant/get_constant via php-rpcnsfisis
Runtime::hasConstant/getConstant need a real PHP interpreter's defined()/ constant() to answer platform requirement checks (e.g. PHP_ZTS, PHP_INT_SIZE), which the shim can't provide since Rust constants aren't queryable by string. Extend shirabe-php-rpc's protocol to carry one string argument and return the full PHP scalar range, add defined/constant dispatch entries to the worker, and wire Runtime and get_php_version/get_php_binary onto them.
2026-07-02chore(lint): ban std::io::Read/Write, Any, Command use importsnsfisis
Extends no_banned_use to cover std::any::Any, std::io::Read/Write, and std::process::Command, and teaches the linter to allow `as _` imports so trait methods can still be brought into scope without binding the banned name. Fully qualifies all existing usages across the codebase.
2026-07-01test(php-rpc): cover serialize parser, framing, and PHP querynsfisis
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01feat(php-rpc): query real PHP version and binary for --versionnsfisis
Add a minimal shirabe-php-rpc crate that spawns the system PHP as a child process and asks it for runtime information over a Unix domain socket, then use it to fill the `--version` PHP line with the real \PHP_VERSION and \PHP_BINARY instead of fixed placeholder values. See docs/dev/php-rpc.md for the design and scope. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>