diff options
Diffstat (limited to 'crates/shirabe-php-rpc/php')
| -rw-r--r-- | crates/shirabe-php-rpc/php/runtime/Shirabe/MaterializedThrowable.php | 70 | ||||
| -rw-r--r-- | crates/shirabe-php-rpc/php/worker.php | 15 |
2 files changed, 80 insertions, 5 deletions
diff --git a/crates/shirabe-php-rpc/php/runtime/Shirabe/MaterializedThrowable.php b/crates/shirabe-php-rpc/php/runtime/Shirabe/MaterializedThrowable.php new file mode 100644 index 00000000..7227a5f3 --- /dev/null +++ b/crates/shirabe-php-rpc/php/runtime/Shirabe/MaterializedThrowable.php @@ -0,0 +1,70 @@ +<?php + +// The PHP half of the Throw frame's exception codec. A Rust-side failure carries the class it +// was thrown as, so `catch (TransportException $e)` in plugin code catches what it would catch +// under Composer, plus the state that class declares beyond message and code. + +namespace Shirabe; + +final class MaterializedThrowable +{ + /** + * Rebuilds the exception a Throw frame describes. A class the child cannot construct from a + * message and a code keeps the RuntimeException shape, which is all the frame guarantees. + * + * @param array<string, mixed> $properties + */ + public static function revive(string $class, string $message, int $code, array $properties): \Throwable + { + $exception = self::instantiate($class, $message, $code); + foreach ($properties as $name => $value) { + // A name the class does not declare is a Shirabe bug rather than a plugin one, and + // ReflectionProperty reports it as such instead of dropping the state silently. + $property = new \ReflectionProperty($exception, $name); + $property->setAccessible(true); + $property->setValue($exception, $value); + } + + return $exception; + } + + private static function instantiate(string $class, string $message, int $code): \Throwable + { + if ($class === '' || !class_exists($class) || !is_a($class, \Throwable::class, true)) { + return new \RuntimeException($message, $code); + } + $constructor = (new \ReflectionClass($class))->getConstructor(); + if ($constructor === null || !self::acceptsMessageAndCode($constructor)) { + return new \RuntimeException($message, $code); + } + + return new $class($message, $code); + } + + /** + * Whether the constructor has \Exception's shape as far as the frame fills it in: a string + * message, an int code, and nothing else required. + */ + private static function acceptsMessageAndCode(\ReflectionMethod $constructor): bool + { + $parameters = $constructor->getParameters(); + if (count($parameters) < 2) { + return false; + } + foreach ($parameters as $position => $parameter) { + $type = $parameter->getType(); + $name = $type instanceof \ReflectionNamedType ? $type->getName() : null; + if ($position === 0 && $name !== 'string') { + return false; + } + if ($position === 1 && $name !== 'int') { + return false; + } + if ($position >= 2 && !$parameter->isOptional()) { + return false; + } + } + + return true; + } +} diff --git a/crates/shirabe-php-rpc/php/worker.php b/crates/shirabe-php-rpc/php/worker.php index bc10c2de..defa4fb5 100644 --- a/crates/shirabe-php-rpc/php/worker.php +++ b/crates/shirabe-php-rpc/php/worker.php @@ -337,10 +337,13 @@ final class ShirabeRpcRuntime $outParams = self::fromWire($fields[1] ?? []); return self::fromWire($fields[0]); } - [$class, $message, $code] = $fields; - // TODO(plugin): reconstruct the original exception class instead of collapsing - // everything to RuntimeException. - throw new RuntimeException($message, (int) $code); + [$class, $message, $code, $properties] = $fields; + throw \Shirabe\MaterializedThrowable::revive( + $class, + $message, + (int) $code, + self::fromWire($properties) + ); } self::dispatchRequest($tag, $inId, $payload); } @@ -435,7 +438,9 @@ final class ShirabeRpcRuntime self::writeFrame( SHIRABE_TAG_THROW, $corrId, - serialize([get_class($e), $e->getMessage(), (int) $e->getCode()]) + // TODO(plugin): the properties field is empty in this direction; no Rust-side + // consumer rebuilds a ported exception from a Throw frame yet. + serialize([get_class($e), $e->getMessage(), (int) $e->getCode(), []]) ); } } |
