aboutsummaryrefslogtreecommitdiffhomepage
path: root/docs/dev/php-rpc.md
blob: 1ec817107f5e7826d1965320eddd3ec9f97f07d5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# PHP RPC

Composer can require a specific PHP version or loaded extensions, i.e., platform requirements.
To mimic this behavior needs a real PHP runtime.

This document describes a first design of PHP runtime: a `shirabe-php-rpc` crate that spawns the
system PHP as a child process and asks it for runtime information over a Unix domain socket.

## Scope

The crate supports exactly one interaction pattern, and nothing else:

> Rust calls a named PHP function, passing a single string argument, and receives a single scalar
> back.

- Rust to PHP only. PHP never calls back into Rust.
- Exactly one argument, and it must be a string.
- Scalar return values only (string / int / float / bool / null).
- Every failure `panic!`s: a PHP exception (not currently possible — the glue script never lets
  one escape), a serialization/deserialization failure, a missing PHP function, a crashed child,
  etc. None are handled as recoverable errors. This is not the final design — a future revision
  will replace these panics with proper `Result` propagation — but panicking beats silently
  returning a plausible-looking default.
- Windows is unsupported and `panic!`s for now.

## Locating PHP

Reuse the existing `PhpExecutableFinder` class to resolve the PHP binary.

## Transport

- A Unix domain socket. (No Windows support for now)
- The PHP glue code is a small script written to a temporary file.
- Message frame: `[usize length (little-endian)][payload]`.
  - Request payload: the PHP function name as raw bytes, followed by a `\0` byte and the string
    argument (function names are static literals and never contain `\0`, so the first `\0`
    unambiguously separates name from argument).
  - Response payload: `serialize()` of the function's return value — any of `N;` (null), `b:0/1;`
    (bool), `i:<n>;` (int), `d:<f>;` (float), or `s:<len>:"<bytes>";` (string).

The PHP worker is a single read-eval-respond loop: read a framed function name and argument, call
the matching entry in a fixed dispatch table (`defined`, `constant`), send back
`serialize($result)`.

## Global state and public API

PHP runtime information (e.g., process handle) is held as process-global state
rather than threaded through call sites for now.
The crate exposes plain free functions. For example:

* get_php_version()
* has_constant()
* get_constant()

The connection is a process-global `static` (e.g. `OnceLock<Mutex<Worker>>`), lazily initialized on
the first call: the first call spawns the child, performs the handshake, and caches the connection.
Commands that never query PHP never start it. The child lives for the rest of the process and is
left to be reaped at exit (no explicit shutdown message).

A future revision threads this runtime information through arguments or embeds it in structs; for now
callers just reach for the global getter.

## Out of scope

Deferred things: multiple/non-string arguments, non-scalar return values, PHP to Rust callbacks
and re-entrancy, object handles / proxies / identity, stub generation, error
propagation, GC / lifecycle, and Windows support.