aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe-php-rpc/src/value.rs
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-08-31 00:15:52 +0900
committernsfisis <nsfisis@gmail.com>2026-08-31 00:15:52 +0900
commit6a6ec1b8f8a5c70d21f3772ce637b763e8ab21ea (patch)
tree5a23dda75a0a8eb39478b77da82cf582e21a1d2c /crates/shirabe-php-rpc/src/value.rs
parentdc1f030e3904677cd65eac0a90c1d850e0ad1bbf (diff)
downloadphp-shirabe-6a6ec1b8f8a5c70d21f3772ce637b763e8ab21ea.tar.gz
php-shirabe-6a6ec1b8f8a5c70d21f3772ce637b763e8ab21ea.tar.zst
php-shirabe-6a6ec1b8f8a5c70d21f3772ce637b763e8ab21ea.zip
feat(plugin): carry Http\Response across as a materialized value
A response is built for one request and the graph never retains it, so there is no entity for a handle to point at. The child holds a real instance instead, revived from the object record the wire carries, and collect() frees the copy each world holds — which is what that method is for. The value-object rule rejects the class only because collect() assigns to $this, so the category comes from an overrides.list entry. HttpDownloader::get() and copy() answer with one. Two gaps stay: decodeJson() reaches Composer\Json\JsonFile, which a guard shadows, and Composer answers a curl request with the CurlResponse subclass where this port flattens the value into a Response. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe-php-rpc/src/value.rs')
-rw-r--r--crates/shirabe-php-rpc/src/value.rs47
1 files changed, 47 insertions, 0 deletions
diff --git a/crates/shirabe-php-rpc/src/value.rs b/crates/shirabe-php-rpc/src/value.rs
index 7203c9fb..a3e14d45 100644
--- a/crates/shirabe-php-rpc/src/value.rs
+++ b/crates/shirabe-php-rpc/src/value.rs
@@ -70,6 +70,18 @@ impl PhpObject {
pub fn set_protected(&mut self, name: &str, value: PluginValue) {
self.props.insert(protected_key(name), value);
}
+
+ /// PHP mangles a private property name to `\0<declaring class>\0name`. The declaring class is
+ /// the one whose body holds the `private` declaration, which is not `self.class` once a
+ /// subclass inherits it.
+ pub fn private(&self, declaring_class: &str, name: &str) -> Option<&PluginValue> {
+ self.props
+ .get(private_key(declaring_class, name).as_slice())
+ }
+
+ pub fn set_private(&mut self, declaring_class: &str, name: &str, value: PluginValue) {
+ self.props.insert(private_key(declaring_class, name), value);
+ }
}
fn protected_key(name: &str) -> Vec<u8> {
@@ -78,6 +90,14 @@ fn protected_key(name: &str) -> Vec<u8> {
key
}
+fn private_key(declaring_class: &str, name: &str) -> Vec<u8> {
+ let mut key = vec![0];
+ key.extend_from_slice(declaring_class.as_bytes());
+ key.push(0);
+ key.extend_from_slice(name.as_bytes());
+ key
+}
+
/// The value model of the plugin RPC boundary: PHP scalars, arrays, object records, and handle
/// descriptors.
///
@@ -795,6 +815,19 @@ mod tests {
b"O:45:\"Composer\\Semver\\Constraint\\MatchAllConstraint\":1:{s:15:\"\0*\0prettyString\";N;}".as_slice(),
);
+ // A private property carries the declaring class rather than `*`, which is what lets a
+ // subclass hold its own property of the same name.
+ let mut response = PhpObject::new("Composer\\Util\\Http\\Response");
+ response.set_private(
+ "Composer\\Util\\Http\\Response",
+ "code",
+ PluginValue::Int(200),
+ );
+ assert_eq!(
+ serialize(&PluginValue::PhpObject(response)),
+ b"O:27:\"Composer\\Util\\Http\\Response\":1:{s:33:\"\0Composer\\Util\\Http\\Response\0code\";i:200;}".as_slice(),
+ );
+
let mut date = PhpObject::new("DateTimeImmutable");
date.set_public("date", PluginValue::string("2026-08-07 12:34:56.123456"));
date.set_public("timezone_type", PluginValue::Int(3));
@@ -819,6 +852,20 @@ mod tests {
assert_eq!(inner.protected("operator"), Some(&PluginValue::Int(4)));
assert_eq!(inner.public("operator"), None);
assert_eq!(outer.protected("prettyConstraint"), None);
+
+ let mut response = PhpObject::new("Composer\\Util\\Http\\Response");
+ response.set_private(
+ "Composer\\Util\\Http\\Response",
+ "body",
+ PluginValue::string("{}"),
+ );
+ roundtrip(PluginValue::PhpObject(response.clone()));
+ assert_eq!(
+ response.private("Composer\\Util\\Http\\Response", "body"),
+ Some(&PluginValue::string("{}"))
+ );
+ assert_eq!(response.protected("body"), None);
+ assert_eq!(response.public("body"), None);
}
/// PHP numbers every value of a payload, including the ones inside an object and the