diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index 4108d1b..db61411 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -24,13 +24,14 @@ - [Class Object](./types/class_object.md) - [Closure](./types/closure.md) - [Functions & methods](./types/functions.md) - - [Async futures](./macros/impl.md#async) + - [Async futures](./macros/async_impl.md) - [Macros](./macros/index.md) - [Module](./macros/module.md) - [Module Startup Function](./macros/module_startup.md) - [Function](./macros/function.md) - [Classes](./macros/classes.md) - [`impl`s](./macros/impl.md) + - [async `impl`s](./macros/async_impl.md) - [Constants](./macros/constant.md) - [`ZvalConvert`](./macros/zval_convert.md) - [Exceptions](./exceptions.md) diff --git a/guide/src/macros/async_impl.md b/guide/src/macros/async_impl.md new file mode 100644 index 0000000..0912001 --- /dev/null +++ b/guide/src/macros/async_impl.md @@ -0,0 +1,130 @@ +# `#[php_async_impl]` + +Using `#[php_async_impl]` instead of `#[php_impl]` allows us to expose any async Rust library to PHP, using [PHP fibers](https://www.php.net/manual/en/language.fibers.php), [php-tokio](https://github.com/danog/php-tokio) and the [PHP Revolt event loop](https://revolt.run) under the hood to handle async interoperability. + +This allows full compatibility with [amphp](https://amphp.org), [PSL](https://github.com/azjezz/psl), [reactphp](https://reactphp.org) and any other async PHP library based on [Revolt](https://revolt.run). + +Traits annotated with `#[php_impl]` can freely expose any async function, using `await` and any async Rust library. + +Make sure to also expose the `php_tokio::EventLoop::init` and `php_tokio::EventLoop::wakeup` functions to PHP in order to initialize the event loop, as specified in the full example [here »](#async-example). + +Also, make sure to invoke `EventLoop::shutdown` in the request shutdown handler to clean up the tokio event loop before finishing the request. + +## Async example + +In this example, we're exposing an async Rust HTTP client library called [reqwest](https://docs.rs/reqwest/latest/reqwest/) to PHP, using [PHP fibers](https://www.php.net/manual/en/language.fibers.php), [php-tokio](https://github.com/danog/php-tokio) and the [PHP Revolt event loop](https://revolt.run) under the hood to handle async interoperability. + +This allows full compatibility with [amphp](https://amphp.org), [PSL](https://github.com/azjezz/psl), [reactphp](https://reactphp.org) and any other async PHP library based on [Revolt](https://revolt.run). + +Make sure to require [php-tokio](https://github.com/danog/php-tokio) as a dependency before proceeding. + +```rust,ignore +use ext_php_rs::prelude::*; +use php_tokio::{php_async_impl, EventLoop}; + +#[php_class] +struct Client {} + +#[php_async_impl] +impl Client { + pub fn init() -> PhpResult { + EventLoop::init() + } + pub fn wakeup() -> PhpResult<()> { + EventLoop::wakeup() + } + pub async fn get(url: &str) -> anyhow::Result { + Ok(reqwest::get(url).await?.text().await?) + } +} + +pub extern "C" fn request_shutdown(_type: i32, _module_number: i32) -> i32 { + EventLoop::shutdown(); + 0 +} + +#[php_module] +pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { + module.request_shutdown_function(request_shutdown) +} +``` + +Here's the async PHP code we use to interact with the Rust class we just exposed. + +The `Client::init` method needs to be called only once in order to initialize the Revolt event loop and link it to the Tokio event loop, as shown by the following code. + +See [here »](https://amphp.org) for more info on async PHP using [amphp](https://amphp.org) + [revolt](https://revolt.run). + +```php + \Client::wakeup()); + } + + public static function reference(): void + { + EventLoop::reference(self::$id); + } + public static function unreference(): void + { + EventLoop::unreference(self::$id); + } + + public static function __callStatic(string $name, array $args): mixed + { + return \Client::$name(...$args); + } +} + + +Client::init(); + +function test(int $delay): void +{ + $url = "https://httpbin.org/delay/$delay"; + $t = time(); + echo "Making async reqwest to $url that will return after $delay seconds...".PHP_EOL; + Client::get($url); + $t = time() - $t; + echo "Got response from $url after ~".$t." seconds!".PHP_EOL; +}; + +$futures = []; +$futures []= async(test(...), 5); +$futures []= async(test(...), 5); +$futures []= async(test(...), 5); + +await($futures); +``` + +Result: + +``` +Making async reqwest to https://httpbin.org/delay/5 that will return after 5 seconds... +Making async reqwest to https://httpbin.org/delay/5 that will return after 5 seconds... +Making async reqwest to https://httpbin.org/delay/5 that will return after 5 seconds... +Got response from https://httpbin.org/delay/5 after ~5 seconds! +Got response from https://httpbin.org/delay/5 after ~5 seconds! +Got response from https://httpbin.org/delay/5 after ~5 seconds! +``` + +[`php_function`]: ./function.md diff --git a/guide/src/macros/impl.md b/guide/src/macros/impl.md index 1b872f1..db12eca 100644 --- a/guide/src/macros/impl.md +++ b/guide/src/macros/impl.md @@ -8,7 +8,7 @@ implementations cannot be exported to PHP. If you do not want a function exported to PHP, you should place it in a separate `impl` block. -If you want to use async Rust, use `#[php_async_impl]`, instead: see [here »](#async) for more info. +If you want to use async Rust, use `#[php_async_impl]`, instead: see [here »](./async_impl.md) for more info. ## Methods @@ -65,20 +65,6 @@ the attribute, the function is not exported to PHP like a regular method. Constructors cannot use the visibility or rename attributes listed above. -### Async - -Using `#[php_async_impl]` instead of `#[php_impl]` allows us to expose any async Rust library to PHP, using [PHP fibers](https://www.php.net/manual/en/language.fibers.php), [php-tokio](https://github.com/danog/php-tokio) and the [PHP Revolt event loop](https://revolt.run) under the hood to handle async interoperability. - -This allows full compatibility with [amphp](https://amphp.org), [PSL](https://github.com/azjezz/psl), [reactphp](https://reactphp.org) and any other async PHP library based on [Revolt](https://revolt.run). - -Traits annotated with `#[php_impl]` can freely expose any async function, using `await` and any async Rust library. - -Make sure to also expose the `php_tokio::EventLoop::init` and `php_tokio::EventLoop::wakeup` functions to PHP in order to initialize the event loop, as specified in the full example [here »](#async-example). - -Also, make sure to invoke `EventLoop::shutdown` in the request shutdown handler to clean up the tokio event loop before finishing the request. - -See [here »](#async-example) for the full example. - ## Constants Constants are defined as regular Rust `impl` constants. Any type that implements @@ -178,121 +164,4 @@ var_dump(Human::get_max_age()); // int(100) var_dump(Human::MAX_AGE); // int(100) ``` -### Async example - -In this example, we're exposing an async Rust HTTP client library called [reqwest](https://docs.rs/reqwest/latest/reqwest/) to PHP, using [PHP fibers](https://www.php.net/manual/en/language.fibers.php), [php-tokio](https://github.com/danog/php-tokio) and the [PHP Revolt event loop](https://revolt.run) under the hood to handle async interoperability. - -This allows full compatibility with [amphp](https://amphp.org), [PSL](https://github.com/azjezz/psl), [reactphp](https://reactphp.org) and any other async PHP library based on [Revolt](https://revolt.run). - -Make sure to require [php-tokio](https://github.com/danog/php-tokio) as a dependency before proceeding. - -```rust,ignore -use ext_php_rs::prelude::*; -use php_tokio::{php_async_impl, EventLoop}; - -#[php_class] -struct Client {} - -#[php_async_impl] -impl Client { - pub fn init() -> PhpResult { - EventLoop::init() - } - pub fn wakeup() -> PhpResult<()> { - EventLoop::wakeup() - } - pub async fn get(url: &str) -> anyhow::Result { - Ok(reqwest::get(url).await?.text().await?) - } -} - -pub extern "C" fn request_shutdown(_type: i32, _module_number: i32) -> i32 { - EventLoop::shutdown(); - 0 -} - -#[php_module] -pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { - module.request_shutdown_function(request_shutdown) -} -``` - -Here's the async PHP code we use to interact with the Rust class we just exposed. - -The `Client::init` method needs to be called only once in order to initialize the Revolt event loop and link it to the Tokio event loop, as shown by the following code. - -See [here »](https://amphp.org) for more info on async PHP using [amphp](https://amphp.org) + [revolt](https://revolt.run). - -```php - \Client::wakeup()); - } - - public static function reference(): void - { - EventLoop::reference(self::$id); - } - public static function unreference(): void - { - EventLoop::unreference(self::$id); - } - - public static function __callStatic(string $name, array $args): mixed - { - return \Client::$name(...$args); - } -} - - -Client::init(); - -function test(int $delay): void -{ - $url = "https://httpbin.org/delay/$delay"; - $t = time(); - echo "Making async reqwest to $url that will return after $delay seconds...".PHP_EOL; - Client::get($url); - $t = time() - $t; - echo "Got response from $url after ~".$t." seconds!".PHP_EOL; -}; - -$futures = []; -$futures []= async(test(...), 5); -$futures []= async(test(...), 5); -$futures []= async(test(...), 5); - -await($futures); -``` - -Result: - -``` -Making async reqwest to https://httpbin.org/delay/5 that will return after 5 seconds... -Making async reqwest to https://httpbin.org/delay/5 that will return after 5 seconds... -Making async reqwest to https://httpbin.org/delay/5 that will return after 5 seconds... -Got response from https://httpbin.org/delay/5 after ~5 seconds! -Got response from https://httpbin.org/delay/5 after ~5 seconds! -Got response from https://httpbin.org/delay/5 after ~5 seconds! -``` - -[`php_function`]: ./function.md +[`php_async_impl`]: ./async_impl.md